1#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
9#[non_exhaustive]
10pub enum LineCap {
11 #[default]
12 Butt = 0,
13 Round = 1,
14 Square = 2,
15}
16
17impl LineCap {
18 pub fn from_i32(v: i32) -> Option<Self> {
19 match v {
20 0 => Some(Self::Butt),
21 1 => Some(Self::Round),
22 2 => Some(Self::Square),
23 _ => None,
24 }
25 }
26}
27
28#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
30#[non_exhaustive]
31pub enum LineJoin {
32 #[default]
33 Miter = 0,
34 Round = 1,
35 Bevel = 2,
36}
37
38impl LineJoin {
39 pub fn from_i32(v: i32) -> Option<Self> {
40 match v {
41 0 => Some(Self::Miter),
42 1 => Some(Self::Round),
43 2 => Some(Self::Bevel),
44 _ => None,
45 }
46 }
47}
48
49#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
51#[non_exhaustive]
52pub enum FillRule {
53 #[default]
54 NonZeroWinding,
55 EvenOdd,
56}
57
58#[derive(Clone, Debug)]
60pub struct DashPattern {
61 pub array: Vec<f64>,
62 pub offset: f64,
63}
64
65impl DashPattern {
66 pub fn solid() -> Self {
67 Self {
68 array: Vec::new(),
69 offset: 0.0,
70 }
71 }
72}
73
74impl Default for DashPattern {
75 fn default() -> Self {
76 Self::solid()
77 }
78}
79
80#[derive(Clone, Debug)]
82pub struct DeviceColor {
83 pub r: f64,
84 pub g: f64,
85 pub b: f64,
86 pub native_cmyk: Option<(f64, f64, f64, f64)>,
90 pub process_cmyk: Option<(f64, f64, f64, f64)>,
96}
97
98impl DeviceColor {
99 pub fn from_gray(gray: f64) -> Self {
100 Self {
101 r: gray,
102 g: gray,
103 b: gray,
104 native_cmyk: None,
105 process_cmyk: None,
106 }
107 }
108
109 pub fn from_rgb(r: f64, g: f64, b: f64) -> Self {
110 Self {
111 r,
112 g,
113 b,
114 native_cmyk: None,
115 process_cmyk: None,
116 }
117 }
118
119 pub fn from_cmyk(c: f64, m: f64, y: f64, k: f64) -> Self {
120 Self {
121 r: 1.0 - (c + k).min(1.0),
122 g: 1.0 - (m + k).min(1.0),
123 b: 1.0 - (y + k).min(1.0),
124 native_cmyk: Some((c, m, y, k)),
125 process_cmyk: None,
126 }
127 }
128
129 pub fn from_cmyk_icc(c: f64, m: f64, y: f64, k: f64, icc: &mut crate::icc::IccCache) -> Self {
132 if let Some((r, g, b)) = icc.convert_cmyk(c, m, y, k) {
133 Self {
134 r,
135 g,
136 b,
137 native_cmyk: Some((c, m, y, k)),
138 process_cmyk: None,
139 }
140 } else {
141 Self::from_cmyk(c, m, y, k)
142 }
143 }
144
145 pub fn from_hsb(h: f64, s: f64, b: f64) -> Self {
146 if s == 0.0 {
147 return Self::from_gray(b);
148 }
149 if b == 0.0 {
150 return Self::from_gray(0.0);
151 }
152
153 let mut hue = h * 6.0;
154 if hue >= 6.0 {
155 hue = 0.0;
156 }
157
158 let sector = hue as i32;
159 let frac = hue - sector as f64;
160
161 let p = b * (1.0 - s);
162 let q = b * (1.0 - s * frac);
163 let t = b * (1.0 - s * (1.0 - frac));
164
165 let (r, g, bl) = match sector {
166 0 => (b, t, p),
167 1 => (q, b, p),
168 2 => (p, b, t),
169 3 => (p, q, b),
170 4 => (t, p, b),
171 _ => (b, p, q),
172 };
173
174 Self {
175 r,
176 g,
177 b: bl,
178 native_cmyk: None,
179 process_cmyk: None,
180 }
181 }
182
183 pub fn to_gray(&self) -> f64 {
185 0.3 * self.r + 0.59 * self.g + 0.11 * self.b
186 }
187
188 pub fn to_cmyk(&self) -> (f64, f64, f64, f64) {
190 if let Some(cmyk) = self.native_cmyk {
191 return cmyk;
192 }
193 let c = 1.0 - self.r;
194 let m = 1.0 - self.g;
195 let y = 1.0 - self.b;
196 let k = c.min(m).min(y);
197 (
198 (c - k).clamp(0.0, 1.0),
199 (m - k).clamp(0.0, 1.0),
200 (y - k).clamp(0.0, 1.0),
201 k.clamp(0.0, 1.0),
202 )
203 }
204
205 pub fn to_hsb(&self) -> (f64, f64, f64) {
207 let max_val = self.r.max(self.g).max(self.b);
208 let min_val = self.r.min(self.g).min(self.b);
209 let diff = max_val - min_val;
210
211 let brightness = max_val;
212 let saturation = if max_val == 0.0 { 0.0 } else { diff / max_val };
213
214 let hue = if diff == 0.0 {
215 0.0
216 } else if max_val == self.r {
217 let mut h = (self.g - self.b) / diff;
218 if h < 0.0 {
219 h += 6.0;
220 }
221 h / 6.0
222 } else if max_val == self.g {
223 ((self.b - self.r) / diff + 2.0) / 6.0
224 } else {
225 ((self.r - self.g) / diff + 4.0) / 6.0
226 };
227
228 (hue, saturation, brightness)
229 }
230
231 pub fn black() -> Self {
233 Self {
234 r: 0.0,
235 g: 0.0,
236 b: 0.0,
237 native_cmyk: None,
238 process_cmyk: None,
239 }
240 }
241
242 fn srgb_gamma(u: f64) -> f64 {
244 if u <= 0.0031308 {
245 12.92 * u
246 } else {
247 1.055 * u.powf(1.0 / 2.4) - 0.055
248 }
249 }
250
251 fn from_xyz(x: f64, y: f64, z: f64) -> Self {
253 let lr = 3.2404542 * x + (-1.5371385) * y + (-0.4985314) * z;
255 let lg = (-0.9692660) * x + 1.8760108 * y + 0.0415560 * z;
256 let lb = 0.0556434 * x + (-0.2040259) * y + 1.0572252 * z;
257
258 Self {
259 r: Self::srgb_gamma(lr.max(0.0)).clamp(0.0, 1.0),
260 g: Self::srgb_gamma(lg.max(0.0)).clamp(0.0, 1.0),
261 b: Self::srgb_gamma(lb.max(0.0)).clamp(0.0, 1.0),
262 native_cmyk: None,
263 process_cmyk: None,
264 }
265 }
266
267 fn adapt_xyz_to_d65(x: f64, y: f64, z: f64, src_wp: &[f64; 3]) -> [f64; 3] {
269 const D65: [f64; 3] = [0.95047, 1.0, 1.08883];
271
272 if (src_wp[0] - D65[0]).abs() < 1e-3
274 && (src_wp[1] - D65[1]).abs() < 1e-3
275 && (src_wp[2] - D65[2]).abs() < 1e-3
276 {
277 return [x, y, z];
278 }
279
280 const M: [f64; 9] = [
282 0.8951, -0.7502, 0.0389, 0.2664, 1.7135, -0.0685, -0.1614, 0.0367, 1.0296,
283 ];
284 const M_INV: [f64; 9] = [
286 0.9869929, 0.4323053, -0.0085287, -0.1470543, 0.5183603, 0.0400428, 0.1599627,
287 0.0492912, 0.9684867,
288 ];
289
290 let lms_src = Self::apply_matrix_3x3(&M, src_wp);
292 let lms_d65 = Self::apply_matrix_3x3(&M, &D65);
293
294 let s0 = if lms_src[0].abs() > 1e-10 {
296 lms_d65[0] / lms_src[0]
297 } else {
298 1.0
299 };
300 let s1 = if lms_src[1].abs() > 1e-10 {
301 lms_d65[1] / lms_src[1]
302 } else {
303 1.0
304 };
305 let s2 = if lms_src[2].abs() > 1e-10 {
306 lms_d65[2] / lms_src[2]
307 } else {
308 1.0
309 };
310
311 let lms = Self::apply_matrix_3x3(&M, &[x, y, z]);
313 let scaled = [lms[0] * s0, lms[1] * s1, lms[2] * s2];
314 Self::apply_matrix_3x3(&M_INV, &scaled)
315 }
316
317 fn apply_matrix_3x3(mat: &[f64; 9], v: &[f64; 3]) -> [f64; 3] {
319 [
320 mat[0] * v[0] + mat[3] * v[1] + mat[6] * v[2],
321 mat[1] * v[0] + mat[4] * v[1] + mat[7] * v[2],
322 mat[2] * v[0] + mat[5] * v[1] + mat[8] * v[2],
323 ]
324 }
325
326 fn decode_lookup(table: &[f64], value: f64) -> f64 {
328 let n = table.len();
329 if n < 2 {
330 return table.first().copied().unwrap_or(value);
331 }
332 let idx = value * (n - 1) as f64;
333 let i0 = (idx as usize).min(n - 2);
334 let frac = idx - i0 as f64;
335 table[i0] + (table[i0 + 1] - table[i0]) * frac
336 }
337
338 pub fn from_lab(l_star: f64, a_star: f64, b_star: f64, range: &[f64; 4]) -> Self {
349 const D65: [f64; 3] = [0.95047, 1.0, 1.08883];
351
352 let l_star = l_star.clamp(0.0, 100.0);
353 let a_star = a_star.clamp(range[0], range[1]);
354 let b_star = b_star.clamp(range[2], range[3]);
355
356 let fy = (l_star + 16.0) / 116.0;
357 let fx = a_star / 500.0 + fy;
358 let fz = fy - b_star / 200.0;
359
360 let x = D65[0] * Self::lab_f_inv(fx);
361 let y = D65[1] * Self::lab_f_inv(fy);
362 let z = D65[2] * Self::lab_f_inv(fz);
363
364 Self::from_xyz(x, y, z)
365 }
366
367 fn lab_f_inv(t: f64) -> f64 {
368 if t > 6.0 / 29.0 {
369 t * t * t
370 } else {
371 3.0 * (6.0 / 29.0) * (6.0 / 29.0) * (t - 4.0 / 29.0)
372 }
373 }
374
375 pub fn from_cie_abc(a: f64, b: f64, c: f64, params: &CieAbcParams) -> Self {
377 let mut a = a.clamp(params.range_abc[0], params.range_abc[1]);
378 let mut b = b.clamp(params.range_abc[2], params.range_abc[3]);
379 let mut c = c.clamp(params.range_abc[4], params.range_abc[5]);
380
381 if let Some(ref tables) = params.decode_abc {
382 let ra = params.range_abc[1] - params.range_abc[0];
383 let rb = params.range_abc[3] - params.range_abc[2];
384 let rc = params.range_abc[5] - params.range_abc[4];
385 let na = if ra > 0.0 {
386 (a - params.range_abc[0]) / ra
387 } else {
388 0.0
389 };
390 let nb = if rb > 0.0 {
391 (b - params.range_abc[2]) / rb
392 } else {
393 0.0
394 };
395 let nc = if rc > 0.0 {
396 (c - params.range_abc[4]) / rc
397 } else {
398 0.0
399 };
400 a = Self::decode_lookup(&tables[0], na);
401 b = Self::decode_lookup(&tables[1], nb);
402 c = Self::decode_lookup(&tables[2], nc);
403 }
404
405 let lmn = Self::apply_matrix_3x3(¶ms.matrix_abc, &[a, b, c]);
406
407 let mut l = lmn[0].clamp(params.range_lmn[0], params.range_lmn[1]);
408 let mut m = lmn[1].clamp(params.range_lmn[2], params.range_lmn[3]);
409 let mut n = lmn[2].clamp(params.range_lmn[4], params.range_lmn[5]);
410
411 if let Some(ref tables) = params.decode_lmn {
412 let rl = params.range_lmn[1] - params.range_lmn[0];
413 let rm = params.range_lmn[3] - params.range_lmn[2];
414 let rn = params.range_lmn[5] - params.range_lmn[4];
415 let nl = if rl > 0.0 {
416 (l - params.range_lmn[0]) / rl
417 } else {
418 0.0
419 };
420 let nm = if rm > 0.0 {
421 (m - params.range_lmn[2]) / rm
422 } else {
423 0.0
424 };
425 let nn = if rn > 0.0 {
426 (n - params.range_lmn[4]) / rn
427 } else {
428 0.0
429 };
430 l = Self::decode_lookup(&tables[0], nl);
431 m = Self::decode_lookup(&tables[1], nm);
432 n = Self::decode_lookup(&tables[2], nn);
433 }
434
435 let xyz = Self::apply_matrix_3x3(¶ms.matrix_lmn, &[l, m, n]);
436
437 let xyz = Self::adapt_xyz_to_d65(xyz[0], xyz[1], xyz[2], ¶ms.white_point);
439 Self::from_xyz(xyz[0], xyz[1], xyz[2])
440 }
441
442 pub fn from_cie_a(a: f64, params: &CieAParams) -> Self {
444 let mut a = a.clamp(params.range_a[0], params.range_a[1]);
445
446 if let Some(ref table) = params.decode_a {
447 let ra = params.range_a[1] - params.range_a[0];
448 let na = if ra > 0.0 {
449 (a - params.range_a[0]) / ra
450 } else {
451 0.0
452 };
453 a = Self::decode_lookup(table, na);
454 }
455
456 let lmn = [
457 params.matrix_a[0] * a,
458 params.matrix_a[1] * a,
459 params.matrix_a[2] * a,
460 ];
461
462 let mut l = lmn[0].clamp(params.range_lmn[0], params.range_lmn[1]);
463 let mut m = lmn[1].clamp(params.range_lmn[2], params.range_lmn[3]);
464 let mut n = lmn[2].clamp(params.range_lmn[4], params.range_lmn[5]);
465
466 if let Some(ref tables) = params.decode_lmn {
467 let rl = params.range_lmn[1] - params.range_lmn[0];
468 let rm = params.range_lmn[3] - params.range_lmn[2];
469 let rn = params.range_lmn[5] - params.range_lmn[4];
470 let nl = if rl > 0.0 {
471 (l - params.range_lmn[0]) / rl
472 } else {
473 0.0
474 };
475 let nm = if rm > 0.0 {
476 (m - params.range_lmn[2]) / rm
477 } else {
478 0.0
479 };
480 let nn = if rn > 0.0 {
481 (n - params.range_lmn[4]) / rn
482 } else {
483 0.0
484 };
485 l = Self::decode_lookup(&tables[0], nl);
486 m = Self::decode_lookup(&tables[1], nm);
487 n = Self::decode_lookup(&tables[2], nn);
488 }
489
490 let xyz = Self::apply_matrix_3x3(¶ms.matrix_lmn, &[l, m, n]);
491
492 let xyz = Self::adapt_xyz_to_d65(xyz[0], xyz[1], xyz[2], ¶ms.white_point);
494 Self::from_xyz(xyz[0], xyz[1], xyz[2])
495 }
496
497 pub fn from_cie_def(d: f64, e: f64, f: f64, params: &CieDefParams) -> Self {
499 let (m1, m2, m3) = (params.m1, params.m2, params.m3);
500 if m1 < 2 || m2 < 2 || m3 < 2 {
501 return Self::from_gray(0.0);
502 }
503
504 let d_range = params.range_def[1] - params.range_def[0];
505 let e_range = params.range_def[3] - params.range_def[2];
506 let f_range = params.range_def[5] - params.range_def[4];
507
508 let di = if d_range > 0.0 {
509 ((d - params.range_def[0]) / d_range * (m1 - 1) as f64).clamp(0.0, (m1 - 1) as f64)
510 } else {
511 0.0
512 };
513 let ei = if e_range > 0.0 {
514 ((e - params.range_def[2]) / e_range * (m2 - 1) as f64).clamp(0.0, (m2 - 1) as f64)
515 } else {
516 0.0
517 };
518 let fi = if f_range > 0.0 {
519 ((f - params.range_def[4]) / f_range * (m3 - 1) as f64).clamp(0.0, (m3 - 1) as f64)
520 } else {
521 0.0
522 };
523
524 let di0 = (di as usize).min(m1 - 2);
525 let ei0 = (ei as usize).min(m2 - 2);
526 let fi0 = (fi as usize).min(m3 - 2);
527 let di1 = di0 + 1;
528 let ei1 = ei0 + 1;
529 let fi1 = fi0 + 1;
530 let dd = di - di0 as f64;
531 let de = ei - ei0 as f64;
532 let df = fi - fi0 as f64;
533
534 let stride_e = m3;
535 let stride_d = m2 * m3;
536
537 let mut abc = [0.0f64; 3];
538 for (ch, table) in [¶ms.a_table, ¶ms.b_table, ¶ms.c_table]
539 .iter()
540 .enumerate()
541 {
542 let c000 = table[di0 * stride_d + ei0 * stride_e + fi0];
543 let c001 = table[di0 * stride_d + ei0 * stride_e + fi1];
544 let c010 = table[di0 * stride_d + ei1 * stride_e + fi0];
545 let c011 = table[di0 * stride_d + ei1 * stride_e + fi1];
546 let c100 = table[di1 * stride_d + ei0 * stride_e + fi0];
547 let c101 = table[di1 * stride_d + ei0 * stride_e + fi1];
548 let c110 = table[di1 * stride_d + ei1 * stride_e + fi0];
549 let c111 = table[di1 * stride_d + ei1 * stride_e + fi1];
550
551 let c00 = c000 * (1.0 - df) + c001 * df;
552 let c01 = c010 * (1.0 - df) + c011 * df;
553 let c10 = c100 * (1.0 - df) + c101 * df;
554 let c11 = c110 * (1.0 - df) + c111 * df;
555
556 let c0 = c00 * (1.0 - de) + c01 * de;
557 let c1 = c10 * (1.0 - de) + c11 * de;
558
559 abc[ch] = c0 * (1.0 - dd) + c1 * dd;
560 }
561
562 Self::from_cie_abc(abc[0], abc[1], abc[2], ¶ms.abc_params)
563 }
564
565 pub fn from_cie_defg(d: f64, e: f64, f: f64, g: f64, params: &CieDefgParams) -> Self {
567 let (m1, m2, m3, m4) = (params.m1, params.m2, params.m3, params.m4);
568 if m1 == 0 || m2 == 0 || m3 == 0 || m4 == 0 {
569 return Self::from_gray(0.0);
570 }
571
572 let d_range = params.range_defg[1] - params.range_defg[0];
573 let e_range = params.range_defg[3] - params.range_defg[2];
574 let f_range = params.range_defg[5] - params.range_defg[4];
575 let g_range = params.range_defg[7] - params.range_defg[6];
576
577 let di = if d_range > 0.0 {
578 ((d - params.range_defg[0]) / d_range * (m1 - 1) as f64 + 0.5) as usize
579 } else {
580 0
581 }
582 .min(m1 - 1);
583 let ei = if e_range > 0.0 {
584 ((e - params.range_defg[2]) / e_range * (m2 - 1) as f64 + 0.5) as usize
585 } else {
586 0
587 }
588 .min(m2 - 1);
589 let fi = if f_range > 0.0 {
590 ((f - params.range_defg[4]) / f_range * (m3 - 1) as f64 + 0.5) as usize
591 } else {
592 0
593 }
594 .min(m3 - 1);
595 let gi = if g_range > 0.0 {
596 ((g - params.range_defg[6]) / g_range * (m4 - 1) as f64 + 0.5) as usize
597 } else {
598 0
599 }
600 .min(m4 - 1);
601
602 let idx = di * m2 * m3 * m4 + ei * m3 * m4 + fi * m4 + gi;
603 if idx >= params.a_table.len() {
604 return Self::from_gray(0.0);
605 }
606
607 Self::from_cie_abc(
608 params.a_table[idx],
609 params.b_table[idx],
610 params.c_table[idx],
611 ¶ms.abc_params,
612 )
613 }
614}
615
616impl Default for DeviceColor {
617 fn default() -> Self {
618 Self::black()
619 }
620}
621
622#[derive(Clone, Debug)]
624pub struct CieAbcParams {
625 pub range_abc: [f64; 6],
626 pub decode_abc: Option<[Vec<f64>; 3]>,
627 pub matrix_abc: [f64; 9],
628 pub range_lmn: [f64; 6],
629 pub decode_lmn: Option<[Vec<f64>; 3]>,
630 pub matrix_lmn: [f64; 9],
631 pub white_point: [f64; 3],
632}
633
634impl Default for CieAbcParams {
635 fn default() -> Self {
636 Self {
637 range_abc: [0.0, 1.0, 0.0, 1.0, 0.0, 1.0],
638 decode_abc: None,
639 matrix_abc: [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0],
640 range_lmn: [0.0, 1.0, 0.0, 1.0, 0.0, 1.0],
641 decode_lmn: None,
642 matrix_lmn: [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0],
643 white_point: [0.9505, 1.0, 1.089],
644 }
645 }
646}
647
648#[derive(Clone, Debug)]
650pub struct CieAParams {
651 pub range_a: [f64; 2],
652 pub decode_a: Option<Vec<f64>>,
653 pub matrix_a: [f64; 3],
654 pub range_lmn: [f64; 6],
655 pub decode_lmn: Option<[Vec<f64>; 3]>,
656 pub matrix_lmn: [f64; 9],
657 pub white_point: [f64; 3],
658}
659
660impl Default for CieAParams {
661 fn default() -> Self {
662 Self {
663 range_a: [0.0, 1.0],
664 decode_a: None,
665 matrix_a: [1.0, 1.0, 1.0],
666 range_lmn: [0.0, 1.0, 0.0, 1.0, 0.0, 1.0],
667 decode_lmn: None,
668 matrix_lmn: [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0],
669 white_point: [0.9505, 1.0, 1.089],
670 }
671 }
672}
673
674#[derive(Clone, Debug)]
676pub struct CieDefParams {
677 pub range_def: [f64; 6],
678 pub m1: usize,
679 pub m2: usize,
680 pub m3: usize,
681 pub a_table: Vec<f64>,
682 pub b_table: Vec<f64>,
683 pub c_table: Vec<f64>,
684 pub abc_params: CieAbcParams,
685}
686
687#[derive(Clone, Debug)]
689pub struct CieDefgParams {
690 pub range_defg: [f64; 8],
691 pub m1: usize,
692 pub m2: usize,
693 pub m3: usize,
694 pub m4: usize,
695 pub a_table: Vec<f64>,
696 pub b_table: Vec<f64>,
697 pub c_table: Vec<f64>,
698 pub abc_params: CieAbcParams,
699}
700
701#[cfg(test)]
702mod tests {
703 use super::*;
704
705 #[test]
706 fn test_color_from_gray() {
707 let c = DeviceColor::from_gray(0.5);
708 assert!((c.r - 0.5).abs() < 1e-10);
709 assert!((c.g - 0.5).abs() < 1e-10);
710 assert!((c.b - 0.5).abs() < 1e-10);
711 }
712
713 #[test]
714 fn test_color_from_cmyk() {
715 let c = DeviceColor::from_cmyk(1.0, 0.0, 0.0, 0.0);
716 assert!((c.r - 0.0).abs() < 1e-10);
717 assert!((c.g - 1.0).abs() < 1e-10);
718 assert!((c.b - 1.0).abs() < 1e-10);
719 }
720
721 #[test]
722 fn test_color_from_hsb() {
723 let c = DeviceColor::from_hsb(0.0, 1.0, 1.0);
724 assert!((c.r - 1.0).abs() < 1e-10);
725 assert!((c.g - 0.0).abs() < 1e-10);
726 assert!((c.b - 0.0).abs() < 1e-10);
727
728 let c = DeviceColor::from_hsb(1.0 / 3.0, 1.0, 1.0);
729 assert!((c.r - 0.0).abs() < 1e-10);
730 assert!((c.g - 1.0).abs() < 1e-10);
731 assert!((c.b - 0.0).abs() < 1e-10);
732 }
733
734 #[test]
735 fn test_color_gray_roundtrip() {
736 let c = DeviceColor::from_gray(0.5);
737 let gray = c.to_gray();
738 assert!((gray - 0.5).abs() < 1e-10);
739 }
740
741 #[test]
742 fn test_color_hsb_roundtrip() {
743 let c = DeviceColor::from_hsb(0.6, 0.8, 0.9);
744 let (h, s, b) = c.to_hsb();
745 assert!((h - 0.6).abs() < 0.01);
746 assert!((s - 0.8).abs() < 0.01);
747 assert!((b - 0.9).abs() < 0.01);
748 }
749
750 #[test]
751 fn test_linecap_from_i32() {
752 assert_eq!(LineCap::from_i32(0), Some(LineCap::Butt));
753 assert_eq!(LineCap::from_i32(1), Some(LineCap::Round));
754 assert_eq!(LineCap::from_i32(2), Some(LineCap::Square));
755 assert_eq!(LineCap::from_i32(3), None);
756 }
757
758 #[test]
759 fn test_linejoin_from_i32() {
760 assert_eq!(LineJoin::from_i32(0), Some(LineJoin::Miter));
761 assert_eq!(LineJoin::from_i32(1), Some(LineJoin::Round));
762 assert_eq!(LineJoin::from_i32(2), Some(LineJoin::Bevel));
763 assert_eq!(LineJoin::from_i32(3), None);
764 }
765}