Skip to main content

qrcode_render/
colors.rs

1//! Common color types and conversions for rendering.
2//!
3//! This module provides convenience types and conversion functions that work
4//! across multiple render backends.
5//!
6//! # Supported formats
7//!
8//! | Format    | Type        | Example                    |
9//! |-----------|-------------|----------------------------|
10//! | RGB       | `(u8,u8,u8)`| `(255, 0, 128)`           |
11//! | RGBA      | `(u8,u8,u8,u8)` | `(255, 0, 128, 200)` |
12//! | Hex       | `&str`      | `"#ff0080"`               |
13//! | Named CSS | `&str`      | `"red"`, `"transparent"`  |
14//!
15//! # Example
16//!
17//! ```
18//! use qrcode_render::colors::hex_to_rgb;
19//!
20//! // Works under any feature combination (no renderer backend required).
21//! assert_eq!(hex_to_rgb("#1a1a2e"), Some((0x1a, 0x1a, 0x2e)));
22//! assert_eq!(hex_to_rgb("invalid"), None);
23//! ```
24
25#[cfg(not(feature = "std"))]
26#[allow(unused_imports)]
27use alloc::{
28    borrow::ToOwned,
29    format,
30    string::{String, ToString},
31    vec,
32    vec::Vec,
33};
34
35/// Parses a `#rrggbb` or `#rrggbbaa` hex color string into RGB or RGBA bytes.
36///
37/// Returns `None` if the format is invalid.
38///
39/// # Example
40///
41/// ```
42/// use qrcode_render::colors::hex_to_rgb;
43/// assert_eq!(hex_to_rgb("#ff0080"), Some((255, 0, 128)));
44/// assert_eq!(hex_to_rgb("#000"), Some((0, 0, 0)));
45/// assert_eq!(hex_to_rgb("invalid"), None);
46/// ```
47pub fn hex_to_rgb(hex: &str) -> Option<(u8, u8, u8)> {
48    let hex = hex.strip_prefix('#').unwrap_or(hex);
49    match hex.len() {
50        3 => {
51            let r = u8::from_str_radix(&hex[0..1], 16).ok()? * 17;
52            let g = u8::from_str_radix(&hex[1..2], 16).ok()? * 17;
53            let b = u8::from_str_radix(&hex[2..3], 16).ok()? * 17;
54            Some((r, g, b))
55        }
56        6 => {
57            let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
58            let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
59            let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
60            Some((r, g, b))
61        }
62        _ => None,
63    }
64}
65
66/// Parses a `#rrggbb` or `#rrggbbaa` hex color string into RGBA bytes.
67///
68/// For `#rrggbb` format, alpha defaults to 255 (fully opaque).
69///
70/// # Example
71///
72/// ```
73/// use qrcode_render::colors::hex_to_rgba;
74/// assert_eq!(hex_to_rgba("#ff0080"), Some((255, 0, 128, 255)));
75/// assert_eq!(hex_to_rgba("#ff008080"), Some((255, 0, 128, 128)));
76/// assert_eq!(hex_to_rgba("invalid"), None);
77/// ```
78pub fn hex_to_rgba(hex: &str) -> Option<(u8, u8, u8, u8)> {
79    let hex = hex.strip_prefix('#').unwrap_or(hex);
80    match hex.len() {
81        3 | 6 => hex_to_rgb(hex).map(|(r, g, b)| (r, g, b, 255)),
82        4 => {
83            let r = u8::from_str_radix(&hex[0..1], 16).ok()? * 17;
84            let g = u8::from_str_radix(&hex[1..2], 16).ok()? * 17;
85            let b = u8::from_str_radix(&hex[2..3], 16).ok()? * 17;
86            let a = u8::from_str_radix(&hex[3..4], 16).ok()? * 17;
87            Some((r, g, b, a))
88        }
89        8 => {
90            let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
91            let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
92            let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
93            let a = u8::from_str_radix(&hex[6..8], 16).ok()?;
94            Some((r, g, b, a))
95        }
96        _ => None,
97    }
98}
99
100/// Converts RGB bytes to a CSS hex string (e.g., `"#ff0080"`).
101///
102/// # Example
103///
104/// ```
105/// use qrcode_render::colors::rgb_to_hex;
106/// assert_eq!(rgb_to_hex(255, 0, 128), "#ff0080");
107/// ```
108pub fn rgb_to_hex(r: u8, g: u8, b: u8) -> String {
109    format!("#{r:02x}{g:02x}{b:02x}")
110}
111
112/// Converts RGBA bytes to a CSS hex string (e.g., `"#ff0080c0"`).
113///
114/// # Example
115///
116/// ```
117/// use qrcode_render::colors::rgba_to_hex;
118/// assert_eq!(rgba_to_hex(255, 0, 128, 192), "#ff0080c0");
119/// ```
120pub fn rgba_to_hex(r: u8, g: u8, b: u8, a: u8) -> String {
121    format!("#{r:02x}{g:02x}{b:02x}{a:02x}")
122}
123
124/// Converts RGB bytes to a `rgb()` CSS function string.
125///
126/// # Example
127///
128/// ```
129/// use qrcode_render::colors::rgb_to_css;
130/// assert_eq!(rgb_to_css(255, 0, 128), "rgb(255,0,128)");
131/// ```
132pub fn rgb_to_css(r: u8, g: u8, b: u8) -> String {
133    format!("rgb({r},{g},{b})")
134}
135
136/// Converts RGBA bytes to a `rgba()` CSS function string.
137///
138/// # Example
139///
140/// ```
141/// use qrcode_render::colors::rgba_to_css;
142/// assert_eq!(rgba_to_css(255, 0, 128, 128), "rgba(255,0,128,128)");
143/// ```
144pub fn rgba_to_css(r: u8, g: u8, b: u8, a: u8) -> String {
145    format!("rgba({r},{g},{b},{a})")
146}
147
148/// A unified sRGBA color type for use across all render backends.
149///
150/// `Srgba` provides a single type that can create colors for any renderer:
151/// SVG (CSS hex), ANSI (escape codes), EPS/PDF (0.0–1.0 arrays), image (`Rgba<u8>`).
152///
153/// # Example
154///
155/// ```
156/// use qrcode_render::colors::Srgba;
157///
158/// let c = Srgba::from_hex("#ff0080").unwrap();
159/// assert_eq!(c.to_hex(), "#ff0080");
160/// assert_eq!(c.to_css(), "rgba(255,0,128,255)");
161/// let arr = c.to_array();
162/// assert!((arr[0] - 1.0).abs() < 0.001);
163/// ```
164#[derive(Copy, Clone, Debug, PartialEq, Eq)]
165pub struct Srgba {
166    /// Red component.
167    pub r: u8,
168    /// Green component.
169    pub g: u8,
170    /// Blue component.
171    pub b: u8,
172    /// Alpha component (0 = transparent, 255 = opaque).
173    pub a: u8,
174}
175
176/// A color space that can convert to and from sRGB.
177///
178/// Render backends can accept a concrete color space and lower it to their
179/// native paint operators. RGB-oriented outputs keep using [`RgbColor`], while
180/// print-oriented outputs such as EPS/PDF can preserve [`CmykColor`].
181pub trait ColorSpace: Copy + Sized {
182    /// Component storage used by this color space.
183    type Component: Copy;
184
185    /// Converts this color to sRGB.
186    fn to_rgb(self) -> RgbColor;
187
188    /// Converts an sRGB color into this color space.
189    fn from_rgb(rgb: RgbColor) -> Self;
190}
191
192/// An opaque sRGB color.
193#[derive(Copy, Clone, Debug, PartialEq, Eq)]
194pub struct RgbColor {
195    /// Red component.
196    pub r: u8,
197    /// Green component.
198    pub g: u8,
199    /// Blue component.
200    pub b: u8,
201}
202
203impl RgbColor {
204    /// Creates an opaque sRGB color.
205    pub const fn new(r: u8, g: u8, b: u8) -> Self {
206        Self { r, g, b }
207    }
208
209    /// Converts this color to normalized RGB components.
210    pub fn to_array(self) -> [f64; 3] {
211        [self.r as f64 / 255.0, self.g as f64 / 255.0, self.b as f64 / 255.0]
212    }
213
214    /// Converts this color to an opaque [`Srgba`].
215    pub const fn to_srgba(self) -> Srgba {
216        Srgba::rgb(self.r, self.g, self.b)
217    }
218}
219
220impl ColorSpace for RgbColor {
221    type Component = u8;
222
223    fn to_rgb(self) -> RgbColor {
224        self
225    }
226
227    fn from_rgb(rgb: RgbColor) -> Self {
228        rgb
229    }
230}
231
232impl From<(u8, u8, u8)> for RgbColor {
233    fn from((r, g, b): (u8, u8, u8)) -> Self {
234        Self::new(r, g, b)
235    }
236}
237
238impl From<Srgba> for RgbColor {
239    fn from(color: Srgba) -> Self {
240        Self::new(color.r, color.g, color.b)
241    }
242}
243
244impl From<RgbColor> for Srgba {
245    fn from(color: RgbColor) -> Self {
246        color.to_srgba()
247    }
248}
249
250/// A CMYK color with normalized `0.0..=1.0` components.
251#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
252pub struct CmykColor {
253    /// Cyan component.
254    pub c: f64,
255    /// Magenta component.
256    pub m: f64,
257    /// Yellow component.
258    pub y: f64,
259    /// Key/black component.
260    pub k: f64,
261}
262
263impl CmykColor {
264    /// Creates a CMYK color, clamping all components to `0.0..=1.0`.
265    pub fn new(c: f64, m: f64, y: f64, k: f64) -> Self {
266        Self { c: clamp_unit(c), m: clamp_unit(m), y: clamp_unit(y), k: clamp_unit(k) }
267    }
268
269    /// Converts this color to normalized CMYK components.
270    pub fn to_array(self) -> [f64; 4] {
271        [self.c, self.m, self.y, self.k]
272    }
273}
274
275impl ColorSpace for CmykColor {
276    type Component = f64;
277
278    fn to_rgb(self) -> RgbColor {
279        let r = (1.0 - self.c) * (1.0 - self.k);
280        let g = (1.0 - self.m) * (1.0 - self.k);
281        let b = (1.0 - self.y) * (1.0 - self.k);
282        RgbColor::new(unit_to_u8(r), unit_to_u8(g), unit_to_u8(b))
283    }
284
285    fn from_rgb(rgb: RgbColor) -> Self {
286        let r = rgb.r as f64 / 255.0;
287        let g = rgb.g as f64 / 255.0;
288        let b = rgb.b as f64 / 255.0;
289        let k = 1.0 - r.max(g).max(b);
290        if k >= 1.0 {
291            return Self::new(0.0, 0.0, 0.0, 1.0);
292        }
293        let denom = 1.0 - k;
294        Self::new((1.0 - r - k) / denom, (1.0 - g - k) / denom, (1.0 - b - k) / denom, k)
295    }
296}
297
298impl From<RgbColor> for CmykColor {
299    fn from(color: RgbColor) -> Self {
300        Self::from_rgb(color)
301    }
302}
303
304impl From<CmykColor> for RgbColor {
305    fn from(color: CmykColor) -> Self {
306        color.to_rgb()
307    }
308}
309
310/// A CIE L*a*b* color using a D65 white point approximation.
311#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
312pub struct LabColor {
313    /// Lightness.
314    pub l: f64,
315    /// Green-red axis.
316    pub a: f64,
317    /// Blue-yellow axis.
318    pub b: f64,
319}
320
321impl LabColor {
322    /// Creates a Lab color.
323    pub const fn new(l: f64, a: f64, b: f64) -> Self {
324        Self { l, a, b }
325    }
326}
327
328impl ColorSpace for LabColor {
329    type Component = f64;
330
331    fn to_rgb(self) -> RgbColor {
332        let fy = (self.l + 16.0) / 116.0;
333        let fx = fy + self.a / 500.0;
334        let fz = fy - self.b / 200.0;
335        let x = lab_inv(fx) * 0.95047;
336        let y = lab_inv(fy);
337        let z = lab_inv(fz) * 1.08883;
338        let r = 3.2404542 * x - 1.5371385 * y - 0.4985314 * z;
339        let g = -0.9692660 * x + 1.8760108 * y + 0.0415560 * z;
340        let b = 0.0556434 * x - 0.2040259 * y + 1.0572252 * z;
341        RgbColor::new(unit_to_u8(r), unit_to_u8(g), unit_to_u8(b))
342    }
343
344    fn from_rgb(rgb: RgbColor) -> Self {
345        let r = rgb.r as f64 / 255.0;
346        let g = rgb.g as f64 / 255.0;
347        let b = rgb.b as f64 / 255.0;
348        let x = (0.4124564 * r + 0.3575761 * g + 0.1804375 * b) / 0.95047;
349        let y = 0.2126729 * r + 0.7151522 * g + 0.0721750 * b;
350        let z = (0.0193339 * r + 0.1191920 * g + 0.9503041 * b) / 1.08883;
351        let fx = lab_f(x);
352        let fy = lab_f(y);
353        let fz = lab_f(z);
354        Self::new(116.0 * fy - 16.0, 500.0 * (fx - fy), 200.0 * (fy - fz))
355    }
356}
357
358impl From<RgbColor> for LabColor {
359    fn from(color: RgbColor) -> Self {
360        Self::from_rgb(color)
361    }
362}
363
364impl From<LabColor> for RgbColor {
365    fn from(color: LabColor) -> Self {
366        color.to_rgb()
367    }
368}
369
370impl Srgba {
371    /// Creates a new sRGBA color.
372    pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
373        Self { r, g, b, a }
374    }
375
376    /// Creates a fully opaque sRGBA color.
377    pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
378        Self { r, g, b, a: 255 }
379    }
380
381    /// Parses a hex color string (`#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`).
382    pub fn from_hex(hex: &str) -> Option<Self> {
383        let (r, g, b, a) = hex_to_rgba(hex)?;
384        Some(Self { r, g, b, a })
385    }
386
387    /// Converts to a CSS hex string (e.g., `"#ff0080"` or `"#ff0080c0"` if alpha < 255).
388    pub fn to_hex(self) -> String {
389        if self.a == 255 { rgb_to_hex(self.r, self.g, self.b) } else { rgba_to_hex(self.r, self.g, self.b, self.a) }
390    }
391
392    /// Converts to a CSS `rgba()` function string.
393    pub fn to_css(self) -> String {
394        rgba_to_css(self.r, self.g, self.b, self.a)
395    }
396
397    /// Converts to an ANSI foreground escape sequence (`\x1b[38;2;R;G;Bm`).
398    pub fn to_ansi_fg(self) -> String {
399        format!("\x1b[38;2;{};{};{}m", self.r, self.g, self.b)
400    }
401
402    /// Converts to an ANSI background escape sequence (`\x1b[48;2;R;G;Bm`).
403    pub fn to_ansi_bg(self) -> String {
404        format!("\x1b[48;2;{};{};{}m", self.r, self.g, self.b)
405    }
406
407    /// Converts to a `[f64; 3]` array with values in 0.0–1.0, suitable for EPS/PDF renderers.
408    pub fn to_array(self) -> [f64; 3] {
409        [self.r as f64 / 255.0, self.g as f64 / 255.0, self.b as f64 / 255.0]
410    }
411
412    /// Linearly interpolates between `self` and `other`.
413    ///
414    /// `t = 0.0` returns `self`, `t = 1.0` returns `other`.
415    pub fn lerp(self, other: Self, t: f32) -> Self {
416        let t = t.clamp(0.0, 1.0);
417        let inv = 1.0 - t;
418        Self {
419            r: (self.r as f32 * inv + other.r as f32 * t) as u8,
420            g: (self.g as f32 * inv + other.g as f32 * t) as u8,
421            b: (self.b as f32 * inv + other.b as f32 * t) as u8,
422            a: (self.a as f32 * inv + other.a as f32 * t) as u8,
423        }
424    }
425}
426
427fn clamp_unit(value: f64) -> f64 {
428    value.clamp(0.0, 1.0)
429}
430
431fn unit_to_u8(value: f64) -> u8 {
432    (clamp_unit(value) * 255.0 + 0.5) as u8
433}
434
435fn lab_f(value: f64) -> f64 {
436    if value > 0.008856 { cube_root(value) } else { 7.787 * value + 16.0 / 116.0 }
437}
438
439fn lab_inv(value: f64) -> f64 {
440    let cube = value * value * value;
441    if cube > 0.008856 { cube } else { (value - 16.0 / 116.0) / 7.787 }
442}
443
444fn cube_root(value: f64) -> f64 {
445    if value <= 0.0 {
446        return 0.0;
447    }
448    let mut x = value.max(1.0);
449    for _ in 0..12 {
450        x = (2.0 * x + value / (x * x)) / 3.0;
451    }
452    x
453}
454
455impl From<(u8, u8, u8)> for Srgba {
456    fn from((r, g, b): (u8, u8, u8)) -> Self {
457        Self::rgb(r, g, b)
458    }
459}
460
461impl From<(u8, u8, u8, u8)> for Srgba {
462    fn from((r, g, b, a): (u8, u8, u8, u8)) -> Self {
463        Self::new(r, g, b, a)
464    }
465}
466
467impl From<Srgba> for (u8, u8, u8, u8) {
468    fn from(c: Srgba) -> Self {
469        (c.r, c.g, c.b, c.a)
470    }
471}
472
473#[cfg(feature = "image")]
474impl From<Srgba> for image::Rgba<u8> {
475    fn from(c: Srgba) -> Self {
476        image::Rgba([c.r, c.g, c.b, c.a])
477    }
478}
479
480#[cfg(feature = "image")]
481impl From<image::Rgba<u8>> for Srgba {
482    fn from(p: image::Rgba<u8>) -> Self {
483        Self::new(p.0[0], p.0[1], p.0[2], p.0[3])
484    }
485}
486
487#[cfg(test)]
488mod tests {
489    use super::*;
490
491    #[test]
492    fn test_hex_to_rgb_6_digit() {
493        assert_eq!(hex_to_rgb("#ff0080"), Some((255, 0, 128)));
494        assert_eq!(hex_to_rgb("#000000"), Some((0, 0, 0)));
495        assert_eq!(hex_to_rgb("#ffffff"), Some((255, 255, 255)));
496    }
497
498    #[test]
499    fn test_hex_to_rgb_3_digit() {
500        assert_eq!(hex_to_rgb("#f08"), Some((255, 0, 136)));
501        assert_eq!(hex_to_rgb("#000"), Some((0, 0, 0)));
502        assert_eq!(hex_to_rgb("#fff"), Some((255, 255, 255)));
503    }
504
505    #[test]
506    fn test_hex_to_rgb_no_hash() {
507        assert_eq!(hex_to_rgb("ff0080"), Some((255, 0, 128)));
508    }
509
510    #[test]
511    fn test_hex_to_rgb_invalid() {
512        assert_eq!(hex_to_rgb("invalid"), None);
513        assert_eq!(hex_to_rgb("#gg0000"), None);
514        assert_eq!(hex_to_rgb("#12345"), None);
515    }
516
517    #[test]
518    fn test_hex_to_rgba_6_digit() {
519        assert_eq!(hex_to_rgba("#ff0080"), Some((255, 0, 128, 255)));
520    }
521
522    #[test]
523    fn test_hex_to_rgba_8_digit() {
524        assert_eq!(hex_to_rgba("#ff008080"), Some((255, 0, 128, 128)));
525    }
526
527    #[test]
528    fn test_hex_to_rgba_4_digit() {
529        assert_eq!(hex_to_rgba("#f08c"), Some((255, 0, 136, 204)));
530    }
531
532    #[test]
533    fn test_rgb_to_hex() {
534        assert_eq!(rgb_to_hex(255, 0, 128), "#ff0080");
535        assert_eq!(rgb_to_hex(0, 0, 0), "#000000");
536    }
537
538    #[test]
539    fn test_rgba_to_hex() {
540        assert_eq!(rgba_to_hex(255, 0, 128, 192), "#ff0080c0");
541    }
542
543    #[test]
544    fn test_rgb_to_css() {
545        assert_eq!(rgb_to_css(255, 0, 128), "rgb(255,0,128)");
546    }
547
548    #[test]
549    fn test_rgba_to_css() {
550        assert_eq!(rgba_to_css(255, 0, 128, 128), "rgba(255,0,128,128)");
551    }
552
553    #[test]
554    fn test_roundtrip() {
555        let (r, g, b) = (42, 128, 255);
556        let hex = rgb_to_hex(r, g, b);
557        assert_eq!(hex_to_rgb(&hex), Some((r, g, b)));
558    }
559
560    #[test]
561    fn test_srgba_from_hex() {
562        let c = Srgba::from_hex("#ff0080").unwrap();
563        assert_eq!(c, Srgba::new(255, 0, 128, 255));
564    }
565
566    #[test]
567    fn test_srgba_from_hex_with_alpha() {
568        let c = Srgba::from_hex("#ff0080c0").unwrap();
569        assert_eq!(c, Srgba::new(255, 0, 128, 192));
570    }
571
572    #[test]
573    fn test_srgba_to_hex() {
574        assert_eq!(Srgba::rgb(255, 0, 128).to_hex(), "#ff0080");
575        assert_eq!(Srgba::new(255, 0, 128, 192).to_hex(), "#ff0080c0");
576    }
577
578    #[test]
579    fn test_srgba_to_css() {
580        assert_eq!(Srgba::rgb(255, 0, 128).to_css(), "rgba(255,0,128,255)");
581    }
582
583    #[test]
584    fn test_srgba_to_ansi() {
585        let c = Srgba::rgb(0, 51, 102);
586        assert_eq!(c.to_ansi_fg(), "\x1b[38;2;0;51;102m");
587        assert_eq!(c.to_ansi_bg(), "\x1b[48;2;0;51;102m");
588    }
589
590    #[test]
591    fn test_srgba_to_array() {
592        let c = Srgba::rgb(255, 0, 128);
593        let arr = c.to_array();
594        assert!((arr[0] - 1.0).abs() < 0.001);
595        assert!((arr[1] - 0.0).abs() < 0.001);
596        assert!((arr[2] - 0.502).abs() < 0.01);
597    }
598
599    #[test]
600    fn test_srgba_lerp() {
601        let a = Srgba::rgb(0, 0, 0);
602        let b = Srgba::rgb(255, 255, 255);
603        assert_eq!(a.lerp(b, 0.0), a);
604        assert_eq!(a.lerp(b, 1.0), b);
605        let mid = a.lerp(b, 0.5);
606        assert_eq!(mid.r, 127);
607    }
608
609    #[test]
610    fn test_srgba_from_tuple() {
611        let c: Srgba = (255, 0, 128).into();
612        assert_eq!(c, Srgba::rgb(255, 0, 128));
613    }
614
615    #[test]
616    fn rgb_color_space_round_trips() {
617        let rgb = RgbColor::new(51, 102, 153);
618
619        assert_eq!(RgbColor::from_rgb(rgb), rgb);
620        assert_eq!(rgb.to_rgb(), rgb);
621        assert_eq!(rgb.to_srgba(), Srgba::rgb(51, 102, 153));
622        assert_eq!(rgb.to_array(), [0.2, 0.4, 0.6]);
623    }
624
625    #[test]
626    fn cmyk_color_space_converts_to_rgb() {
627        assert_eq!(CmykColor::new(0.0, 0.0, 0.0, 1.0).to_rgb(), RgbColor::new(0, 0, 0));
628        assert_eq!(CmykColor::new(0.0, 1.0, 1.0, 0.0).to_rgb(), RgbColor::new(255, 0, 0));
629
630        let cmyk = CmykColor::from_rgb(RgbColor::new(51, 102, 153));
631        assert_eq!(cmyk.to_rgb(), RgbColor::new(51, 102, 153));
632    }
633
634    #[test]
635    fn lab_color_space_handles_neutral_extremes() {
636        let white = LabColor::from_rgb(RgbColor::new(255, 255, 255));
637        assert!((white.l - 100.0).abs() < 0.01);
638        assert!(white.a.abs() < 0.01);
639        assert!(white.b.abs() < 0.01);
640
641        let black = LabColor::from_rgb(RgbColor::new(0, 0, 0));
642        assert!(black.l.abs() < 0.01);
643        assert_eq!(black.to_rgb(), RgbColor::new(0, 0, 0));
644    }
645}