1#![warn(missing_docs)]
2
3#[cfg(test)]
15mod tests;
16
17mod generated_quantiles;
18
19use core::cmp::PartialOrd;
20use core::ffi::{c_char, CStr};
21use core::fmt::{Debug, Display};
22use core::ops::{Add, Div, Mul, Neg, Rem, Sub};
23
24pub struct Channels<const N: usize>;
29pub trait ValidChannels {}
32impl ValidChannels for Channels<3> {}
33impl ValidChannels for Channels<4> {}
34
35#[allow(missing_docs)]
36pub trait FromF32: Sized {
38 fn ff32(f: f32) -> Self;
39}
40
41impl FromF32 for f32 {
42 fn ff32(f: f32) -> Self {
43 f
44 }
45}
46
47impl FromF32 for f64 {
48 fn ff32(f: f32) -> Self {
49 f.into()
50 }
51}
52
53trait ToDType<T>: Sized {
54 fn to_dt(self) -> T;
55}
56
57impl<U> ToDType<U> for f32
58where
59 U: FromF32 + Sized,
60{
61 fn to_dt(self) -> U {
62 FromF32::ff32(self)
63 }
64}
65
66#[allow(missing_docs)]
67pub trait DType:
69 Sized
70 + Copy
71 + Add<Output = Self>
72 + Div<Output = Self>
73 + Mul<Output = Self>
74 + Neg<Output = Self>
75 + Rem<Output = Self>
76 + Sub<Output = Self>
77 + PartialOrd
78 + Debug
79 + Display
80 + FromF32
81{
82 fn powi(self, rhs: i32) -> Self;
83 fn powf(self, rhs: Self) -> Self;
84 fn spowf(self, rhs: Self) -> Self;
86 fn rem_euclid(self, rhs: Self) -> Self;
87
88 fn abs(self) -> Self;
89 fn trunc(self) -> Self;
90 fn max(self, other: Self) -> Self;
91 fn min(self, other: Self) -> Self;
92
93 fn sin(self) -> Self;
94 fn cos(self) -> Self;
95 fn to_degrees(self) -> Self;
96 fn to_radians(self) -> Self;
97 fn atan2(self, rhs: Self) -> Self;
98
99 fn sqrt(self) -> Self {
100 self.powf((1.0 / 2.0).to_dt())
101 }
102 fn cbrt(self) -> Self {
103 self.powf((1.0 / 3.0).to_dt())
104 }
105 fn ssqrt(self) -> Self {
106 self.spowf((1.0 / 2.0).to_dt())
107 }
108 fn scbrt(self) -> Self {
109 self.spowf((1.0 / 3.0).to_dt())
110 }
111
112 fn _fma(self, mul: Self, add: Self) -> Self;
113 fn fma(self, mul: Self, add: Self) -> Self {
115 if cfg!(target_feature = "fma") {
117 self._fma(mul, add) } else {
119 self * mul + add
120 }
121 }
122}
123
124macro_rules! impl_float {
125 ($type:ident) => {
126 impl DType for $type {
127 fn powi(self, rhs: i32) -> Self {
128 self.powi(rhs)
129 }
130 fn powf(self, rhs: Self) -> Self {
131 self.powf(rhs)
132 }
133 fn spowf(self, rhs: Self) -> Self {
134 self.abs().powf(rhs).copysign(self)
135 }
136 fn rem_euclid(self, rhs: Self) -> Self {
137 self.rem_euclid(rhs)
138 }
139 fn abs(self) -> Self {
140 self.abs()
141 }
142 fn trunc(self) -> Self {
143 self.trunc()
144 }
145 fn max(self, other: Self) -> Self {
146 self.max(other)
147 }
148 fn min(self, other: Self) -> Self {
149 self.min(other)
150 }
151 fn sin(self) -> Self {
152 self.sin()
153 }
154 fn cos(self) -> Self {
155 self.cos()
156 }
157 fn to_degrees(self) -> Self {
158 self.to_degrees()
159 }
160 fn to_radians(self) -> Self {
161 self.to_radians()
162 }
163 fn atan2(self, rhs: Self) -> Self {
164 self.atan2(rhs)
165 }
166 fn sqrt(self) -> Self {
167 self.sqrt()
168 }
169 fn _fma(self, mul: Self, add: Self) -> Self {
174 self.mul_add(mul, add)
175 }
176 }
177 };
178}
179
180impl_float!(f32);
181impl_float!(f64);
182
183pub fn unweave<T, const N: usize>(slice: &[T]) -> [Box<[T]>; N]
188where
189 T: Debug + Copy,
190{
191 let len = slice.len() / N;
192 let mut result: [Vec<T>; N] = (0..N)
193 .map(|_| Vec::with_capacity(len))
194 .collect::<Vec<Vec<T>>>()
195 .try_into()
196 .unwrap();
197
198 slice.chunks_exact(N).for_each(|chunk| {
199 chunk.iter().zip(result.iter_mut()).for_each(|(v, arr)| arr.push(*v));
200 });
201
202 result.map(|v| v.into_boxed_slice())
203}
204
205pub fn weave<T, const N: usize>(array: [Box<[T]>; N]) -> Box<[T]>
208where
209 T: Debug + Copy,
210{
211 let len = array[0].len();
212 (0..len)
213 .into_iter()
214 .fold(Vec::with_capacity(len * N), |mut acc, it| {
215 (0..N).into_iter().for_each(|n| acc.push(array[n][it]));
216 acc
217 })
218 .into_boxed_slice()
219}
220
221pub const D65: [f32; 3] = [0.9504559270516716, 1.0, 1.0890577507598784];
225
226const SRGBEOTF_ALPHA: f32 = 0.055;
227const SRGBEOTF_GAMMA: f32 = 2.4;
228const SRGBEOTF_PHI: f32 = 12.92;
234const SRGBEOTF_CHI: f32 = 0.04045;
235const SRGBEOTF_CHI_INV: f32 = 0.0031308;
236
237const LAB_DELTA: f32 = 6.0 / 29.0;
239
240const PQEOTF_M1: f32 = 2610. / 16384.;
242const PQEOTF_M2: f32 = 2523. / 4096. * 128.;
243const PQEOTF_C1: f32 = 3424. / 4096.;
244const PQEOTF_C2: f32 = 2413. / 4096. * 32.;
245const PQEOTF_C3: f32 = 2392. / 4096. * 32.;
246
247const JZAZBZ_B: f32 = 1.15;
249const JZAZBZ_G: f32 = 0.66;
250const JZAZBZ_D: f32 = -0.56;
251const JZAZBZ_D0: f32 = 1.6295499532821566e-11;
252const JZAZBZ_P: f32 = 1.7 * PQEOTF_M2;
253
254const fn t(m: [[f32; 3]; 3]) -> [[f32; 3]; 3] {
262 [
263 [m[0][0], m[1][0], m[2][0]],
264 [m[0][1], m[1][1], m[2][1]],
265 [m[0][2], m[1][2], m[2][2]],
266 ]
267}
268
269fn mm<T: DType>(m: [[f32; 3]; 3], p: [T; 3]) -> [T; 3] {
271 [
272 p[0].fma(m[0][0].to_dt(), p[1].fma(m[1][0].to_dt(), p[2] * m[2][0].to_dt())),
273 p[0].fma(m[0][1].to_dt(), p[1].fma(m[1][1].to_dt(), p[2] * m[2][1].to_dt())),
274 p[0].fma(m[0][2].to_dt(), p[1].fma(m[1][2].to_dt(), p[2] * m[2][2].to_dt())),
275 ]
276}
277
278const XYZ65_MAT: [[f32; 3]; 3] = t([
280 [0.4124, 0.3576, 0.1805],
281 [0.2126, 0.7152, 0.0722],
282 [0.0193, 0.1192, 0.9505],
283]);
284
285const XYZ65_MAT_INV: [[f32; 3]; 3] = t([
294 [3.2406254773, -1.5372079722, -0.4986285987],
295 [-0.9689307147, 1.8757560609, 0.0415175238],
296 [0.0557101204, -0.2040210506, 1.0569959423],
297]);
298
299const OKLAB_M1: [[f32; 3]; 3] = [
302 [0.8189330101, 0.0329845436, 0.0482003018],
303 [0.3618667424, 0.9293118715, 0.2643662691],
304 [-0.1288597137, 0.0361456387, 0.6338517070],
305];
306const OKLAB_M2: [[f32; 3]; 3] = [
307 [0.2104542553, 1.9779984951, 0.0259040371],
308 [0.7936177850, -2.4285922050, 0.7827717662],
309 [-0.0040720468, 0.4505937099, -0.8086757660],
310];
311const OKLAB_M1_INV: [[f32; 3]; 3] = [
312 [1.2270138511, -0.0405801784, -0.0763812845],
313 [-0.5577999807, 1.1122568696, -0.4214819784],
314 [0.281256149, -0.0716766787, 1.5861632204],
315];
316const OKLAB_M2_INV: [[f32; 3]; 3] = [
317 [0.9999999985, 1.0000000089, 1.0000000547],
318 [0.3963377922, -0.1055613423, -0.0894841821],
319 [0.2158037581, -0.0638541748, -1.2914855379],
320];
321
322const JZAZBZ_M1: [[f32; 3]; 3] = t([
324 [0.41478972, 0.579999, 0.0146480],
325 [-0.2015100, 1.120649, 0.0531008],
326 [-0.0166008, 0.264800, 0.6684799],
327]);
328const JZAZBZ_M2: [[f32; 3]; 3] = t([
329 [0.500000, 0.500000, 0.000000],
330 [3.524000, -4.066708, 0.542708],
331 [0.199076, 1.096799, -1.295875],
332]);
333
334const JZAZBZ_M1_INV: [[f32; 3]; 3] = t([
335 [1.9242264358, -1.0047923126, 0.037651404],
336 [0.3503167621, 0.7264811939, -0.0653844229],
337 [-0.090982811, -0.3127282905, 1.5227665613],
338]);
339const JZAZBZ_M2_INV: [[f32; 3]; 3] = t([
340 [1., 0.1386050433, 0.0580473162],
341 [1., -0.1386050433, -0.0580473162],
342 [1., -0.096019242, -0.8118918961],
343]);
344
345const ICTCP_M1: [[f32; 3]; 3] = t([
347 [1688. / 4096., 2146. / 4096., 262. / 4096.],
348 [683. / 4096., 2951. / 4096., 462. / 4096.],
349 [99. / 4096., 309. / 4096., 3688. / 4096.],
350]);
351const ICTCP_M2: [[f32; 3]; 3] = t([
352 [2048. / 4096., 2048. / 4096., 0. / 4096.],
353 [6610. / 4096., -13613. / 4096., 7003. / 4096.],
354 [17933. / 4096., -17390. / 4096., -543. / 4096.],
355]);
356
357const ICTCP_M1_INV: [[f32; 3]; 3] = t([
358 [3.4366066943, -2.5064521187, 0.0698454243],
359 [-0.7913295556, 1.9836004518, -0.1922708962],
360 [-0.0259498997, -0.0989137147, 1.1248636144],
361]);
362const ICTCP_M2_INV: [[f32; 3]; 3] = t([
363 [1., 0.008609037, 0.111029625],
364 [1., -0.008609037, -0.111029625],
365 [1., 0.5600313357, -0.320627175],
366]);
367pub fn srgb_eotf<T: DType>(n: T) -> T {
375 if n <= SRGBEOTF_CHI.to_dt() {
376 n / SRGBEOTF_PHI.to_dt()
377 } else {
378 ((n + SRGBEOTF_ALPHA.to_dt()) / (SRGBEOTF_ALPHA + 1.0).to_dt()).powf(SRGBEOTF_GAMMA.to_dt())
379 }
380}
381
382pub fn srgb_oetf<T: DType>(n: T) -> T {
386 if n <= SRGBEOTF_CHI_INV.to_dt() {
387 n * SRGBEOTF_PHI.to_dt()
388 } else {
389 (n.powf((1.0 / SRGBEOTF_GAMMA).to_dt())).fma((1.0 + SRGBEOTF_ALPHA).to_dt(), (-SRGBEOTF_ALPHA).to_dt())
390 }
391}
392
393fn pq_eotf_common<T: DType>(e: T, m2: T) -> T {
395 let ep_pow_1divm2 = e.spowf(T::ff32(1.0) / m2);
396
397 let numerator: T = (ep_pow_1divm2 - PQEOTF_C1.to_dt()).max(0.0.to_dt());
398 let denominator: T = ep_pow_1divm2.fma(T::ff32(-PQEOTF_C3), PQEOTF_C2.to_dt());
399
400 let y = (numerator / denominator).spowf((1.0 / PQEOTF_M1).to_dt());
401
402 y * 10000.0.to_dt()
403}
404
405fn pq_oetf_common<T: DType>(f: T, m2: T) -> T {
407 let y = f / 10000.0.to_dt();
408 let y_pow_m1 = y.spowf(PQEOTF_M1.to_dt());
409
410 let numerator: T = T::ff32(PQEOTF_C2).fma(y_pow_m1, PQEOTF_C1.to_dt());
411 let denominator: T = T::ff32(PQEOTF_C3).fma(y_pow_m1, 1.0.to_dt());
412
413 (numerator / denominator).spowf(m2)
414}
415
416pub fn pq_eotf<T: DType>(e: T) -> T {
420 pq_eotf_common(e, PQEOTF_M2.to_dt())
421}
422
423pub fn pq_oetf<T: DType>(f: T) -> T {
427 pq_oetf_common(f, PQEOTF_M2.to_dt())
428}
429
430pub fn pqz_eotf<T: DType>(e: T) -> T {
436 pq_eotf_common(e, JZAZBZ_P.to_dt())
437}
438
439pub fn pqz_oetf<T: DType>(f: T) -> T {
445 pq_oetf_common(f, JZAZBZ_P.to_dt())
446}
447
448const K_HIGH2022: [f32; 4] = [0.1644, 0.0603, 0.1307, 0.0060];
454
455pub const HIGH2023_MEAN: f32 = 20.956442;
460
461pub fn hk_high2023<T: DType, const N: usize>(lch: &[T; N]) -> T
464where
465 Channels<N>: ValidChannels,
466{
467 let fby: T = T::ff32(K_HIGH2022[0]).fma(
468 ((lch[2] - 90.0.to_dt()) / 2.0.to_dt()).to_radians().sin().abs(),
469 K_HIGH2022[1].to_dt(),
470 );
471
472 let fr: T = if lch[2] <= 90.0.to_dt() || lch[2] >= 270.0.to_dt() {
473 T::ff32(K_HIGH2022[2]).fma(lch[2].to_radians().cos().abs(), K_HIGH2022[3].to_dt())
474 } else {
475 0.0.to_dt()
476 };
477
478 (fby + fr) * lch[1]
479}
480
481pub fn hk_high2023_comp<T: DType, const N: usize>(lch: &mut [T; N])
484where
485 Channels<N>: ValidChannels,
486{
487 lch[0] = lch[0] + (T::ff32(HIGH2023_MEAN) - hk_high2023(lch)) * (lch[1] / 100.0.to_dt())
488}
489
490#[derive(Clone, Copy, PartialEq, Eq, Debug)]
496pub enum Space {
497 SRGB,
499
500 HSV,
504
505 LRGB,
507
508 XYZ,
510
511 CIELAB,
515
516 CIELCH,
520
521 OKLAB,
527
528 OKLCH,
530
531 JZAZBZ,
537
538 JZCZHZ,
540}
541
542impl TryFrom<&str> for Space {
543 type Error = ();
544 fn try_from(value: &str) -> Result<Self, ()> {
545 match value.to_ascii_lowercase().trim() {
546 "srgb" => Ok(Space::SRGB),
547 "hsv" => Ok(Space::HSV),
548 "lrgb" | "rgb" => Ok(Space::LRGB),
549 "xyz" | "cie xyz" | "ciexyz" => Ok(Space::XYZ),
550 "lab" | "cie lab" | "cielab" => Ok(Space::CIELAB),
552 "lch" | "cie lch" | "cielch" => Ok(Space::CIELCH),
553 "oklab" => Ok(Space::OKLAB),
554 "oklch" => Ok(Space::OKLCH),
555 "jzazbz" => Ok(Space::JZAZBZ),
556 "jzczhz" => Ok(Space::JZCZHZ),
557 _ => Err(()),
558 }
559 }
560}
561
562impl TryFrom<*const c_char> for Space {
563 type Error = ();
564 fn try_from(value: *const c_char) -> Result<Self, ()> {
565 if value.is_null() {
566 Err(())
567 } else {
568 unsafe { CStr::from_ptr(value) }
569 .to_str()
570 .ok()
571 .map(|s| Self::try_from(s).ok())
572 .flatten()
573 .ok_or(())
574 }
575 }
576}
577
578impl Display for Space {
579 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
580 core::fmt::write(
581 f,
582 core::format_args!(
583 "{}",
584 match self {
585 Self::SRGB => "sRGB",
586 Self::HSV => "HSV",
587 Self::LRGB => "RGB",
588 Self::XYZ => "CIE XYZ",
589 Self::CIELAB => "CIE LAB",
590 Self::CIELCH => "CIE LCH",
591 Self::OKLAB => "Oklab",
592 Self::OKLCH => "Oklch",
593 Self::JZAZBZ => "JzAzBz",
594 Self::JZCZHZ => "JzCzHz",
595 }
596 ),
597 )
598 }
599}
600
601impl Space {
602 pub fn channels(&self) -> [char; 3] {
604 match self {
605 Space::SRGB => ['r', 'g', 'b'],
606 Space::HSV => ['h', 's', 'v'],
607 Space::LRGB => ['r', 'g', 'b'],
608 Space::XYZ => ['x', 'y', 'z'],
609 Space::CIELAB => ['l', 'a', 'b'],
610 Space::CIELCH => ['l', 'c', 'h'],
611 Space::OKLAB => ['l', 'a', 'b'],
612 Space::OKLCH => ['l', 'c', 'h'],
613 Space::JZAZBZ => ['j', 'a', 'b'],
614 Space::JZCZHZ => ['j', 'c', 'h'],
615 }
616 }
617
618 pub const ALL: &'static [Space] = &[
620 Space::SRGB,
621 Space::HSV,
622 Space::LRGB,
623 Space::XYZ,
624 Space::CIELAB,
625 Space::CIELCH,
626 Space::OKLAB,
627 Space::OKLCH,
628 Space::JZAZBZ,
629 Space::JZCZHZ,
630 ];
631
632 pub const UCS: &'static [Space] = &[Space::CIELAB, Space::OKLAB, Space::JZAZBZ];
634
635 pub const UCS_POLAR: &'static [Space] = &[Space::CIELCH, Space::OKLCH, Space::JZCZHZ];
637
638 pub const TRI: &'static [Space] = &[Space::SRGB, Space::LRGB, Space::XYZ];
640
641 pub const fn srgb_quants(&self) -> [[f32; 3]; 101] {
647 generated_quantiles::srgb_quants(self)
649 }
650}
651
652macro_rules! op_single {
657 ($func:ident, $data:expr) => {
658 $func($data)
659 };
660}
661
662macro_rules! op_chunk {
663 ($func:ident, $data:expr) => {
664 $data.iter_mut().for_each(|pixel| $func(pixel))
665 };
666}
667
668#[rustfmt::skip]
669macro_rules! graph {
670 ($recurse:ident, $data:expr, $from:expr, $to:expr, $op:ident) => {
671 match ($from, $to) {
672 (Space::HSV, Space::HSV) => (),
674 (Space::SRGB, Space::SRGB) => (),
675 (Space::LRGB, Space::LRGB) => (),
676 (Space::XYZ, Space::XYZ) => (),
677 (Space::CIELAB, Space::CIELAB) => (),
678 (Space::CIELCH, Space::CIELCH) => (),
679 (Space::OKLAB, Space::OKLAB) => (),
680 (Space::OKLCH, Space::OKLCH) => (),
681 (Space::JZAZBZ, Space::JZAZBZ) => (),
682 (Space::JZCZHZ, Space::JZCZHZ) => (),
683
684 (Space::SRGB, Space::HSV) => $op!(srgb_to_hsv, $data),
686 (Space::CIELAB, Space::CIELCH)
687 | (Space::OKLAB, Space::OKLCH)
688 | (Space::JZAZBZ, Space::JZCZHZ) => $op!(lab_to_lch, $data),
689
690 (Space::HSV, _) => { $op!(hsv_to_srgb, $data); $recurse(Space::SRGB, $to, $data) }
692 (Space::CIELCH, _) => { $op!(lch_to_lab, $data); $recurse(Space::CIELAB, $to, $data) }
693 (Space::OKLCH, _) => { $op!(lch_to_lab, $data); $recurse(Space::OKLAB, $to, $data) }
694 (Space::JZCZHZ, _) => { $op!(lch_to_lab, $data); $recurse(Space::JZAZBZ, $to, $data) }
695
696 (Space::SRGB, _) => { $op!(srgb_to_lrgb, $data); $recurse(Space::LRGB, $to, $data) }
698
699 (Space::LRGB, Space::SRGB | Space::HSV) => { $op!(lrgb_to_srgb, $data); $recurse(Space::SRGB, $to, $data) }
701 (Space::LRGB, _) => { $op!(lrgb_to_xyz, $data); $recurse(Space::XYZ, $to, $data) }
703
704 (Space::XYZ, Space::SRGB | Space::LRGB | Space::HSV) => { $op!(xyz_to_lrgb, $data); $recurse(Space::LRGB, $to, $data) }
706 (Space::XYZ, Space::CIELAB | Space::CIELCH) => { $op!(xyz_to_cielab, $data); $recurse(Space::CIELAB, $to, $data) }
708 (Space::XYZ, Space::OKLAB | Space::OKLCH) => { $op!(xyz_to_oklab, $data); $recurse(Space::OKLAB, $to, $data) }
709 (Space::XYZ, Space::JZAZBZ | Space::JZCZHZ) => { $op!(xyz_to_jzazbz, $data); $recurse(Space::JZAZBZ, $to, $data) }
710
711 (Space::CIELAB, _) => { $op!(cielab_to_xyz, $data); $recurse(Space::XYZ, $to, $data) }
713 (Space::OKLAB, _) => { $op!(oklab_to_xyz, $data); $recurse(Space::XYZ, $to, $data) }
714 (Space::JZAZBZ, _) => { $op!(jzazbz_to_xyz, $data); $recurse(Space::XYZ, $to, $data) }
715 }
716 };
717}
718
719pub fn convert_space<T: DType, const N: usize>(from: Space, to: Space, pixel: &mut [T; N])
722where
723 Channels<N>: ValidChannels,
724{
725 graph!(convert_space, pixel, from, to, op_single);
726}
727
728pub fn convert_space_chunked<T: DType, const N: usize>(from: Space, to: Space, pixels: &mut [[T; N]])
733where
734 Channels<N>: ValidChannels,
735{
736 graph!(convert_space_chunked, pixels, from, to, op_chunk);
737}
738
739pub fn convert_space_sliced<T: DType, const N: usize>(from: Space, to: Space, pixels: &mut [T])
744where
745 Channels<N>: ValidChannels,
746{
747 let (mut_chunks, _remainder): (&mut [[T; N]], &mut [T]) = {
749 let len = pixels.len() / N;
750 let (multiple_of_n, remainder) = pixels.split_at_mut(len * N);
751 let array_slice = {
752 let this = &mut *multiple_of_n;
753 let new_len = this.len() / N;
754 unsafe { core::slice::from_raw_parts_mut(this.as_mut_ptr().cast(), new_len) }
755 };
756 (array_slice, remainder)
757 };
758 graph!(convert_space_chunked, mut_chunks, from, to, op_chunk);
759}
760
761pub fn convert_space_ffi<T: DType, const N: usize>(
767 from: *const c_char,
768 to: *const c_char,
769 pixels: *mut T,
770 len: usize,
771) -> i32
772where
773 Channels<N>: ValidChannels,
774{
775 let Ok(from) = Space::try_from(from) else { return 1 };
776 let Ok(to) = Space::try_from(to) else { return 2 };
777 let pixels = unsafe {
778 if pixels.is_null() {
779 return 3;
780 } else {
781 core::slice::from_raw_parts_mut(pixels, len)
782 }
783 };
784 convert_space_sliced::<T, N>(from, to, pixels);
785 0
786}
787
788fn rm_paren<'a>(s: &'a str) -> &'a str {
792 if let (Some(f), Some(l)) = (s.chars().next(), s.chars().last()) {
793 if ['(', '[', '{'].contains(&f) && [')', ']', '}'].contains(&l) {
794 return &s[1..(s.len() - 1)];
795 }
796 }
797 s
798}
799
800pub fn str2col<T: DType, const N: usize>(mut s: &str) -> Option<(Space, [T; N])>
818where
819 Channels<N>: ValidChannels,
820{
821 s = rm_paren(s.trim());
822 let mut space = Space::SRGB;
823 let mut result = [f32::NAN; N];
824
825 if let Ok(irgb) = hex_to_irgb(s) {
827 return Some((space, irgb_to_srgb(irgb)));
828 }
829
830 let seps = [',', ':', ';'];
831
832 if let Some(i) = s.find(|c: char| c.is_whitespace() || seps.contains(&c) || ['(', '[', '{'].contains(&c)) {
834 if let Ok(sp) = Space::try_from(&s[..i]) {
835 space = sp;
836 s = rm_paren(s[i..].trim_start_matches(|c: char| c.is_whitespace() || seps.contains(&c)));
837 }
838 }
839
840 for (n, split) in s
842 .split(|c: char| c.is_whitespace() || seps.contains(&c))
843 .filter(|s| !s.is_empty())
844 .enumerate()
845 {
846 if n > 3 {
847 return None;
848 } else if n >= result.len() {
849 continue;
850 } else if let Ok(value) = split.parse::<f32>() {
851 result[n] = value;
852 } else if split.ends_with('%') {
853 if let Ok(percent) = split[0..(split.len() - 1)].parse::<f32>() {
854 if n == 3 {
856 result[n] = percent / 100.0;
857 continue;
858 }
859 let (q0, q100) = (space.srgb_quants()[0][n], space.srgb_quants()[100][n]);
860 if q0.is_finite() && q100.is_finite() {
861 result[n] = percent / 100.0 * (q100 - q0) + q0;
862 } else if Space::UCS_POLAR.contains(&space) {
863 result[n] = percent / 100.0 * 360.0
864 } else if space == Space::HSV {
865 result[n] = percent / 100.0
866 } else {
867 return None;
868 }
869 } else {
870 return None;
871 }
872 } else {
873 return None;
874 }
875 }
876 if result.iter().take(3).all(|v| v.is_finite()) {
877 Some((space, result.map(|c| c.to_dt())))
878 } else {
879 None
880 }
881}
882
883pub fn str2space<T: DType, const N: usize>(s: &str, to: Space) -> Option<[T; N]>
887where
888 Channels<N>: ValidChannels,
889{
890 str2col(s).map(|(from, mut col)| {
891 convert_space(from, to, &mut col);
892 col
893 })
894}
895
896pub fn str2space_ffi<T: DType, const N: usize>(s: *const c_char, to: *const c_char) -> *const T
900where
901 Channels<N>: ValidChannels,
902{
903 if s.is_null() {
904 return core::ptr::null();
905 };
906 let Some(s) = unsafe { CStr::from_ptr(s) }.to_str().ok() else {
907 return core::ptr::null();
908 };
909 let Ok(to) = Space::try_from(to) else {
910 return core::ptr::null();
911 };
912 str2space::<T, N>(s, to).map_or(core::ptr::null(), |b| Box::into_raw(Box::new(b)).cast())
913}
914pub fn srgb_to_irgb<const N: usize>(pixel: [f32; N]) -> [u8; N]
920where
921 Channels<N>: ValidChannels,
922{
923 pixel.map(|c| ((c * 255.0).round().max(0.0).min(255.0) as u8))
924}
925
926pub fn irgb_to_hex<const N: usize>(pixel: [u8; N]) -> String
928where
929 Channels<N>: ValidChannels,
930{
931 let mut hex = String::with_capacity(N * 2 + 1);
932 hex.push('#');
933
934 pixel.into_iter().for_each(|c| {
935 [c / 16, c % 16]
936 .into_iter()
937 .for_each(|n| hex.push(if n >= 10 { n + 55 } else { n + 48 } as char))
938 });
939
940 hex
941}
942
943pub fn srgb_to_hsv<T: DType, const N: usize>(pixel: &mut [T; N])
945where
946 Channels<N>: ValidChannels,
947{
948 let vmin = pixel[0].min(pixel[1]).min(pixel[2]);
949 let vmax = pixel[0].max(pixel[1]).max(pixel[2]);
950 let dmax = vmax - vmin;
951
952 let v = vmax;
953
954 let (h, s): (T, T) = if dmax == 0.0.to_dt() {
955 (0.0.to_dt(), 0.0.to_dt())
956 } else {
957 let s = dmax / vmax;
958
959 let [branch_0, branch_1] = [pixel[0] == vmax, pixel[1] == vmax];
960
961 pixel.iter_mut().take(3).for_each(|c| {
962 *c = (((vmax - *c) / 6.0.to_dt()) + (dmax / 2.0.to_dt())) / dmax;
963 });
964
965 let h = if branch_0 {
966 pixel[2] - pixel[1]
967 } else if branch_1 {
968 T::ff32(1.0 / 3.0) + pixel[0] - pixel[2]
969 } else {
970 T::ff32(2.0 / 3.0) + pixel[1] - pixel[0]
971 }
972 .rem_euclid(1.0.to_dt());
973 (h, s)
974 };
975 pixel[0] = h;
976 pixel[1] = s;
977 pixel[2] = v;
978}
979
980pub fn srgb_to_lrgb<T: DType, const N: usize>(pixel: &mut [T; N])
984where
985 Channels<N>: ValidChannels,
986{
987 pixel.iter_mut().take(3).for_each(|c| *c = srgb_eotf(*c));
988}
989
990pub fn lrgb_to_xyz<T: DType, const N: usize>(pixel: &mut [T; N])
994where
995 Channels<N>: ValidChannels,
996{
997 [pixel[0], pixel[1], pixel[2]] = mm(XYZ65_MAT, [pixel[0], pixel[1], pixel[2]])
998}
999
1000pub fn xyz_to_cielab<T: DType, const N: usize>(pixel: &mut [T; N])
1004where
1005 Channels<N>: ValidChannels,
1006{
1007 pixel.iter_mut().take(3).zip(D65).for_each(|(c, d)| *c = *c / d.to_dt());
1009
1010 pixel.iter_mut().take(3).for_each(|c| {
1011 if *c > T::ff32(LAB_DELTA).powi(3) {
1012 *c = c.cbrt()
1013 } else {
1014 *c = *c / (3.0 * LAB_DELTA.powi(2)).to_dt() + (4f32 / 29f32).to_dt()
1015 }
1016 });
1017
1018 [pixel[0], pixel[1], pixel[2]] = [
1019 T::ff32(116.0).fma(pixel[1], T::ff32(-16.0)),
1020 T::ff32(500.0) * (pixel[0] - pixel[1]),
1021 T::ff32(200.0) * (pixel[1] - pixel[2]),
1022 ]
1023}
1024
1025pub fn xyz_to_oklab<T: DType, const N: usize>(pixel: &mut [T; N])
1029where
1030 Channels<N>: ValidChannels,
1031{
1032 let mut lms = mm(OKLAB_M1, [pixel[0], pixel[1], pixel[2]]);
1033 lms.iter_mut().for_each(|c| *c = c.scbrt());
1034 [pixel[0], pixel[1], pixel[2]] = mm(OKLAB_M2, lms);
1035}
1036
1037pub fn xyz_to_jzazbz<T: DType, const N: usize>(pixel: &mut [T; N])
1041where
1042 Channels<N>: ValidChannels,
1043{
1044 let mut lms = mm(
1045 JZAZBZ_M1,
1046 [
1047 pixel[0].fma(JZAZBZ_B.to_dt(), T::ff32(-JZAZBZ_B + 1.0) * pixel[2]),
1048 pixel[1].fma(JZAZBZ_G.to_dt(), T::ff32(-JZAZBZ_G + 1.0) * pixel[0]),
1049 pixel[2],
1050 ],
1051 );
1052
1053 lms.iter_mut().for_each(|e| *e = pqz_oetf(*e));
1054
1055 let lab = mm(JZAZBZ_M2, lms);
1056
1057 pixel[0] = (T::ff32(1.0 + JZAZBZ_D) * lab[0]) / lab[0].fma(JZAZBZ_D.to_dt(), 1.0.to_dt()) - JZAZBZ_D0.to_dt();
1058 pixel[1] = lab[1];
1059 pixel[2] = lab[2];
1060}
1061
1062pub fn _lrgb_to_ictcp<T: DType, const N: usize>(pixel: &mut [T; N])
1073where
1074 Channels<N>: ValidChannels,
1075{
1076 let mut lms = mm(ICTCP_M1, [pixel[0], pixel[1], pixel[2]]);
1086 lms.iter_mut().for_each(|c| *c = pq_oetf(*c));
1088 [pixel[0], pixel[1], pixel[2]] = mm(ICTCP_M2, lms);
1089}
1090
1091pub fn lab_to_lch<T: DType, const N: usize>(pixel: &mut [T; N])
1095where
1096 Channels<N>: ValidChannels,
1097{
1098 [pixel[0], pixel[1], pixel[2]] = [
1099 pixel[0],
1100 (pixel[1].powi(2) + pixel[2].powi(2)).sqrt(),
1101 pixel[2].atan2(pixel[1]).to_degrees().rem_euclid(360.0.to_dt()),
1102 ];
1103}
1104
1105pub fn irgb_to_srgb<T: DType, const N: usize>(pixel: [u8; N]) -> [T; N]
1111where
1112 Channels<N>: ValidChannels,
1113{
1114 pixel.map(|c| T::ff32(c as f32 / 255.0))
1115}
1116
1117pub fn hex_to_irgb_default<const N: usize, const DEFAULT: u8>(hex: &str) -> Result<[u8; N], String>
1120where
1121 Channels<N>: ValidChannels,
1122{
1123 let mut chars = hex.trim().chars();
1124 if chars.as_str().starts_with('#') {
1125 chars.next();
1126 }
1127
1128 let ids: Vec<u32> = match chars.as_str().len() {
1129 6 | 8 => chars
1130 .map(|c| {
1131 let u = c as u32;
1132 if 57 >= u && u >= 48 {
1134 Ok(u - 48)
1135 } else if 70 >= u && u >= 65 {
1137 Ok(u - 55)
1138 } else if 102 >= u && u >= 97 {
1140 Ok(u - 87)
1141 } else {
1142 Err(String::from("Hex character '") + &String::from(c) + "' out of bounds")
1143 }
1144 })
1145 .collect(),
1146 n => Err(String::from("Incorrect hex length ") + &n.to_string()),
1147 }?;
1148
1149 let mut result = [DEFAULT; N];
1150
1151 ids.chunks(2)
1152 .take(result.len())
1153 .enumerate()
1154 .for_each(|(n, chunk)| result[n] = ((chunk[0]) * 16 + chunk[1]) as u8);
1155
1156 Ok(result)
1157}
1158
1159pub fn hex_to_irgb<const N: usize>(hex: &str) -> Result<[u8; N], String>
1163where
1164 Channels<N>: ValidChannels,
1165{
1166 hex_to_irgb_default::<N, 255>(hex)
1167}
1168
1169pub fn hsv_to_srgb<T: DType, const N: usize>(pixel: &mut [T; N])
1171where
1172 Channels<N>: ValidChannels,
1173{
1174 if pixel[1] == 0.0.to_dt() {
1175 [pixel[0], pixel[1]] = [pixel[2]; 2];
1176 } else {
1177 let mut var_h = pixel[0] * 6.0.to_dt();
1178 if var_h == 6.0.to_dt() {
1179 var_h = 0.0.to_dt()
1180 }
1181 let var_i = var_h.trunc();
1182 let var_1 = pixel[2] * (T::ff32(1.0) - pixel[1]);
1183 let var_2 = pixel[2] * (-var_h + var_i).fma(pixel[1], 1.0.to_dt());
1184 let var_3 = pixel[2] * (T::ff32(-1.0) + (var_h - var_i)).fma(pixel[1], T::ff32(1.0));
1185
1186 [pixel[0], pixel[1], pixel[2]] = if var_i == 0.0.to_dt() {
1187 [pixel[2], var_3, var_1]
1188 } else if var_i == 1.0.to_dt() {
1189 [var_2, pixel[2], var_1]
1190 } else if var_i == 2.0.to_dt() {
1191 [var_1, pixel[2], var_3]
1192 } else if var_i == 3.0.to_dt() {
1193 [var_1, var_2, pixel[2]]
1194 } else if var_i == 4.0.to_dt() {
1195 [var_3, var_1, pixel[2]]
1196 } else {
1197 [pixel[2], var_1, var_2]
1198 }
1199 }
1200}
1201
1202pub fn lrgb_to_srgb<T: DType, const N: usize>(pixel: &mut [T; N])
1206where
1207 Channels<N>: ValidChannels,
1208{
1209 pixel.iter_mut().take(3).for_each(|c| *c = srgb_oetf(*c));
1210}
1211
1212pub fn xyz_to_lrgb<T: DType, const N: usize>(pixel: &mut [T; N])
1216where
1217 Channels<N>: ValidChannels,
1218{
1219 [pixel[0], pixel[1], pixel[2]] = mm(XYZ65_MAT_INV, [pixel[0], pixel[1], pixel[2]])
1220}
1221
1222pub fn cielab_to_xyz<T: DType, const N: usize>(pixel: &mut [T; N])
1226where
1227 Channels<N>: ValidChannels,
1228{
1229 pixel[0] = pixel[0].fma((1.0 / 116.0).to_dt(), (16.0 / 116.0).to_dt());
1230 [pixel[0], pixel[1], pixel[2]] = [
1231 pixel[0] + pixel[1] / 500.0.to_dt(),
1232 pixel[0],
1233 pixel[0] - pixel[2] / 200.0.to_dt(),
1234 ];
1235
1236 pixel.iter_mut().take(3).for_each(|c| {
1237 if *c > LAB_DELTA.to_dt() {
1238 *c = c.powi(3)
1239 } else {
1240 *c = T::ff32(3.0) * LAB_DELTA.powi(2).to_dt() * (*c - (4f32 / 29f32).to_dt())
1241 }
1242 });
1243
1244 pixel.iter_mut().take(3).zip(D65).for_each(|(c, d)| *c = *c * d.to_dt());
1245}
1246
1247pub fn oklab_to_xyz<T: DType, const N: usize>(pixel: &mut [T; N])
1251where
1252 Channels<N>: ValidChannels,
1253{
1254 let mut lms = mm(OKLAB_M2_INV, [pixel[0], pixel[1], pixel[2]]);
1255 lms.iter_mut().for_each(|c| *c = c.powi(3));
1256 [pixel[0], pixel[1], pixel[2]] = mm(OKLAB_M1_INV, lms);
1257}
1258
1259pub fn jzazbz_to_xyz<T: DType, const N: usize>(pixel: &mut [T; N])
1263where
1264 Channels<N>: ValidChannels,
1265{
1266 let mut lms = mm(
1267 JZAZBZ_M2_INV,
1268 [
1269 (pixel[0] + JZAZBZ_D0.to_dt())
1270 / (pixel[0] + JZAZBZ_D0.to_dt()).fma(T::ff32(-JZAZBZ_D), T::ff32(1.0 + JZAZBZ_D)),
1271 pixel[1],
1272 pixel[2],
1273 ],
1274 );
1275
1276 lms.iter_mut().for_each(|c| *c = pqz_eotf(*c));
1277
1278 [pixel[0], pixel[1], pixel[2]] = mm(JZAZBZ_M1_INV, lms);
1279
1280 pixel[0] = pixel[2].fma((JZAZBZ_B - 1.0).to_dt(), pixel[0]) / JZAZBZ_B.to_dt();
1281 pixel[1] = pixel[0].fma((JZAZBZ_G - 1.0).to_dt(), pixel[1]) / JZAZBZ_G.to_dt();
1282}
1283
1284pub fn _ictcp_to_lrgb<T: DType, const N: usize>(pixel: &mut [T; N])
1296where
1297 Channels<N>: ValidChannels,
1298{
1299 let mut lms = mm(ICTCP_M2_INV, [pixel[0], pixel[1], pixel[2]]);
1301 lms.iter_mut().for_each(|c| *c = pq_eotf(*c));
1303 [pixel[0], pixel[1], pixel[2]] = mm(ICTCP_M1_INV, lms);
1304}
1305
1306pub fn lch_to_lab<T: DType, const N: usize>(pixel: &mut [T; N])
1310where
1311 Channels<N>: ValidChannels,
1312{
1313 [pixel[0], pixel[1], pixel[2]] = [
1314 pixel[0],
1315 pixel[1] * pixel[2].to_radians().cos(),
1316 pixel[1] * pixel[2].to_radians().sin(),
1317 ]
1318}
1319
1320#[no_mangle]
1325extern "C" fn convert_space_3f32(from: *const c_char, to: *const c_char, pixels: *mut f32, len: usize) -> i32 {
1326 convert_space_ffi::<_, 3>(from, to, pixels, len)
1327}
1328#[no_mangle]
1329extern "C" fn convert_space_4f32(from: *const c_char, to: *const c_char, pixels: *mut f32, len: usize) -> i32 {
1330 convert_space_ffi::<_, 4>(from, to, pixels, len)
1331}
1332#[no_mangle]
1333extern "C" fn convert_space_3f64(from: *const c_char, to: *const c_char, pixels: *mut f64, len: usize) -> i32 {
1334 convert_space_ffi::<_, 3>(from, to, pixels, len)
1335}
1336#[no_mangle]
1337extern "C" fn convert_space_4f64(from: *const c_char, to: *const c_char, pixels: *mut f64, len: usize) -> i32 {
1338 convert_space_ffi::<_, 4>(from, to, pixels, len)
1339}
1340
1341#[no_mangle]
1342extern "C" fn str2space_3f32(s: *const c_char, to: *const c_char) -> *const f32 {
1343 str2space_ffi::<f32, 3>(s, to)
1344}
1345#[no_mangle]
1346extern "C" fn str2space_4f32(s: *const c_char, to: *const c_char) -> *const f32 {
1347 str2space_ffi::<f32, 4>(s, to)
1348}
1349#[no_mangle]
1350extern "C" fn str2space_3f64(s: *const c_char, to: *const c_char) -> *const f64 {
1351 str2space_ffi::<f64, 3>(s, to)
1352}
1353#[no_mangle]
1354extern "C" fn str2space_4f64(s: *const c_char, to: *const c_char) -> *const f64 {
1355 str2space_ffi::<f64, 4>(s, to)
1356}
1357
1358macro_rules! cdef1 {
1359 ($base:ident, $f32:ident, $f64:ident) => {
1360 #[no_mangle]
1361 extern "C" fn $f32(value: f32) -> f32 {
1362 $base(value)
1363 }
1364 #[no_mangle]
1365 extern "C" fn $f64(value: f64) -> f64 {
1366 $base(value)
1367 }
1368 };
1369}
1370
1371macro_rules! cdef3 {
1372 ($base:ident, $f32_3:ident, $f64_3:ident, $f32_4:ident, $f64_4:ident) => {
1373 #[no_mangle]
1374 extern "C" fn $f32_3(pixel: &mut [f32; 3]) {
1375 $base(pixel)
1376 }
1377 #[no_mangle]
1378 extern "C" fn $f64_3(pixel: &mut [f64; 3]) {
1379 $base(pixel)
1380 }
1381 #[no_mangle]
1382 extern "C" fn $f32_4(pixel: &mut [f32; 4]) {
1383 $base(pixel)
1384 }
1385 #[no_mangle]
1386 extern "C" fn $f64_4(pixel: &mut [f64; 4]) {
1387 $base(pixel)
1388 }
1389 };
1390}
1391
1392macro_rules! cdef31 {
1393 ($base:ident, $f32_3:ident, $f64_3:ident, $f32_4:ident, $f64_4:ident) => {
1394 #[no_mangle]
1395 extern "C" fn $f32_3(pixel: &[f32; 3]) -> f32 {
1396 $base(pixel)
1397 }
1398 #[no_mangle]
1399 extern "C" fn $f64_3(pixel: &[f64; 3]) -> f64 {
1400 $base(pixel)
1401 }
1402 #[no_mangle]
1403 extern "C" fn $f32_4(pixel: &[f32; 4]) -> f32 {
1404 $base(pixel)
1405 }
1406 #[no_mangle]
1407 extern "C" fn $f64_4(pixel: &[f64; 4]) -> f64 {
1408 $base(pixel)
1409 }
1410 };
1411}
1412
1413cdef1!(srgb_eotf, srgb_eotf_f32, srgb_eotf_f64);
1415cdef1!(srgb_oetf, srgb_oetf_f32, srgb_oetf_f64);
1416cdef1!(pq_eotf, pq_eotf_f32, pq_eotf_f64);
1417cdef1!(pqz_eotf, pqz_eotf_f32, pqz_eotf_f64);
1418cdef1!(pq_oetf, pq_oetf_f32, pq_oetf_f64);
1419cdef1!(pqz_oetf, pqz_oetf_f32, pqz_oetf_f64);
1420
1421cdef31!(
1423 hk_high2023,
1424 hk_high2023_3f32,
1425 hk_high2023_3f64,
1426 hk_high2023_4f32,
1427 hk_high2023_4f64
1428);
1429cdef3!(
1430 hk_high2023_comp,
1431 hk_high2023_comp_3f32,
1432 hk_high2023_comp_3f64,
1433 hk_high2023_comp_4f32,
1434 hk_high2023_comp_4f64
1435);
1436
1437cdef3!(
1439 srgb_to_hsv,
1440 srgb_to_hsv_3f32,
1441 srgb_to_hsv_3f64,
1442 srgb_to_hsv_4f32,
1443 srgb_to_hsv_4f64
1444);
1445cdef3!(
1446 srgb_to_lrgb,
1447 srgb_to_lrgb_3f32,
1448 srgb_to_lrgb_3f64,
1449 srgb_to_lrgb_4f32,
1450 srgb_to_lrgb_4f64
1451);
1452cdef3!(
1453 lrgb_to_xyz,
1454 lrgb_to_xyz_3f32,
1455 lrgb_to_xyz_3f64,
1456 lrgb_to_xyz_4f32,
1457 lrgb_to_xyz_4f64
1458);
1459cdef3!(
1460 xyz_to_cielab,
1461 xyz_to_cielab_3f32,
1462 xyz_to_cielab_3f64,
1463 xyz_to_cielab_4f32,
1464 xyz_to_cielab_4f64
1465);
1466cdef3!(
1467 xyz_to_oklab,
1468 xyz_to_oklab_3f32,
1469 xyz_to_oklab_3f64,
1470 xyz_to_oklab_4f32,
1471 xyz_to_oklab_4f64
1472);
1473cdef3!(
1474 xyz_to_jzazbz,
1475 xyz_to_jzazbz_3f32,
1476 xyz_to_jzazbz_3f64,
1477 xyz_to_jzazbz_4f32,
1478 xyz_to_jzazbz_4f64
1479);
1480cdef3!(
1481 lab_to_lch,
1482 lab_to_lch_3f32,
1483 lab_to_lch_3f64,
1484 lab_to_lch_4f32,
1485 lab_to_lch_4f64
1486);
1487cdef3!(
1488 _lrgb_to_ictcp,
1489 _lrgb_to_ictcp_3f32,
1490 _lrgb_to_ictcp_3f64,
1491 _lrgb_to_ictcp_4f32,
1492 _lrgb_to_ictcp_4f64
1493);
1494
1495cdef3!(
1497 hsv_to_srgb,
1498 hsv_to_srgb_3f32,
1499 hsv_to_srgb_3f64,
1500 hsv_to_srgb_4f32,
1501 hsv_to_srgb_4f64
1502);
1503cdef3!(
1504 lrgb_to_srgb,
1505 lrgb_to_srgb_3f32,
1506 lrgb_to_srgb_3f64,
1507 lrgb_to_srgb_4f32,
1508 lrgb_to_srgb_4f64
1509);
1510cdef3!(
1511 xyz_to_lrgb,
1512 xyz_to_lrgb_3f32,
1513 xyz_to_lrgb_3f64,
1514 xyz_to_lrgb_4f32,
1515 xyz_to_lrgb_4f64
1516);
1517cdef3!(
1518 cielab_to_xyz,
1519 cielab_to_xyz_3f32,
1520 cielab_to_xyz_3f64,
1521 cielab_to_xyz_4f32,
1522 cielab_to_xyz_4f64
1523);
1524cdef3!(
1525 oklab_to_xyz,
1526 oklab_to_xyz_3f32,
1527 oklab_to_xyz_3f64,
1528 oklab_to_xyz_4f32,
1529 oklab_to_xyz_4f64
1530);
1531cdef3!(
1532 jzazbz_to_xyz,
1533 jzazbz_to_xyz_3f32,
1534 jzazbz_to_xyz_3f64,
1535 jzazbz_to_xyz_4f32,
1536 jzazbz_to_xyz_4f64
1537);
1538cdef3!(
1539 lch_to_lab,
1540 lch_to_lab_3f32,
1541 lch_to_lab_3f64,
1542 lch_to_lab_4f32,
1543 lch_to_lab_4f64
1544);
1545cdef3!(
1546 _ictcp_to_lrgb,
1547 _ictcp_to_lrgb_3f32,
1548 _ictcp_to_lrgb_3f64,
1549 _ictcp_to_lrgb_4f32,
1550 _ictcp_to_lrgb_4f64
1551);
1552
1553