Skip to main content

material_colors/
color.rs

1#[cfg(all(not(feature = "std"), feature = "libm"))]
2#[allow(unused_imports)]
3use crate::utils::no_std::FloatExt;
4use crate::{utils::math::matrix_multiply, Error};
5#[cfg(not(feature = "std"))]
6use alloc::{
7    format,
8    string::{String, ToString},
9};
10use core::{fmt, str::FromStr};
11#[cfg(feature = "serde")]
12use serde::Serialize;
13#[cfg(feature = "std")]
14use std::{
15    format,
16    string::{String, ToString},
17};
18
19pub const SRGB_TO_XYZ: [[f64; 3]; 3] = [
20    [0.41233895, 0.35762064, 0.18051042],
21    [0.2126, 0.7152, 0.0722],
22    [0.01932141, 0.11916382, 0.95034478],
23];
24pub const XYZ_TO_SRGB: [[f64; 3]; 3] = [
25    [
26        3.2413774792388685,
27        -1.5376652402851851,
28        -0.49885366846268053,
29    ],
30    [-0.9691452513005321, 1.8758853451067872, 0.04156585616912061],
31    [
32        0.05562093689691305,
33        -0.20395524564742123,
34        1.0571799111220335,
35    ],
36];
37pub const WHITE_POINT_D65: [f64; 3] = [95.047, 100.0, 108.883];
38
39#[derive(Debug, Default, Clone, Copy)]
40#[cfg_attr(feature = "serde", derive(Serialize))]
41pub struct Rgb {
42    pub red: u8,
43    pub green: u8,
44    pub blue: u8,
45}
46
47/// ARGB representation of color. Can be created using [`Argb::new`], [`Argb::from_u32`] or
48/// [`Argb::from_str`].
49///
50/// ## Examples:
51/// ```rust
52/// use std::str::FromStr;
53/// use material_colors::color::Argb;
54///
55/// // from_str can accept any valid HEX color
56/// let color = Argb::from_str("abc").unwrap();
57/// let color = Argb::from_str("aabbcc").unwrap();
58/// let color = Argb::from_str("aabbccdd").unwrap();
59/// let color = Argb::from_str("#abc").unwrap();
60/// let color = Argb::from_str("#aabbcc").unwrap();
61/// let color = Argb::from_str("#aabbccdd").unwrap();
62/// ```
63#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
64#[cfg_attr(feature = "serde", derive(Serialize))]
65pub struct Argb {
66    pub alpha: u8,
67    pub red: u8,
68    pub green: u8,
69    pub blue: u8,
70}
71
72#[derive(Debug, Default, Clone, Copy)]
73#[cfg_attr(feature = "serde", derive(Serialize))]
74pub struct LinearRgb {
75    pub red: f64,
76    pub green: f64,
77    pub blue: f64,
78}
79
80#[derive(Debug, Default, Clone, Copy)]
81#[cfg_attr(feature = "serde", derive(Serialize))]
82pub struct Xyz {
83    pub x: f64,
84    pub y: f64,
85    pub z: f64,
86}
87
88#[derive(Debug, Default, Clone, Copy)]
89#[cfg_attr(feature = "serde", derive(Serialize))]
90pub struct Lab {
91    pub l: f64,
92    pub a: f64,
93    pub b: f64,
94}
95
96/** Converts a color from Rgb components to Argb format. */
97impl From<Rgb> for Argb {
98    fn from(Rgb { red, green, blue }: Rgb) -> Self {
99        Self {
100            alpha: 255,
101            red,
102            green,
103            blue,
104        }
105    }
106}
107
108/** Converts a color from linear Rgb components to Argb format. */
109impl From<LinearRgb> for Argb {
110    fn from(linear: LinearRgb) -> Self {
111        let r = delinearized(linear.red);
112        let g = delinearized(linear.green);
113        let b = delinearized(linear.blue);
114
115        Rgb::new(r, g, b).into()
116    }
117}
118
119/** Converts a color from Argb to Xyz. */
120impl From<Xyz> for Argb {
121    fn from(Xyz { x, y, z }: Xyz) -> Self {
122        let matrix = XYZ_TO_SRGB;
123
124        let (linear_r, linear_g, linear_b) = (
125            matrix[0][2].mul_add(z, matrix[0][0].mul_add(x, matrix[0][1] * y)),
126            matrix[1][2].mul_add(z, matrix[1][0].mul_add(x, matrix[1][1] * y)),
127            matrix[2][2].mul_add(z, matrix[2][0].mul_add(x, matrix[2][1] * y)),
128        );
129
130        let r = delinearized(linear_r);
131        let g = delinearized(linear_g);
132        let b = delinearized(linear_b);
133
134        Rgb::new(r, g, b).into()
135    }
136}
137
138/** Converts a color from Xyz to Argb. */
139impl From<Argb> for Xyz {
140    fn from(
141        Argb {
142            alpha: _,
143            red,
144            green,
145            blue,
146        }: Argb,
147    ) -> Self {
148        let r = linearized(red);
149        let g = linearized(green);
150        let b = linearized(blue);
151
152        let [x, y, z] = matrix_multiply([r, g, b], SRGB_TO_XYZ);
153
154        Self { x, y, z }
155    }
156}
157
158/** Converts a color represented in Lab color space into an Argb integer. */
159impl From<Lab> for Argb {
160    fn from(Lab { l, a, b }: Lab) -> Self {
161        let white_point = WHITE_POINT_D65;
162
163        let fy = (l + 16.0) / 116.0;
164        let fx = a / 500.0 + fy;
165        let fz = fy - b / 200.0;
166
167        let x_normalized = lab_invf(fx);
168        let y_normalized = lab_invf(fy);
169        let z_normalized = lab_invf(fz);
170
171        let x = x_normalized * white_point[0];
172        let y = y_normalized * white_point[1];
173        let z = z_normalized * white_point[2];
174
175        Xyz::new(x, y, z).into()
176    }
177}
178
179impl From<Argb> for Lab {
180    fn from(
181        Argb {
182            alpha: _,
183            red,
184            green,
185            blue,
186        }: Argb,
187    ) -> Self {
188        let linear_r = linearized(red);
189        let linear_g = linearized(green);
190        let linear_b = linearized(blue);
191
192        let matrix = SRGB_TO_XYZ;
193
194        let (x, y, z) = (
195            matrix[0][2].mul_add(
196                linear_b,
197                matrix[0][0].mul_add(linear_r, matrix[0][1] * linear_g),
198            ),
199            matrix[1][2].mul_add(
200                linear_b,
201                matrix[1][0].mul_add(linear_r, matrix[1][1] * linear_g),
202            ),
203            matrix[2][2].mul_add(
204                linear_b,
205                matrix[2][0].mul_add(linear_r, matrix[2][1] * linear_g),
206            ),
207        );
208
209        let white_point = WHITE_POINT_D65;
210
211        let x_normalized = x / white_point[0];
212        let y_normalized = y / white_point[1];
213        let z_normalized = z / white_point[2];
214
215        let fx = lab_f(x_normalized);
216        let fy = lab_f(y_normalized);
217        let fz = lab_f(z_normalized);
218
219        let l = 116.0f64.mul_add(fy, -16.0);
220        let a = 500.0 * (fx - fy);
221        let b = 200.0 * (fy - fz);
222
223        Self { l, a, b }
224    }
225}
226
227const HASH: char = '#';
228
229impl FromStr for Argb {
230    type Err = Error;
231
232    fn from_str(hex: &str) -> Result<Self, Self::Err> {
233        let hex = hex.strip_prefix(HASH).unwrap_or(hex);
234
235        if ![3, 6, 8].contains(&hex.len()) {
236            return Err(Error::ParseRGB);
237        }
238
239        let hex_str = if hex.len() == 3 {
240            format!(
241                "FF{a}{a}{b}{b}{c}{c}",
242                a = hex.get(..1).unwrap(),
243                b = hex.get(1..2).unwrap(),
244                c = hex.get(2..3).unwrap()
245            )
246        } else if hex.len() == 6 {
247            format!("FF{hex}")
248        } else {
249            hex.to_string()
250        };
251
252        let hex_digit = u32::from_str_radix(&hex_str, 16).map_err(|_| Error::ParseRGB)?;
253
254        Ok(Self::from_u32(hex_digit))
255    }
256}
257
258impl Xyz {
259    pub const fn new(x: f64, y: f64, z: f64) -> Self {
260        Self { x, y, z }
261    }
262}
263
264impl Lab {
265    pub const fn new(l: f64, a: f64, b: f64) -> Self {
266        Self { l, a, b }
267    }
268}
269
270impl Rgb {
271    pub const fn new(red: u8, green: u8, blue: u8) -> Self {
272        Self { red, green, blue }
273    }
274}
275
276impl Argb {
277    pub const fn new(alpha: u8, red: u8, green: u8, blue: u8) -> Self {
278        Self {
279            alpha,
280            red,
281            green,
282            blue,
283        }
284    }
285
286    pub const fn from_u32(value: u32) -> Self {
287        Self {
288            alpha: ((value >> 24) & 0xFF) as u8,
289            red: ((value >> 16) & 0xFF) as u8,
290            green: ((value >> 8) & 0xFF) as u8,
291            blue: ((value) & 0xFF) as u8,
292        }
293    }
294
295    /// Converts an L* value to an Argb representation.
296    ///
297    /// - `lstar`: L* in L*a*b*
298    ///
299    /// Returns ARGB representation of grayscale color with lightness matching L*
300    pub fn from_lstar(lstar: f64) -> Self {
301        let y = y_from_lstar(lstar);
302        let component = delinearized(y);
303
304        Rgb::new(component, component, component).into()
305    }
306
307    /// Computes the L* value of a color in Argb representation.
308    ///
309    /// - `argb`: ARGB representation of a color
310    ///
311    /// returns L*, from L*a*b*, coordinate of the color
312    pub fn as_lstar(&self) -> f64 {
313        116.0f64.mul_add(lab_f(Xyz::from(*self).y / 100.0), -16.0)
314    }
315
316    fn hex(number: u8) -> String {
317        let string = format!("{number:x}");
318
319        if string.len() == 1 {
320            String::from("0") + &string
321        } else {
322            string
323        }
324    }
325
326    pub fn to_hex(&self) -> String {
327        format!(
328            "{}{}{}",
329            Self::hex(self.red),
330            Self::hex(self.green),
331            Self::hex(self.blue)
332        )
333    }
334
335    pub fn to_hex_with_pound(&self) -> String {
336        format!(
337            "#{}{}{}",
338            Self::hex(self.red),
339            Self::hex(self.green),
340            Self::hex(self.blue)
341        )
342    }
343}
344
345impl fmt::Display for Argb {
346    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
347        write!(f, "{}", self.to_hex_with_pound())
348    }
349}
350
351/// Converts an L* value to a Y value.
352///
353/// L* in L*a*b* and Y in Xyz measure the same quantity, luminance.
354///
355/// L* measures perceptual luminance, a linear scale. Y in Xyz measures relative luminance, a
356/// logarithmic scale.
357///
358/// - `lstar`: L* in L*a*b*
359///
360/// Returns Y in Xyz
361pub fn y_from_lstar(lstar: f64) -> f64 {
362    100.0 * lab_invf((lstar + 16.0) / 116.0)
363}
364
365/// Converts a Y value to an L* value.
366///
367/// L* in L*a*b* and Y in Xyz measure the same quantity, luminance.
368///
369/// L* measures perceptual luminance, a linear scale. Y in Xyz measures relative luminance, a
370/// logarithmic scale.
371///
372/// - `y`: Y in Xyz
373///
374/// Returns L* in L*a*b*
375pub fn lstar_from_y(y: f64) -> f64 {
376    lab_f(y / 100.0).mul_add(116.0, -16.0)
377}
378
379/// Linearizes an Rgb component.
380///
381/// - `rgb_component`: 0 <= `rgb_component` <= 255, represents R/G/B channel
382///
383/// Returns 0.0 <= output <= 100.0, color channel converted to linear Rgb space
384pub fn linearized(rgb_component: u8) -> f64 {
385    let normalized = f64::from(rgb_component) / 255.0;
386
387    if normalized <= 0.040449936 {
388        normalized / 12.92 * 100.0
389    } else {
390        ((normalized + 0.055) / 1.055).powf(2.4) * 100.0
391    }
392}
393
394/// Delinearizes an Rgb component.
395///
396/// - `rgb_component`: 0.0 <= `rgb_component` <= 100.0, represents linear R/G/B channel
397///
398/// Returns 0 <= output <= 255, color channel converted to regular Rgb space
399pub fn delinearized(rgb_component: f64) -> u8 {
400    let normalized = rgb_component / 100.0;
401    let delinearized = if normalized <= 0.0031308 {
402        normalized * 12.92
403    } else {
404        1.055f64.mul_add(normalized.powf(1.0 / 2.4), -0.055)
405    };
406
407    ((delinearized * 255.0).round() as u8).clamp(0, 255)
408}
409
410fn lab_f(t: f64) -> f64 {
411    let e = 216.0 / 24389.0;
412    let kappa: f64 = 24389.0 / 27.0;
413
414    if t > e {
415        t.cbrt()
416    } else {
417        kappa.mul_add(t, 16.0) / 116.0
418    }
419}
420
421fn lab_invf(ft: f64) -> f64 {
422    let e = 216.0 / 24389.0;
423    let kappa = 24389.0 / 27.0;
424    let ft3 = ft * ft * ft;
425
426    if ft3 > e {
427        ft3
428    } else {
429        116.0f64.mul_add(ft, -16.0) / kappa
430    }
431}
432
433#[cfg(test)]
434mod tests {
435    use super::Lab;
436    use crate::color::{delinearized, linearized, lstar_from_y, y_from_lstar, Argb, Xyz};
437    #[cfg(not(feature = "std"))]
438    use alloc::vec::Vec;
439    use float_cmp::assert_approx_eq;
440    #[cfg(feature = "std")]
441    use std::vec::Vec;
442
443    fn _range(start: f64, stop: f64, case_count: i64) -> Vec<f64> {
444        let step_size = (stop - start) / (case_count as f64 - 1.0);
445
446        (0..case_count)
447            .map(|index| step_size.mul_add(index as f64, start))
448            .collect()
449    }
450
451    fn rgb_range() -> Vec<u8> {
452        _range(0.0, 255.0, 8)
453            .into_iter()
454            .map(|element| element.round() as u8)
455            .collect()
456    }
457
458    fn full_rgb_range() -> Vec<u8> {
459        (0..=255).collect()
460    }
461
462    #[test]
463    fn test_range_integrity() {
464        let range = _range(3.0, 9999.0, 1234);
465
466        for (i, value) in range.into_iter().enumerate().take(1234) {
467            assert_approx_eq!(
468                f64,
469                value,
470                8.1070559611f64.mul_add(i as f64, 3.0),
471                epsilon = 1e-5
472            );
473        }
474    }
475
476    #[test]
477    fn test_yto_lstar_to_y() {
478        for y in _range(0.0, 100.0, 1001) {
479            let result = y_from_lstar(lstar_from_y(y));
480
481            assert_approx_eq!(f64, result, y, epsilon = 1e-5);
482        }
483    }
484
485    #[test]
486    fn test_lstar_to_yto_lstar() {
487        for lstar in _range(0.0, 100.0, 1001) {
488            let result = lstar_from_y(y_from_lstar(lstar));
489
490            assert_approx_eq!(f64, result, lstar, epsilon = 1e-5);
491        }
492    }
493
494    #[test]
495    fn test_yfrom_lstar() {
496        assert_approx_eq!(f64, y_from_lstar(0.0), 0.0, epsilon = 1e-5);
497        assert_approx_eq!(f64, y_from_lstar(0.1), 0.0110705, epsilon = 1e-5);
498        assert_approx_eq!(f64, y_from_lstar(0.2), 0.0221411, epsilon = 1e-5);
499        assert_approx_eq!(f64, y_from_lstar(0.3), 0.0332116, epsilon = 1e-5);
500        assert_approx_eq!(f64, y_from_lstar(0.4), 0.0442822, epsilon = 1e-5);
501        assert_approx_eq!(f64, y_from_lstar(0.5), 0.0553528, epsilon = 1e-5);
502        assert_approx_eq!(f64, y_from_lstar(1.0), 0.1107056, epsilon = 1e-5);
503        assert_approx_eq!(f64, y_from_lstar(2.0), 0.2214112, epsilon = 1e-5);
504        assert_approx_eq!(f64, y_from_lstar(3.0), 0.3321169, epsilon = 1e-5);
505        assert_approx_eq!(f64, y_from_lstar(4.0), 0.4428225, epsilon = 1e-5);
506        assert_approx_eq!(f64, y_from_lstar(5.0), 0.5535282, epsilon = 1e-5);
507        assert_approx_eq!(f64, y_from_lstar(8.0), 0.8856451, epsilon = 1e-5);
508        assert_approx_eq!(f64, y_from_lstar(10.0), 1.1260199, epsilon = 1e-5);
509        assert_approx_eq!(f64, y_from_lstar(15.0), 1.9085832, epsilon = 1e-5);
510        assert_approx_eq!(f64, y_from_lstar(20.0), 2.9890524, epsilon = 1e-5);
511        assert_approx_eq!(f64, y_from_lstar(25.0), 4.4154767, epsilon = 1e-5);
512        assert_approx_eq!(f64, y_from_lstar(30.0), 6.2359055, epsilon = 1e-5);
513        assert_approx_eq!(f64, y_from_lstar(40.0), 11.2509737, epsilon = 1e-5);
514        assert_approx_eq!(f64, y_from_lstar(50.0), 18.4186518, epsilon = 1e-5);
515        assert_approx_eq!(f64, y_from_lstar(60.0), 28.1233342, epsilon = 1e-5);
516        assert_approx_eq!(f64, y_from_lstar(70.0), 40.7494157, epsilon = 1e-5);
517        assert_approx_eq!(f64, y_from_lstar(80.0), 56.6812907, epsilon = 1e-5);
518        assert_approx_eq!(f64, y_from_lstar(90.0), 76.3033539, epsilon = 1e-5);
519        assert_approx_eq!(f64, y_from_lstar(95.0), 87.6183294, epsilon = 1e-5);
520        assert_approx_eq!(f64, y_from_lstar(99.0), 97.4360239, epsilon = 1e-5);
521        assert_approx_eq!(f64, y_from_lstar(100.0), 100.0, epsilon = 1e-5);
522    }
523
524    #[test]
525    fn test_lstar_from_y() {
526        assert_approx_eq!(f64, lstar_from_y(0.0), 0.0, epsilon = 1e-5);
527        assert_approx_eq!(f64, lstar_from_y(0.1), 0.9032962, epsilon = 1e-5);
528        assert_approx_eq!(f64, lstar_from_y(0.2), 1.8065925, epsilon = 1e-5);
529        assert_approx_eq!(f64, lstar_from_y(0.3), 2.7098888, epsilon = 1e-5);
530        assert_approx_eq!(f64, lstar_from_y(0.4), 3.6131851, epsilon = 1e-5);
531        assert_approx_eq!(f64, lstar_from_y(0.5), 4.5164814, epsilon = 1e-5);
532        assert_approx_eq!(f64, lstar_from_y(0.8856451), 8.0, epsilon = 1e-5);
533        assert_approx_eq!(f64, lstar_from_y(1.0), 8.9914424, epsilon = 1e-5);
534        assert_approx_eq!(f64, lstar_from_y(2.0), 15.4872443, epsilon = 1e-5);
535        assert_approx_eq!(f64, lstar_from_y(3.0), 20.0438970, epsilon = 1e-5);
536        assert_approx_eq!(f64, lstar_from_y(4.0), 23.6714419, epsilon = 1e-5);
537        assert_approx_eq!(f64, lstar_from_y(5.0), 26.7347653, epsilon = 1e-5);
538        assert_approx_eq!(f64, lstar_from_y(10.0), 37.8424304, epsilon = 1e-5);
539        assert_approx_eq!(f64, lstar_from_y(15.0), 45.6341970, epsilon = 1e-5);
540        assert_approx_eq!(f64, lstar_from_y(20.0), 51.8372115, epsilon = 1e-5);
541        assert_approx_eq!(f64, lstar_from_y(25.0), 57.0754208, epsilon = 1e-5);
542        assert_approx_eq!(f64, lstar_from_y(30.0), 61.6542222, epsilon = 1e-5);
543        assert_approx_eq!(f64, lstar_from_y(40.0), 69.4695307, epsilon = 1e-5);
544        assert_approx_eq!(f64, lstar_from_y(50.0), 76.0692610, epsilon = 1e-5);
545        assert_approx_eq!(f64, lstar_from_y(60.0), 81.8381891, epsilon = 1e-5);
546        assert_approx_eq!(f64, lstar_from_y(70.0), 86.9968642, epsilon = 1e-5);
547        assert_approx_eq!(f64, lstar_from_y(80.0), 91.6848609, epsilon = 1e-5);
548        assert_approx_eq!(f64, lstar_from_y(90.0), 95.9967686, epsilon = 1e-5);
549        assert_approx_eq!(f64, lstar_from_y(95.0), 98.0335184, epsilon = 1e-5);
550        assert_approx_eq!(f64, lstar_from_y(99.0), 99.6120372, epsilon = 1e-5);
551        assert_approx_eq!(f64, lstar_from_y(100.0), 100.0, epsilon = 1e-5);
552    }
553
554    #[test]
555    fn test_ycontinuity() {
556        let epsilon = 1e-6;
557        let delta = 1e-8;
558        let left = 8.0 - delta;
559        let mid = 8.0;
560        let right = 8.0 + delta;
561
562        assert_approx_eq!(
563            f64,
564            y_from_lstar(left),
565            y_from_lstar(mid),
566            epsilon = epsilon
567        );
568        assert_approx_eq!(
569            f64,
570            y_from_lstar(right),
571            y_from_lstar(mid),
572            epsilon = epsilon
573        );
574    }
575
576    #[test]
577    fn test_rgb_to_xyz_to_rgb() {
578        for r in rgb_range() {
579            for g in rgb_range() {
580                for b in rgb_range() {
581                    let argb = Argb::new(255, r, g, b);
582                    let xyz = Xyz::from(argb);
583                    let converted = Argb::from(xyz);
584
585                    assert_approx_eq!(f64, f64::from(converted.red), f64::from(r), epsilon = 1.5);
586                    assert_approx_eq!(f64, f64::from(converted.green), f64::from(g), epsilon = 1.5);
587                    assert_approx_eq!(f64, f64::from(converted.blue), f64::from(b), epsilon = 1.5);
588                }
589            }
590        }
591    }
592
593    #[test]
594    fn test_rgb_to_lab_to_rgb() {
595        for r in rgb_range() {
596            for g in rgb_range() {
597                for b in rgb_range() {
598                    let argb = Argb::new(255, r, g, b);
599                    let lab = Lab::from(argb);
600                    let converted = Argb::from(lab);
601
602                    assert_approx_eq!(f64, f64::from(converted.red), f64::from(r), epsilon = 1.5);
603                    assert_approx_eq!(f64, f64::from(converted.green), f64::from(g), epsilon = 1.5);
604                    assert_approx_eq!(f64, f64::from(converted.blue), f64::from(b), epsilon = 1.5);
605                }
606            }
607        }
608    }
609
610    #[test]
611    fn test_rgb_to_lstar_to_rgb() {
612        let full_rgb_range = full_rgb_range();
613
614        for component in full_rgb_range {
615            let argb = Argb::new(255, component, component, component);
616            let lstar = argb.as_lstar();
617            let converted = Argb::from_lstar(lstar);
618
619            assert_eq!(converted, argb);
620        }
621    }
622
623    #[test]
624    fn test_rgb_to_lstar_to_ycommutes() {
625        for r in rgb_range() {
626            for g in rgb_range() {
627                for b in rgb_range() {
628                    let argb = Argb::new(255, r, g, b);
629                    let lstar = argb.as_lstar();
630                    let y = y_from_lstar(lstar);
631                    let y2 = Xyz::from(argb).y;
632
633                    assert_approx_eq!(f64, y, y2, epsilon = 1e-5);
634                }
635            }
636        }
637    }
638
639    #[test]
640    fn test_lstar_to_rgb_to_ycommutes() {
641        for lstar in _range(0.0, 100.0, 1001) {
642            let argb = Argb::from_lstar(lstar);
643            let y = Xyz::from(argb).y;
644            let y2 = y_from_lstar(lstar);
645
646            assert_approx_eq!(f64, y, y2, epsilon = 1.0);
647        }
648    }
649
650    #[test]
651    fn test_linearize_delinearize() {
652        let full_rgb_range = full_rgb_range();
653
654        for rgb_component in full_rgb_range {
655            let converted = delinearized(linearized(rgb_component));
656
657            assert_eq!(converted, rgb_component);
658        }
659    }
660}