Skip to main content

tiny_skia/
color.rs

1// Copyright 2006 The Android Open Source Project
2// Copyright 2020 Yevhenii Reizner
3//
4// Use of this source code is governed by a BSD-style license that can be
5// found in the LICENSE file.
6
7use 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
13/// 8-bit type for an alpha value. 255 is 100% opaque, zero is 100% transparent.
14pub type AlphaU8 = u8;
15
16/// Represents fully transparent AlphaU8 value.
17pub const ALPHA_U8_TRANSPARENT: AlphaU8 = 0x00;
18
19/// Represents fully opaque AlphaU8 value.
20pub const ALPHA_U8_OPAQUE: AlphaU8 = 0xFF;
21
22/// Represents fully transparent Alpha value.
23pub const ALPHA_TRANSPARENT: NormalizedF32 = NormalizedF32::ZERO;
24
25/// Represents fully opaque Alpha value.
26pub const ALPHA_OPAQUE: NormalizedF32 = NormalizedF32::ONE;
27
28/// A 32-bit RGBA color value.
29///
30/// Byteorder: RGBA (relevant for bytemuck casts)
31#[repr(transparent)]
32#[derive(Copy, Clone, PartialEq)]
33pub struct ColorU8([u8; 4]);
34
35impl ColorU8 {
36    /// Creates a new color.
37    pub const fn from_rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
38        ColorU8([r, g, b, a])
39    }
40
41    /// Returns color's red component.
42    pub const fn red(self) -> u8 {
43        self.0[0]
44    }
45
46    /// Returns color's green component.
47    pub const fn green(self) -> u8 {
48        self.0[1]
49    }
50
51    /// Returns color's blue component.
52    pub const fn blue(self) -> u8 {
53        self.0[2]
54    }
55
56    /// Returns color's alpha component.
57    pub const fn alpha(self) -> u8 {
58        self.0[3]
59    }
60
61    /// Check that color is opaque.
62    ///
63    /// Alpha == 255
64    pub fn is_opaque(&self) -> bool {
65        self.alpha() == ALPHA_U8_OPAQUE
66    }
67
68    /// Converts into a premultiplied color.
69    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/// A 32-bit premultiplied RGBA color value.
96///
97/// Byteorder: RGBA (relevant for bytemuck casts)
98#[repr(transparent)]
99#[derive(Copy, Clone, PartialEq)]
100pub struct PremultipliedColorU8([u8; 4]);
101
102// Perfectly safe, since [u8; 4] is already Pod.
103unsafe impl bytemuck::Zeroable for PremultipliedColorU8 {}
104unsafe impl bytemuck::Pod for PremultipliedColorU8 {}
105
106impl PremultipliedColorU8 {
107    /// A transparent color.
108    pub const TRANSPARENT: Self = PremultipliedColorU8::from_rgba_unchecked(0, 0, 0, 0);
109
110    /// Creates a new premultiplied color.
111    ///
112    /// RGB components must be <= alpha.
113    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    /// Creates a new color.
122    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    /// Returns color's red component.
127    ///
128    /// The value is <= alpha.
129    pub const fn red(self) -> u8 {
130        self.0[0]
131    }
132
133    /// Returns color's green component.
134    ///
135    /// The value is <= alpha.
136    pub const fn green(self) -> u8 {
137        self.0[1]
138    }
139
140    /// Returns color's blue component.
141    ///
142    /// The value is <= alpha.
143    pub const fn blue(self) -> u8 {
144        self.0[2]
145    }
146
147    /// Returns color's alpha component.
148    pub const fn alpha(self) -> u8 {
149        self.0[3]
150    }
151
152    /// Check that color is opaque.
153    ///
154    /// Alpha == 255
155    pub fn is_opaque(&self) -> bool {
156        self.alpha() == ALPHA_U8_OPAQUE
157    }
158
159    /// Returns a demultiplied color.
160    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/// An RGBA color value, holding four floating point components.
188///
189/// # Guarantees
190///
191/// - All values are in 0..=1 range.
192#[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    /// A transparent color.
205    pub const TRANSPARENT: Color = Color {
206        r: NV_ZERO,
207        g: NV_ZERO,
208        b: NV_ZERO,
209        a: NV_ZERO,
210    };
211    /// A black color.
212    pub const BLACK: Color = Color {
213        r: NV_ZERO,
214        g: NV_ZERO,
215        b: NV_ZERO,
216        a: NV_ONE,
217    };
218    /// A white color.
219    pub const WHITE: Color = Color {
220        r: NV_ONE,
221        g: NV_ONE,
222        b: NV_ONE,
223        a: NV_ONE,
224    };
225
226    /// Creates a new color from 4 components.
227    ///
228    /// # Safety
229    ///
230    /// All values must be in 0..=1 range.
231    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    /// Creates a new color from 4 components.
241    ///
242    /// All values must be in 0..=1 range.
243    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    /// Creates a new color from 4 components.
253    ///
254    /// u8 will be divided by 255 to get the float component.
255    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    /// Returns color's red component.
265    ///
266    /// The value is guarantee to be in a 0..=1 range.
267    pub fn red(&self) -> f32 {
268        self.r.get()
269    }
270
271    /// Returns color's green component.
272    ///
273    /// The value is guarantee to be in a 0..=1 range.
274    pub fn green(&self) -> f32 {
275        self.g.get()
276    }
277
278    /// Returns color's blue component.
279    ///
280    /// The value is guarantee to be in a 0..=1 range.
281    pub fn blue(&self) -> f32 {
282        self.b.get()
283    }
284
285    /// Returns color's alpha component.
286    ///
287    /// The value is guarantee to be in a 0..=1 range.
288    pub fn alpha(&self) -> f32 {
289        self.a.get()
290    }
291
292    /// Sets the red component value.
293    ///
294    /// The new value will be clipped to the 0..=1 range.
295    pub fn set_red(&mut self, c: f32) {
296        self.r = NormalizedF32::new_clamped(c);
297    }
298
299    /// Sets the green component value.
300    ///
301    /// The new value will be clipped to the 0..=1 range.
302    pub fn set_green(&mut self, c: f32) {
303        self.g = NormalizedF32::new_clamped(c);
304    }
305
306    /// Sets the blue component value.
307    ///
308    /// The new value will be clipped to the 0..=1 range.
309    pub fn set_blue(&mut self, c: f32) {
310        self.b = NormalizedF32::new_clamped(c);
311    }
312
313    /// Sets the alpha component value.
314    ///
315    /// The new value will be clipped to the 0..=1 range.
316    pub fn set_alpha(&mut self, c: f32) {
317        self.a = NormalizedF32::new_clamped(c);
318    }
319
320    /// Shifts color's opacity.
321    ///
322    /// Essentially, multiplies color's alpha by opacity.
323    ///
324    /// `opacity` will be clamped to the 0..=1 range first.
325    /// The final alpha will also be clamped.
326    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    /// Check that color is opaque.
331    ///
332    /// Alpha == 1.0
333    pub fn is_opaque(&self) -> bool {
334        self.a == ALPHA_OPAQUE
335    }
336
337    /// Converts into a premultiplied color.
338    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    /// Converts into `ColorU8`.
357    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/// A premultiplied RGBA color value, holding four floating point components.
364///
365/// # Guarantees
366///
367/// - All values are in 0..=1 range.
368/// - RGB components are <= A.
369#[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    /// Returns color's red component.
379    ///
380    /// - The value is guarantee to be in a 0..=1 range.
381    /// - The value is <= alpha.
382    pub fn red(&self) -> f32 {
383        self.r.get()
384    }
385
386    /// Returns color's green component.
387    ///
388    /// - The value is guarantee to be in a 0..=1 range.
389    /// - The value is <= alpha.
390    pub fn green(&self) -> f32 {
391        self.g.get()
392    }
393
394    /// Returns color's blue component.
395    ///
396    /// - The value is guarantee to be in a 0..=1 range.
397    /// - The value is <= alpha.
398    pub fn blue(&self) -> f32 {
399        self.b.get()
400    }
401
402    /// Returns color's alpha component.
403    ///
404    /// - The value is guarantee to be in a 0..=1 range.
405    pub fn alpha(&self) -> f32 {
406        self.a.get()
407    }
408
409    /// Returns a demultiplied color.
410    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    /// Converts into `PremultipliedColorU8`.
425    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
431/// Return a*b/255, rounding any fractional bits.
432pub 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/// The colorspace used to interpret pixel values.
452///
453/// This is a very limited subset of SkColorSpace.
454#[derive(Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Debug)]
455pub enum ColorSpace {
456    /// Linear RGB, the default.  Assumes #7f7f7f is half as bright as #ffffff.
457    #[default]
458    Linear,
459
460    /// Apply a gamma factor of 2.
461    ///
462    /// This is the fastest gamma correction to apply, and produces reasonable
463    /// quality blending.
464    Gamma2,
465
466    /// Apply a gamma factor of 2.2.
467    ///
468    /// This is also known as "simple sRGB"; it's very close to the actual gamma
469    /// function specified by the sRGB color space, but much faster to compute.
470    SimpleSRGB,
471
472    /// Apply the full sRGB gamma function.
473    ///
474    /// This does not convert the RGB colors to CIE XYZ for blending; it only
475    /// applies the (full) gamma function.
476    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}