1use zenpixels::{ColorPrimaries, TransferFunction};
8
9use crate::convert::{hlg_eotf, hlg_oetf, pq_eotf, pq_oetf};
10use crate::gamut::GamutMatrix;
11
12pub trait TransferFunctionExt {
18 #[must_use]
22 fn linearize(&self, v: f32) -> f32;
23
24 #[must_use]
28 fn delinearize(&self, v: f32) -> f32;
29}
30
31impl TransferFunctionExt for TransferFunction {
32 #[allow(unreachable_patterns)]
33 fn linearize(&self, v: f32) -> f32 {
34 match self {
35 Self::Linear | Self::Unknown => v,
36 Self::Srgb => linear_srgb::precise::srgb_to_linear(v),
37 Self::Bt709 => linear_srgb::tf::bt709_to_linear(v),
38 Self::Pq => pq_eotf(v),
39 Self::Hlg => hlg_eotf(v),
40 _ => v,
41 }
42 }
43
44 #[allow(unreachable_patterns)]
45 fn delinearize(&self, v: f32) -> f32 {
46 match self {
47 Self::Linear | Self::Unknown => v,
48 Self::Srgb => linear_srgb::precise::linear_to_srgb(v),
49 Self::Bt709 => linear_srgb::tf::linear_to_bt709(v),
50 Self::Pq => pq_oetf(v),
51 Self::Hlg => hlg_oetf(v),
52 _ => v,
53 }
54 }
55}
56
57#[allow(clippy::wrong_self_convention)]
63pub trait ColorPrimariesExt {
64 fn to_xyz_matrix(&self) -> Option<&'static GamutMatrix>;
68
69 fn from_xyz_matrix(&self) -> Option<&'static GamutMatrix>;
73}
74
75impl ColorPrimariesExt for ColorPrimaries {
76 #[allow(unreachable_patterns)]
77 fn to_xyz_matrix(&self) -> Option<&'static GamutMatrix> {
78 match self {
79 Self::Bt709 => Some(&crate::gamut::BT709_TO_XYZ),
80 Self::DisplayP3 => Some(&crate::gamut::DISPLAY_P3_TO_XYZ),
81 Self::Bt2020 => Some(&crate::gamut::BT2020_TO_XYZ),
82 _ => None,
83 }
84 }
85
86 #[allow(unreachable_patterns)]
87 fn from_xyz_matrix(&self) -> Option<&'static GamutMatrix> {
88 match self {
89 Self::Bt709 => Some(&crate::gamut::XYZ_TO_BT709),
90 Self::DisplayP3 => Some(&crate::gamut::XYZ_TO_DISPLAY_P3),
91 Self::Bt2020 => Some(&crate::gamut::XYZ_TO_BT2020),
92 _ => None,
93 }
94 }
95}
96
97use alloc::sync::Arc;
102use whereat::{At, ResultAtExt};
103use zenpixels::PixelDescriptor;
104use zenpixels::buffer::PixelBuffer;
105use zenpixels::descriptor::{AlphaMode, ChannelLayout, ChannelType};
106
107pub trait PixelBufferConvertExt {
109 fn convert_to(&self, target: PixelDescriptor) -> Result<PixelBuffer, At<crate::ConvertError>>;
116
117 fn try_add_alpha(&self) -> Result<PixelBuffer, At<crate::ConvertError>>;
123
124 fn try_widen_to_u16(&self) -> Result<PixelBuffer, At<crate::ConvertError>>;
126
127 fn try_narrow_to_u8(&self) -> Result<PixelBuffer, At<crate::ConvertError>>;
129
130 fn linearize(&self) -> Result<PixelBuffer, At<crate::ConvertError>>;
137
138 fn delinearize(
147 &self,
148 transfer: TransferFunction,
149 ) -> Result<PixelBuffer, At<crate::ConvertError>>;
150}
151
152#[cfg(feature = "rgb")]
156pub trait PixelBufferConvertTypedExt: PixelBufferConvertExt {
157 fn to_rgb8(&self) -> PixelBuffer<rgb::Rgb<u8>>;
159
160 fn to_rgba8(&self) -> PixelBuffer<rgb::Rgba<u8>>;
162
163 fn to_gray8(&self) -> PixelBuffer<rgb::Gray<u8>>;
165
166 fn to_bgra8(&self) -> PixelBuffer<rgb::alt::BGRA<u8>>;
168}
169
170impl PixelBufferConvertExt for PixelBuffer {
171 #[track_caller]
172 fn convert_to(&self, target: PixelDescriptor) -> Result<PixelBuffer, At<crate::ConvertError>> {
173 let src_desc = self.descriptor();
174 if src_desc == target {
175 let dst_stride = target.aligned_stride(self.width());
177 let total = dst_stride
178 .checked_mul(self.height() as usize)
179 .ok_or_else(|| whereat::at!(crate::ConvertError::AllocationFailed))?;
180 let mut out = alloc::vec![0u8; total];
181 let src_slice = self.as_slice();
182 for y in 0..self.height() {
183 let src_row = src_slice.row(y);
184 let dst_start = y as usize * dst_stride;
185 out[dst_start..dst_start + src_row.len()].copy_from_slice(src_row);
186 }
187 let mut buf = PixelBuffer::from_vec(out, self.width(), self.height(), target)
188 .map_err(|_| whereat::at!(crate::ConvertError::AllocationFailed))?;
189 if let Some(ctx) = self.color_context() {
190 buf = buf.with_color_context(Arc::clone(ctx));
191 }
192 return Ok(buf);
193 }
194
195 let mut converter = crate::RowConverter::new(src_desc, target).at()?;
196
197 let dst_stride = target.aligned_stride(self.width());
198 let total = dst_stride
199 .checked_mul(self.height() as usize)
200 .ok_or_else(|| whereat::at!(crate::ConvertError::AllocationFailed))?;
201 let mut out = alloc::vec![0u8; total];
202
203 let src_slice = self.as_slice();
204 for y in 0..self.height() {
205 let src_row = src_slice.row(y);
206 let dst_start = y as usize * dst_stride;
207 let dst_end = dst_start + dst_stride;
208 converter.convert_row(src_row, &mut out[dst_start..dst_end], self.width());
209 }
210
211 let mut buf = PixelBuffer::from_vec(out, self.width(), self.height(), target)
212 .map_err(|_| whereat::at!(crate::ConvertError::AllocationFailed))?;
213 if let Some(ctx) = self.color_context() {
214 buf = buf.with_color_context(Arc::clone(ctx));
215 }
216 Ok(buf)
217 }
218
219 #[track_caller]
220 fn try_add_alpha(&self) -> Result<PixelBuffer, At<crate::ConvertError>> {
221 let desc = self.descriptor();
222 let target_layout = match desc.layout() {
223 ChannelLayout::Gray => ChannelLayout::GrayAlpha,
224 ChannelLayout::Rgb => ChannelLayout::Rgba,
225 other => other,
226 };
227 let alpha = if target_layout.has_alpha() && desc.alpha().is_none() {
228 Some(AlphaMode::Straight)
229 } else {
230 desc.alpha()
231 };
232 let target =
233 PixelDescriptor::new(desc.channel_type(), target_layout, alpha, desc.transfer());
234 self.convert_to(target)
235 }
236
237 #[track_caller]
238 fn try_widen_to_u16(&self) -> Result<PixelBuffer, At<crate::ConvertError>> {
239 let desc = self.descriptor();
240 let target = PixelDescriptor::new(
241 ChannelType::U16,
242 desc.layout(),
243 desc.alpha(),
244 desc.transfer(),
245 );
246 self.convert_to(target)
247 }
248
249 #[track_caller]
250 fn try_narrow_to_u8(&self) -> Result<PixelBuffer, At<crate::ConvertError>> {
251 let desc = self.descriptor();
252 let target = PixelDescriptor::new(
253 ChannelType::U8,
254 desc.layout(),
255 desc.alpha(),
256 desc.transfer(),
257 );
258 self.convert_to(target)
259 }
260
261 #[track_caller]
262 fn linearize(&self) -> Result<PixelBuffer, At<crate::ConvertError>> {
263 let desc = self.descriptor();
264 let target = PixelDescriptor::new_full(
265 ChannelType::F32,
266 desc.layout(),
267 desc.alpha(),
268 TransferFunction::Linear,
269 desc.primaries,
270 );
271 self.convert_to(target)
272 }
273
274 #[track_caller]
275 fn delinearize(
276 &self,
277 transfer: TransferFunction,
278 ) -> Result<PixelBuffer, At<crate::ConvertError>> {
279 let target = self.descriptor().with_transfer(transfer);
280 self.convert_to(target)
281 }
282}
283
284#[cfg(feature = "rgb")]
285use zenpixels::buffer::Pixel;
286
287#[cfg(feature = "rgb")]
288impl PixelBufferConvertTypedExt for PixelBuffer {
289 fn to_rgb8(&self) -> PixelBuffer<rgb::Rgb<u8>> {
290 convert_to_typed(self, PixelDescriptor::RGB8_SRGB)
291 }
292
293 fn to_rgba8(&self) -> PixelBuffer<rgb::Rgba<u8>> {
294 convert_to_typed(self, PixelDescriptor::RGBA8_SRGB)
295 }
296
297 fn to_gray8(&self) -> PixelBuffer<rgb::Gray<u8>> {
298 convert_to_typed(self, PixelDescriptor::GRAY8_SRGB)
299 }
300
301 fn to_bgra8(&self) -> PixelBuffer<rgb::alt::BGRA<u8>> {
302 convert_to_typed(self, PixelDescriptor::BGRA8_SRGB)
303 }
304}
305
306#[cfg(feature = "rgb")]
308fn convert_to_typed<Q: Pixel>(buf: &PixelBuffer, target: PixelDescriptor) -> PixelBuffer<Q> {
309 use alloc::vec;
310 let mut conv = crate::RowConverter::new(buf.descriptor(), target)
311 .expect("RowConverter: no conversion path");
312 let dst_bpp = target.bytes_per_pixel();
313 let dst_stride = target.aligned_stride(buf.width());
314 let total = dst_stride * buf.height() as usize;
315 let mut out = vec![0u8; total];
316 let src_slice = buf.as_slice();
317 for y in 0..buf.height() {
318 let src_row = src_slice.row(y);
319 let dst_start = y as usize * dst_stride;
320 let dst_end = dst_start + buf.width() as usize * dst_bpp;
321 conv.convert_row(src_row, &mut out[dst_start..dst_end], buf.width());
322 }
323 let erased = PixelBuffer::from_vec(out, buf.width(), buf.height(), target)
326 .expect("convert_to_typed: buffer construction failed");
327 let erased = if let Some(ctx) = buf.color_context() {
329 erased.with_color_context(Arc::clone(ctx))
330 } else {
331 erased
332 };
333 erased
334 .try_typed::<Q>()
335 .expect("convert_to_typed: type mismatch after conversion")
336}
337
338#[cfg(test)]
339mod tests {
340 use super::*;
341
342 #[test]
345 fn srgb_linearize_roundtrip() {
346 let tf = TransferFunction::Srgb;
347 for &v in &[0.0, 0.04045, 0.1, 0.5, 0.73, 1.0] {
348 let lin = tf.linearize(v);
349 let back = tf.delinearize(lin);
350 assert!(
351 (v - back).abs() < 1e-5,
352 "sRGB roundtrip failed for {v}: linearize={lin}, delinearize={back}"
353 );
354 }
355 }
356
357 #[test]
358 fn pq_linearize_roundtrip() {
359 let tf = TransferFunction::Pq;
360 for &v in &[0.0, 0.1, 0.5, 0.75, 1.0] {
363 let lin = tf.linearize(v);
364 let back = tf.delinearize(lin);
365 assert!(
366 (v - back).abs() < 5e-4,
367 "PQ roundtrip failed for {v}: linearize={lin}, delinearize={back}"
368 );
369 }
370 }
371
372 #[test]
373 fn hlg_linearize_roundtrip() {
374 let tf = TransferFunction::Hlg;
375 for &v in &[0.0, 0.1, 0.3, 0.5, 0.8, 1.0] {
376 let lin = tf.linearize(v);
377 let back = tf.delinearize(lin);
378 assert!(
379 (v - back).abs() < 1e-4,
380 "HLG roundtrip failed for {v}: linearize={lin}, delinearize={back}"
381 );
382 }
383 }
384
385 #[test]
386 fn linear_identity() {
387 let tf = TransferFunction::Linear;
388 for &v in &[0.0, 0.5, 1.0] {
389 assert_eq!(tf.linearize(v), v);
390 assert_eq!(tf.delinearize(v), v);
391 }
392 }
393
394 #[test]
397 fn xyz_matrix_availability() {
398 assert!(ColorPrimaries::Bt709.to_xyz_matrix().is_some());
399 assert!(ColorPrimaries::Bt709.from_xyz_matrix().is_some());
400 assert!(ColorPrimaries::DisplayP3.to_xyz_matrix().is_some());
401 assert!(ColorPrimaries::Bt2020.to_xyz_matrix().is_some());
402 assert!(ColorPrimaries::Unknown.to_xyz_matrix().is_none());
403 assert!(ColorPrimaries::Unknown.from_xyz_matrix().is_none());
404 }
405
406 #[test]
407 fn xyz_roundtrip_bt709() {
408 let to = ColorPrimaries::Bt709.to_xyz_matrix().unwrap();
409 let from = ColorPrimaries::Bt709.from_xyz_matrix().unwrap();
410 let rgb = [0.5f32, 0.3, 0.8];
411 let mut v = rgb;
412 crate::gamut::apply_matrix_f32(&mut v, to);
413 crate::gamut::apply_matrix_f32(&mut v, from);
414 for c in 0..3 {
415 assert!(
416 (v[c] - rgb[c]).abs() < 1e-4,
417 "XYZ roundtrip BT.709 ch{c}: {:.6} vs {:.6}",
418 v[c],
419 rgb[c]
420 );
421 }
422 }
423
424 #[test]
427 fn bt709_linearize_roundtrip() {
428 let tf = TransferFunction::Bt709;
429 for &v in &[0.0, 0.04045, 0.1, 0.5, 0.73, 1.0] {
430 let lin = tf.linearize(v);
431 let back = tf.delinearize(lin);
432 assert!(
433 (v - back).abs() < 1e-5,
434 "BT.709 roundtrip failed for {v}: linearize={lin}, delinearize={back}"
435 );
436 }
437 }
438
439 #[test]
440 fn unknown_transfer_identity() {
441 let tf = TransferFunction::Unknown;
442 for &v in &[0.0, 0.25, 0.5, 0.75, 1.0] {
443 assert_eq!(
444 tf.linearize(v),
445 v,
446 "Unknown linearize should be identity for {v}"
447 );
448 assert_eq!(
449 tf.delinearize(v),
450 v,
451 "Unknown delinearize should be identity for {v}"
452 );
453 }
454 }
455
456 use super::PixelBufferConvertExt;
459
460 #[test]
461 fn convert_to_identity() {
462 let data = vec![100u8, 150, 200, 50, 100, 150];
463 let buf = PixelBuffer::from_vec(data.clone(), 2, 1, PixelDescriptor::RGB8_SRGB).unwrap();
464 let out = buf.convert_to(PixelDescriptor::RGB8_SRGB).unwrap();
465 assert_eq!(out.descriptor(), PixelDescriptor::RGB8_SRGB);
466 assert_eq!(out.width(), 2);
467 assert_eq!(out.height(), 1);
468 assert_eq!(&out.as_slice().row(0)[..6], &data[..]);
469 }
470
471 #[test]
472 fn convert_to_rgba8() {
473 let data = vec![100u8, 150, 200, 50, 100, 150];
474 let buf = PixelBuffer::from_vec(data, 2, 1, PixelDescriptor::RGB8_SRGB).unwrap();
475 let out = buf.convert_to(PixelDescriptor::RGBA8_SRGB).unwrap();
476 assert_eq!(out.descriptor(), PixelDescriptor::RGBA8_SRGB);
477 let slice = out.as_slice();
478 let row = slice.row(0);
479 assert_eq!(row[0], 100);
481 assert_eq!(row[1], 150);
482 assert_eq!(row[2], 200);
483 assert_eq!(row[3], 255);
484 assert_eq!(row[4], 50);
486 assert_eq!(row[5], 100);
487 assert_eq!(row[6], 150);
488 assert_eq!(row[7], 255);
489 }
490
491 #[test]
492 fn try_add_alpha_rgb() {
493 let data = vec![100u8, 150, 200, 50, 100, 150];
494 let buf = PixelBuffer::from_vec(data, 2, 1, PixelDescriptor::RGB8_SRGB).unwrap();
495 let out = buf.try_add_alpha().unwrap();
496 assert_eq!(
498 out.descriptor().layout(),
499 zenpixels::descriptor::ChannelLayout::Rgba
500 );
501 let slice = out.as_slice();
502 let row = slice.row(0);
503 assert_eq!(row[3], 255);
504 assert_eq!(row[7], 255);
505 }
506
507 #[test]
508 fn try_widen_to_u16() {
509 let data = vec![100u8, 150, 200, 50, 100, 150];
510 let buf = PixelBuffer::from_vec(data, 2, 1, PixelDescriptor::RGB8_SRGB).unwrap();
511 let out = buf.try_widen_to_u16().unwrap();
512 assert_eq!(
513 out.descriptor().channel_type(),
514 zenpixels::descriptor::ChannelType::U16
515 );
516 let slice = out.as_slice();
517 let row = slice.row(0);
518 for (i, &expected_u8) in [100u8, 150, 200, 50, 100, 150].iter().enumerate() {
520 let lo = row[i * 2];
521 let hi = row[i * 2 + 1];
522 let val = u16::from_le_bytes([lo, hi]);
523 let expected = expected_u8 as u16 * 257;
524 assert_eq!(
525 val, expected,
526 "channel {i}: expected {expected} (u8={expected_u8}*257), got {val}"
527 );
528 }
529 }
530
531 #[test]
532 fn linearize_srgb_to_linear_f32() {
533 let data = vec![128u8, 128, 128, 64, 64, 64];
534 let buf = PixelBuffer::from_vec(data, 2, 1, PixelDescriptor::RGB8_SRGB).unwrap();
535 let lin = buf.linearize().unwrap();
536 assert_eq!(lin.descriptor().transfer(), TransferFunction::Linear);
537 assert_eq!(
538 lin.descriptor().channel_type(),
539 zenpixels::descriptor::ChannelType::F32
540 );
541 assert_eq!(lin.descriptor().primaries, ColorPrimaries::Bt709);
542 let slice = lin.as_slice();
544 let row = slice.row(0);
545 let r = f32::from_le_bytes([row[0], row[1], row[2], row[3]]);
546 assert!(
547 (r - 0.216).abs() < 0.01,
548 "sRGB 128 should linearize to ~0.216, got {r}"
549 );
550 }
551
552 #[test]
553 fn delinearize_linear_to_srgb() {
554 let linear_val: f32 = 0.216;
556 let mut data = vec![0u8; 24]; for i in 0..6 {
558 let bytes = linear_val.to_le_bytes();
559 data[i * 4..i * 4 + 4].copy_from_slice(&bytes);
560 }
561 let buf = PixelBuffer::from_vec(data, 2, 1, PixelDescriptor::RGBF32_LINEAR).unwrap();
562 let srgb = buf.delinearize(TransferFunction::Srgb).unwrap();
563 assert_eq!(srgb.descriptor().transfer(), TransferFunction::Srgb);
564 let slice = srgb.as_slice();
566 let row = slice.row(0);
567 let r = f32::from_le_bytes([row[0], row[1], row[2], row[3]]);
568 assert!(
569 (r - 0.502).abs() < 0.01,
570 "linear 0.216 should delinearize to ~0.502, got {r}"
571 );
572 }
573
574 #[test]
575 fn linearize_delinearize_roundtrip() {
576 let data = vec![100u8, 150, 200, 50, 100, 150];
577 let buf = PixelBuffer::from_vec(data.clone(), 2, 1, PixelDescriptor::RGB8_SRGB).unwrap();
578 let lin = buf.linearize().unwrap();
579 let back = lin.delinearize(TransferFunction::Srgb).unwrap();
581 let slice = back.as_slice();
583 let row = slice.row(0);
584 let r = f32::from_le_bytes([row[0], row[1], row[2], row[3]]);
585 let expected = 100.0 / 255.0;
586 assert!(
587 (r - expected).abs() < 0.005,
588 "roundtrip pixel 0 R: expected ~{expected}, got {r}"
589 );
590 }
591
592 #[test]
593 fn linearize_preserves_alpha() {
594 let data = vec![100u8, 150, 200, 128, 50, 100, 150, 64];
595 let buf = PixelBuffer::from_vec(data, 2, 1, PixelDescriptor::RGBA8_SRGB).unwrap();
596 let lin = buf.linearize().unwrap();
597 assert_eq!(
598 lin.descriptor().layout(),
599 zenpixels::descriptor::ChannelLayout::Rgba
600 );
601 assert!(lin.descriptor().alpha().is_some());
602 }
603
604 #[test]
605 fn linearize_preserves_primaries() {
606 let data = vec![100u8, 150, 200, 50, 100, 150];
607 let desc = PixelDescriptor::RGB8_SRGB.with_primaries(ColorPrimaries::DisplayP3);
608 let buf = PixelBuffer::from_vec(data, 2, 1, desc).unwrap();
609 let lin = buf.linearize().unwrap();
610 assert_eq!(lin.descriptor().primaries, ColorPrimaries::DisplayP3);
611 }
612
613 #[test]
614 fn linearize_already_linear_is_identity() {
615 let val: f32 = 0.5;
616 let mut data = vec![0u8; 12]; for i in 0..3 {
618 data[i * 4..i * 4 + 4].copy_from_slice(&val.to_le_bytes());
619 }
620 let buf = PixelBuffer::from_vec(data, 1, 1, PixelDescriptor::RGBF32_LINEAR).unwrap();
621 let lin = buf.linearize().unwrap();
622 let slice = lin.as_slice();
623 let row = slice.row(0);
624 let r = f32::from_le_bytes([row[0], row[1], row[2], row[3]]);
625 assert!(
626 (r - val).abs() < 1e-6,
627 "already-linear should be identity, got {r}"
628 );
629 }
630
631 #[test]
632 fn try_narrow_to_u8() {
633 let values: [u16; 6] = [
635 100 * 257,
636 150 * 257,
637 200 * 257,
638 50 * 257,
639 100 * 257,
640 150 * 257,
641 ];
642 let mut data = vec![0u8; 12];
643 for (i, &v) in values.iter().enumerate() {
644 let bytes = v.to_le_bytes();
645 data[i * 2] = bytes[0];
646 data[i * 2 + 1] = bytes[1];
647 }
648 let buf = PixelBuffer::from_vec(data, 2, 1, PixelDescriptor::RGB16_SRGB).unwrap();
649 let out = buf.try_narrow_to_u8().unwrap();
650 assert_eq!(
651 out.descriptor().channel_type(),
652 zenpixels::descriptor::ChannelType::U8
653 );
654 let slice = out.as_slice();
655 let row = slice.row(0);
656 assert_eq!(row[0], 100);
657 assert_eq!(row[1], 150);
658 assert_eq!(row[2], 200);
659 assert_eq!(row[3], 50);
660 assert_eq!(row[4], 100);
661 assert_eq!(row[5], 150);
662 }
663
664 #[test]
665 #[cfg(feature = "rgb")]
666 fn to_rgb8() {
667 let data = vec![100u8, 150, 200, 255, 50, 100, 150, 255];
669 let buf = PixelBuffer::from_vec(data, 2, 1, PixelDescriptor::RGBA8_SRGB).unwrap();
670 let typed: PixelBuffer<rgb::Rgb<u8>> = buf.to_rgb8();
671 assert_eq!(typed.width(), 2);
672 assert_eq!(typed.height(), 1);
673 let slice = typed.as_slice();
674 let row = slice.row(0);
675 assert_eq!(row[0], 100);
677 assert_eq!(row[1], 150);
678 assert_eq!(row[2], 200);
679 assert_eq!(row[3], 50);
680 assert_eq!(row[4], 100);
681 assert_eq!(row[5], 150);
682 }
683
684 #[test]
685 #[cfg(feature = "rgb")]
686 fn to_rgba8() {
687 let data = vec![100u8, 150, 200, 50, 100, 150];
688 let buf = PixelBuffer::from_vec(data, 2, 1, PixelDescriptor::RGB8_SRGB).unwrap();
689 let typed: PixelBuffer<rgb::Rgba<u8>> = buf.to_rgba8();
690 assert_eq!(typed.width(), 2);
691 assert_eq!(typed.height(), 1);
692 let slice = typed.as_slice();
693 let row = slice.row(0);
694 assert_eq!(row[0], 100);
696 assert_eq!(row[1], 150);
697 assert_eq!(row[2], 200);
698 assert_eq!(row[3], 255);
699 assert_eq!(row[4], 50);
700 assert_eq!(row[5], 100);
701 assert_eq!(row[6], 150);
702 assert_eq!(row[7], 255);
703 }
704
705 #[test]
706 #[cfg(feature = "rgb")]
707 fn to_gray8() {
708 let data = vec![100u8, 150, 200, 50, 100, 150];
709 let buf = PixelBuffer::from_vec(data, 2, 1, PixelDescriptor::RGB8_SRGB).unwrap();
710 let typed: PixelBuffer<rgb::Gray<u8>> = buf.to_gray8();
711 assert_eq!(typed.width(), 2);
712 assert_eq!(typed.height(), 1);
713 let slice = typed.as_slice();
714 let row = slice.row(0);
715 assert!(row[0] > 0, "gray pixel 0 should be non-zero");
717 assert!(row[1] > 0, "gray pixel 1 should be non-zero");
718 }
719
720 #[test]
721 #[cfg(feature = "rgb")]
722 fn to_bgra8() {
723 let data = vec![100u8, 150, 200, 50, 100, 150];
724 let buf = PixelBuffer::from_vec(data, 2, 1, PixelDescriptor::RGB8_SRGB).unwrap();
725 let typed: PixelBuffer<rgb::alt::BGRA<u8>> = buf.to_bgra8();
726 assert_eq!(typed.width(), 2);
727 assert_eq!(typed.height(), 1);
728 let slice = typed.as_slice();
729 let row = slice.row(0);
730 assert_eq!(row[0], 200);
733 assert_eq!(row[1], 150);
734 assert_eq!(row[2], 100);
735 assert_eq!(row[3], 255);
736 assert_eq!(row[4], 150);
738 assert_eq!(row[5], 100);
739 assert_eq!(row[6], 50);
740 assert_eq!(row[7], 255);
741 }
742}