1use crate::{math::approx_powf, pipeline};
8use tiny_skia_path::{NormalizedF32, Scalar};
9
10#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
11use tiny_skia_path::NoStdFloat;
12
13pub type AlphaU8 = u8;
15
16pub const ALPHA_U8_TRANSPARENT: AlphaU8 = 0x00;
18
19pub const ALPHA_U8_OPAQUE: AlphaU8 = 0xFF;
21
22pub const ALPHA_TRANSPARENT: NormalizedF32 = NormalizedF32::ZERO;
24
25pub const ALPHA_OPAQUE: NormalizedF32 = NormalizedF32::ONE;
27
28#[repr(transparent)]
32#[derive(Copy, Clone, PartialEq)]
33pub struct ColorU8([u8; 4]);
34
35impl ColorU8 {
36 pub const fn from_rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
38 ColorU8([r, g, b, a])
39 }
40
41 pub const fn red(self) -> u8 {
43 self.0[0]
44 }
45
46 pub const fn green(self) -> u8 {
48 self.0[1]
49 }
50
51 pub const fn blue(self) -> u8 {
53 self.0[2]
54 }
55
56 pub const fn alpha(self) -> u8 {
58 self.0[3]
59 }
60
61 pub fn is_opaque(&self) -> bool {
65 self.alpha() == ALPHA_U8_OPAQUE
66 }
67
68 pub fn premultiply(&self) -> PremultipliedColorU8 {
70 let a = self.alpha();
71 if a != ALPHA_U8_OPAQUE {
72 PremultipliedColorU8::from_rgba_unchecked(
73 premultiply_u8(self.red(), a),
74 premultiply_u8(self.green(), a),
75 premultiply_u8(self.blue(), a),
76 a,
77 )
78 } else {
79 PremultipliedColorU8::from_rgba_unchecked(self.red(), self.green(), self.blue(), a)
80 }
81 }
82}
83
84impl core::fmt::Debug for ColorU8 {
85 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
86 f.debug_struct("ColorU8")
87 .field("r", &self.red())
88 .field("g", &self.green())
89 .field("b", &self.blue())
90 .field("a", &self.alpha())
91 .finish()
92 }
93}
94
95#[repr(transparent)]
99#[derive(Copy, Clone, PartialEq)]
100pub struct PremultipliedColorU8([u8; 4]);
101
102unsafe impl bytemuck::Zeroable for PremultipliedColorU8 {}
104unsafe impl bytemuck::Pod for PremultipliedColorU8 {}
105
106impl PremultipliedColorU8 {
107 pub const TRANSPARENT: Self = PremultipliedColorU8::from_rgba_unchecked(0, 0, 0, 0);
109
110 pub fn from_rgba(r: u8, g: u8, b: u8, a: u8) -> Option<Self> {
114 if r <= a && g <= a && b <= a {
115 Some(PremultipliedColorU8([r, g, b, a]))
116 } else {
117 None
118 }
119 }
120
121 pub(crate) const fn from_rgba_unchecked(r: u8, g: u8, b: u8, a: u8) -> Self {
123 PremultipliedColorU8([r, g, b, a])
124 }
125
126 pub const fn red(self) -> u8 {
130 self.0[0]
131 }
132
133 pub const fn green(self) -> u8 {
137 self.0[1]
138 }
139
140 pub const fn blue(self) -> u8 {
144 self.0[2]
145 }
146
147 pub const fn alpha(self) -> u8 {
149 self.0[3]
150 }
151
152 pub fn is_opaque(&self) -> bool {
156 self.alpha() == ALPHA_U8_OPAQUE
157 }
158
159 pub fn demultiply(&self) -> ColorU8 {
161 let alpha = self.alpha();
162 if alpha == ALPHA_U8_OPAQUE {
163 ColorU8(self.0)
164 } else {
165 let a = alpha as f64 / 255.0;
166 ColorU8::from_rgba(
167 (self.red() as f64 / a + 0.5) as u8,
168 (self.green() as f64 / a + 0.5) as u8,
169 (self.blue() as f64 / a + 0.5) as u8,
170 alpha,
171 )
172 }
173 }
174}
175
176impl core::fmt::Debug for PremultipliedColorU8 {
177 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
178 f.debug_struct("PremultipliedColorU8")
179 .field("r", &self.red())
180 .field("g", &self.green())
181 .field("b", &self.blue())
182 .field("a", &self.alpha())
183 .finish()
184 }
185}
186
187#[derive(Copy, Clone, PartialEq, Debug)]
193pub struct Color {
194 r: NormalizedF32,
195 g: NormalizedF32,
196 b: NormalizedF32,
197 a: NormalizedF32,
198}
199
200const NV_ZERO: NormalizedF32 = NormalizedF32::ZERO;
201const NV_ONE: NormalizedF32 = NormalizedF32::ONE;
202
203impl Color {
204 pub const TRANSPARENT: Color = Color {
206 r: NV_ZERO,
207 g: NV_ZERO,
208 b: NV_ZERO,
209 a: NV_ZERO,
210 };
211 pub const BLACK: Color = Color {
213 r: NV_ZERO,
214 g: NV_ZERO,
215 b: NV_ZERO,
216 a: NV_ONE,
217 };
218 pub const WHITE: Color = Color {
220 r: NV_ONE,
221 g: NV_ONE,
222 b: NV_ONE,
223 a: NV_ONE,
224 };
225
226 pub const unsafe fn from_rgba_unchecked(r: f32, g: f32, b: f32, a: f32) -> Self {
232 Color {
233 r: NormalizedF32::new_unchecked(r),
234 g: NormalizedF32::new_unchecked(g),
235 b: NormalizedF32::new_unchecked(b),
236 a: NormalizedF32::new_unchecked(a),
237 }
238 }
239
240 pub fn from_rgba(r: f32, g: f32, b: f32, a: f32) -> Option<Self> {
244 Some(Color {
245 r: NormalizedF32::new(r)?,
246 g: NormalizedF32::new(g)?,
247 b: NormalizedF32::new(b)?,
248 a: NormalizedF32::new(a)?,
249 })
250 }
251
252 pub fn from_rgba8(r: u8, g: u8, b: u8, a: u8) -> Self {
256 Color {
257 r: NormalizedF32::new_u8(r),
258 g: NormalizedF32::new_u8(g),
259 b: NormalizedF32::new_u8(b),
260 a: NormalizedF32::new_u8(a),
261 }
262 }
263
264 pub fn red(&self) -> f32 {
268 self.r.get()
269 }
270
271 pub fn green(&self) -> f32 {
275 self.g.get()
276 }
277
278 pub fn blue(&self) -> f32 {
282 self.b.get()
283 }
284
285 pub fn alpha(&self) -> f32 {
289 self.a.get()
290 }
291
292 pub fn set_red(&mut self, c: f32) {
296 self.r = NormalizedF32::new_clamped(c);
297 }
298
299 pub fn set_green(&mut self, c: f32) {
303 self.g = NormalizedF32::new_clamped(c);
304 }
305
306 pub fn set_blue(&mut self, c: f32) {
310 self.b = NormalizedF32::new_clamped(c);
311 }
312
313 pub fn set_alpha(&mut self, c: f32) {
317 self.a = NormalizedF32::new_clamped(c);
318 }
319
320 pub fn apply_opacity(&mut self, opacity: f32) {
327 self.a = NormalizedF32::new_clamped(self.a.get() * opacity.bound(0.0, 1.0));
328 }
329
330 pub fn is_opaque(&self) -> bool {
334 self.a == ALPHA_OPAQUE
335 }
336
337 pub fn premultiply(&self) -> PremultipliedColor {
339 if self.is_opaque() {
340 PremultipliedColor {
341 r: self.r,
342 g: self.g,
343 b: self.b,
344 a: self.a,
345 }
346 } else {
347 PremultipliedColor {
348 r: NormalizedF32::new_clamped(self.r.get() * self.a.get()),
349 g: NormalizedF32::new_clamped(self.g.get() * self.a.get()),
350 b: NormalizedF32::new_clamped(self.b.get() * self.a.get()),
351 a: self.a,
352 }
353 }
354 }
355
356 pub fn to_color_u8(&self) -> ColorU8 {
358 let c = color_f32_to_u8(self.r, self.g, self.b, self.a);
359 ColorU8::from_rgba(c[0], c[1], c[2], c[3])
360 }
361}
362
363#[derive(Copy, Clone, PartialEq, Debug)]
370pub struct PremultipliedColor {
371 r: NormalizedF32,
372 g: NormalizedF32,
373 b: NormalizedF32,
374 a: NormalizedF32,
375}
376
377impl PremultipliedColor {
378 pub fn red(&self) -> f32 {
383 self.r.get()
384 }
385
386 pub fn green(&self) -> f32 {
391 self.g.get()
392 }
393
394 pub fn blue(&self) -> f32 {
399 self.b.get()
400 }
401
402 pub fn alpha(&self) -> f32 {
406 self.a.get()
407 }
408
409 pub fn demultiply(&self) -> Color {
411 let a = self.a.get();
412 if a == 0.0 {
413 Color::TRANSPARENT
414 } else {
415 Color {
416 r: NormalizedF32::new_clamped(self.r.get() / a),
417 g: NormalizedF32::new_clamped(self.g.get() / a),
418 b: NormalizedF32::new_clamped(self.b.get() / a),
419 a: self.a,
420 }
421 }
422 }
423
424 pub fn to_color_u8(&self) -> PremultipliedColorU8 {
426 let c = color_f32_to_u8(self.r, self.g, self.b, self.a);
427 PremultipliedColorU8::from_rgba_unchecked(c[0], c[1], c[2], c[3])
428 }
429}
430
431pub fn premultiply_u8(c: u8, a: u8) -> u8 {
433 let prod = u32::from(c) * u32::from(a) + 128;
434 ((prod + (prod >> 8)) >> 8) as u8
435}
436
437fn color_f32_to_u8(
438 r: NormalizedF32,
439 g: NormalizedF32,
440 b: NormalizedF32,
441 a: NormalizedF32,
442) -> [u8; 4] {
443 [
444 (r.get() * 255.0 + 0.5) as u8,
445 (g.get() * 255.0 + 0.5) as u8,
446 (b.get() * 255.0 + 0.5) as u8,
447 (a.get() * 255.0 + 0.5) as u8,
448 ]
449}
450
451#[derive(Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Debug)]
455pub enum ColorSpace {
456 #[default]
458 Linear,
459
460 Gamma2,
465
466 SimpleSRGB,
471
472 FullSRGBGamma,
477}
478
479impl ColorSpace {
480 pub(crate) fn expand_channel(self, x: NormalizedF32) -> NormalizedF32 {
481 match self {
482 ColorSpace::Linear => x,
483 ColorSpace::Gamma2 => x * x,
484 ColorSpace::SimpleSRGB => NormalizedF32::new_clamped(approx_powf(x.get(), 2.2)),
485 ColorSpace::FullSRGBGamma => {
486 let x = x.get();
487 let x = if x <= 0.04045 {
488 x / 12.92
489 } else {
490 approx_powf((x + 0.055) / 1.055, 2.4)
491 };
492 NormalizedF32::new_clamped(x)
493 }
494 }
495 }
496
497 pub(crate) fn expand_color(self, mut color: Color) -> Color {
498 color.r = self.expand_channel(color.r);
499 color.g = self.expand_channel(color.g);
500 color.b = self.expand_channel(color.b);
501 color
502 }
503
504 #[allow(unused)]
505 pub(crate) fn compress_channel(self, x: NormalizedF32) -> NormalizedF32 {
506 match self {
507 ColorSpace::Linear => x,
508 ColorSpace::Gamma2 => NormalizedF32::new_clamped(x.get().sqrt()),
509 ColorSpace::SimpleSRGB => NormalizedF32::new_clamped(approx_powf(x.get(), 0.45454545)),
510 ColorSpace::FullSRGBGamma => {
511 let x = x.get();
512 let x = if x <= 0.0031308 {
513 x * 12.92
514 } else {
515 approx_powf(x, 0.416666666) * 1.055 - 0.055
516 };
517 NormalizedF32::new_clamped(x)
518 }
519 }
520 }
521
522 pub(crate) fn expand_stage(self) -> Option<pipeline::Stage> {
523 match self {
524 ColorSpace::Linear => None,
525 ColorSpace::Gamma2 => Some(pipeline::Stage::GammaExpand2),
526 ColorSpace::SimpleSRGB => Some(pipeline::Stage::GammaExpand22),
527 ColorSpace::FullSRGBGamma => Some(pipeline::Stage::GammaExpandSrgb),
528 }
529 }
530 pub(crate) fn expand_dest_stage(self) -> Option<pipeline::Stage> {
531 match self {
532 ColorSpace::Linear => None,
533 ColorSpace::Gamma2 => Some(pipeline::Stage::GammaExpandDestination2),
534 ColorSpace::SimpleSRGB => Some(pipeline::Stage::GammaExpandDestination22),
535 ColorSpace::FullSRGBGamma => Some(pipeline::Stage::GammaExpandDestinationSrgb),
536 }
537 }
538 pub(crate) fn compress_stage(self) -> Option<pipeline::Stage> {
539 match self {
540 ColorSpace::Linear => None,
541 ColorSpace::Gamma2 => Some(pipeline::Stage::GammaCompress2),
542 ColorSpace::SimpleSRGB => Some(pipeline::Stage::GammaCompress22),
543 ColorSpace::FullSRGBGamma => Some(pipeline::Stage::GammaCompressSrgb),
544 }
545 }
546}
547
548#[cfg(test)]
549mod tests {
550 use super::*;
551
552 #[test]
553 fn premultiply_u8() {
554 assert_eq!(
555 ColorU8::from_rgba(10, 20, 30, 40).premultiply(),
556 PremultipliedColorU8::from_rgba_unchecked(2, 3, 5, 40)
557 );
558 }
559
560 #[test]
561 fn premultiply_u8_opaque() {
562 assert_eq!(
563 ColorU8::from_rgba(10, 20, 30, 255).premultiply(),
564 PremultipliedColorU8::from_rgba_unchecked(10, 20, 30, 255)
565 );
566 }
567
568 #[test]
569 fn demultiply_u8_1() {
570 assert_eq!(
571 PremultipliedColorU8::from_rgba_unchecked(2, 3, 5, 40).demultiply(),
572 ColorU8::from_rgba(13, 19, 32, 40)
573 );
574 }
575
576 #[test]
577 fn demultiply_u8_2() {
578 assert_eq!(
579 PremultipliedColorU8::from_rgba_unchecked(10, 20, 30, 255).demultiply(),
580 ColorU8::from_rgba(10, 20, 30, 255)
581 );
582 }
583
584 #[test]
585 fn demultiply_u8_3() {
586 assert_eq!(
587 PremultipliedColorU8::from_rgba_unchecked(153, 99, 54, 180).demultiply(),
588 ColorU8::from_rgba(217, 140, 77, 180)
589 );
590 }
591
592 #[test]
593 fn bytemuck_casts_rgba() {
594 let slice = &[
595 PremultipliedColorU8::from_rgba_unchecked(0, 1, 2, 3),
596 PremultipliedColorU8::from_rgba_unchecked(10, 11, 12, 13),
597 ];
598 let bytes: &[u8] = bytemuck::cast_slice(slice);
599 assert_eq!(bytes, &[0, 1, 2, 3, 10, 11, 12, 13]);
600 }
601}