1use alloc::vec;
9use alloc::vec::Vec;
10use core::cmp::min;
11
12use crate::policy::{AlphaPolicy, ConvertOptions, DepthPolicy, LumaCoefficients};
13use crate::{
14 AlphaMode, ChannelLayout, ChannelType, ColorPrimaries, ConvertError, PixelDescriptor,
15 TransferFunction,
16};
17use whereat::{At, ResultAtExt};
18
19#[derive(Clone, Debug)]
24pub struct ConvertPlan {
25 pub(crate) from: PixelDescriptor,
26 pub(crate) to: PixelDescriptor,
27 pub(crate) steps: Vec<ConvertStep>,
28}
29
30#[derive(Clone)]
38pub(crate) enum ConvertStep {
39 Identity,
41 SwizzleBgraRgba,
43 RgbToBgra,
47 AddAlpha,
49 DropAlpha,
51 MatteComposite { r: u8, g: u8, b: u8 },
62 GrayToRgb,
64 GrayToRgba,
66 RgbToGray { coefficients: LumaCoefficients },
78 RgbaToGray { coefficients: LumaCoefficients },
81 GrayAlphaToRgba,
83 GrayAlphaToRgb,
85 GrayToGrayAlpha,
87 GrayAlphaToGray,
89 SrgbU8ToLinearF32,
91 LinearF32ToSrgbU8,
93 NaiveU8ToF32,
95 NaiveF32ToU8,
97 U16ToU8,
99 U8ToU16,
101 U16ToF32,
103 F32ToU16,
105 F16ToF32,
107 F32ToF16,
109 PqU16ToLinearF32,
111 LinearF32ToPqU16,
113 PqF32ToLinearF32,
115 LinearF32ToPqF32,
117 HlgU16ToLinearF32,
119 LinearF32ToHlgU16,
121 HlgF32ToLinearF32,
123 LinearF32ToHlgF32,
125 SrgbF32ToLinearF32,
127 LinearF32ToSrgbF32,
129 SrgbF32ToLinearF32Extended,
132 LinearF32ToSrgbF32Extended,
134 Bt709F32ToLinearF32,
136 LinearF32ToBt709F32,
138 Gamma22F32ToLinearF32,
141 LinearF32ToGamma22F32,
143 StraightToPremul,
145 PremulToStraight,
147 LinearRgbToOklab,
149 OklabToLinearRgb,
151 LinearRgbaToOklaba,
153 OklabaToLinearRgba,
155 GamutMatrixRgbF32([f32; 9]),
161 GamutMatrixRgbaF32([f32; 9]),
163 FusedSrgbU8GamutRgb([f32; 9]),
167 FusedSrgbU8GamutRgba([f32; 9]),
169 FusedSrgbU16GamutRgb([f32; 9]),
171 FusedSrgbU8ToLinearF32Rgb([f32; 9]),
174 FusedLinearF32ToSrgbU8Rgb([f32; 9]),
177}
178
179impl core::fmt::Debug for ConvertStep {
180 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
181 match self {
182 Self::Identity => f.write_str("Identity"),
183 Self::SwizzleBgraRgba => f.write_str("SwizzleBgraRgba"),
184 Self::RgbToBgra => f.write_str("RgbToBgra"),
185 Self::AddAlpha => f.write_str("AddAlpha"),
186 Self::DropAlpha => f.write_str("DropAlpha"),
187 Self::MatteComposite { r, g, b } => f
188 .debug_struct("MatteComposite")
189 .field("r", r)
190 .field("g", g)
191 .field("b", b)
192 .finish(),
193 Self::GrayToRgb => f.write_str("GrayToRgb"),
194 Self::GrayToRgba => f.write_str("GrayToRgba"),
195 Self::RgbToGray { coefficients } => f
196 .debug_struct("RgbToGray")
197 .field("coefficients", coefficients)
198 .finish(),
199 Self::RgbaToGray { coefficients } => f
200 .debug_struct("RgbaToGray")
201 .field("coefficients", coefficients)
202 .finish(),
203 Self::GrayAlphaToRgba => f.write_str("GrayAlphaToRgba"),
204 Self::GrayAlphaToRgb => f.write_str("GrayAlphaToRgb"),
205 Self::GrayToGrayAlpha => f.write_str("GrayToGrayAlpha"),
206 Self::GrayAlphaToGray => f.write_str("GrayAlphaToGray"),
207 Self::SrgbU8ToLinearF32 => f.write_str("SrgbU8ToLinearF32"),
208 Self::LinearF32ToSrgbU8 => f.write_str("LinearF32ToSrgbU8"),
209 Self::NaiveU8ToF32 => f.write_str("NaiveU8ToF32"),
210 Self::NaiveF32ToU8 => f.write_str("NaiveF32ToU8"),
211 Self::U16ToU8 => f.write_str("U16ToU8"),
212 Self::U8ToU16 => f.write_str("U8ToU16"),
213 Self::U16ToF32 => f.write_str("U16ToF32"),
214 Self::F32ToU16 => f.write_str("F32ToU16"),
215 Self::F16ToF32 => f.write_str("F16ToF32"),
216 Self::F32ToF16 => f.write_str("F32ToF16"),
217 Self::PqU16ToLinearF32 => f.write_str("PqU16ToLinearF32"),
218 Self::LinearF32ToPqU16 => f.write_str("LinearF32ToPqU16"),
219 Self::PqF32ToLinearF32 => f.write_str("PqF32ToLinearF32"),
220 Self::LinearF32ToPqF32 => f.write_str("LinearF32ToPqF32"),
221 Self::HlgU16ToLinearF32 => f.write_str("HlgU16ToLinearF32"),
222 Self::LinearF32ToHlgU16 => f.write_str("LinearF32ToHlgU16"),
223 Self::HlgF32ToLinearF32 => f.write_str("HlgF32ToLinearF32"),
224 Self::LinearF32ToHlgF32 => f.write_str("LinearF32ToHlgF32"),
225 Self::SrgbF32ToLinearF32 => f.write_str("SrgbF32ToLinearF32"),
226 Self::LinearF32ToSrgbF32 => f.write_str("LinearF32ToSrgbF32"),
227 Self::SrgbF32ToLinearF32Extended => f.write_str("SrgbF32ToLinearF32Extended"),
228 Self::LinearF32ToSrgbF32Extended => f.write_str("LinearF32ToSrgbF32Extended"),
229 Self::Bt709F32ToLinearF32 => f.write_str("Bt709F32ToLinearF32"),
230 Self::LinearF32ToBt709F32 => f.write_str("LinearF32ToBt709F32"),
231 Self::Gamma22F32ToLinearF32 => f.write_str("Gamma22F32ToLinearF32"),
232 Self::LinearF32ToGamma22F32 => f.write_str("LinearF32ToGamma22F32"),
233 Self::StraightToPremul => f.write_str("StraightToPremul"),
234 Self::PremulToStraight => f.write_str("PremulToStraight"),
235 Self::LinearRgbToOklab => f.write_str("LinearRgbToOklab"),
236 Self::OklabToLinearRgb => f.write_str("OklabToLinearRgb"),
237 Self::LinearRgbaToOklaba => f.write_str("LinearRgbaToOklaba"),
238 Self::OklabaToLinearRgba => f.write_str("OklabaToLinearRgba"),
239 Self::GamutMatrixRgbF32(m) => f.debug_tuple("GamutMatrixRgbF32").field(m).finish(),
240 Self::GamutMatrixRgbaF32(m) => f.debug_tuple("GamutMatrixRgbaF32").field(m).finish(),
241 Self::FusedSrgbU8GamutRgb(m) => f.debug_tuple("FusedSrgbU8GamutRgb").field(m).finish(),
242 Self::FusedSrgbU8GamutRgba(m) => {
243 f.debug_tuple("FusedSrgbU8GamutRgba").field(m).finish()
244 }
245 Self::FusedSrgbU16GamutRgb(m) => {
246 f.debug_tuple("FusedSrgbU16GamutRgb").field(m).finish()
247 }
248 Self::FusedSrgbU8ToLinearF32Rgb(m) => {
249 f.debug_tuple("FusedSrgbU8ToLinearF32Rgb").field(m).finish()
250 }
251 Self::FusedLinearF32ToSrgbU8Rgb(m) => {
252 f.debug_tuple("FusedLinearF32ToSrgbU8Rgb").field(m).finish()
253 }
254 }
255 }
256}
257
258fn assert_not_cmyk(desc: &PixelDescriptor) {
263 assert!(
264 desc.color_model() != crate::ColorModel::Cmyk,
265 "CMYK pixel data cannot be processed by zenpixels-convert. \
266 Use a CMS (e.g., moxcms) with an ICC profile for CMYK↔RGB conversion."
267 );
268}
269
270impl ConvertPlan {
271 #[track_caller]
280 pub fn new(from: PixelDescriptor, to: PixelDescriptor) -> Result<Self, At<ConvertError>> {
281 assert_not_cmyk(&from);
282 assert_not_cmyk(&to);
283 if from == to {
284 return Ok(Self {
285 from,
286 to,
287 steps: vec![ConvertStep::Identity],
288 });
289 }
290
291 let mut steps = Vec::with_capacity(3);
292
293 let need_depth_change = from.channel_type() != to.channel_type();
302 let need_layout_change = from.layout() != to.layout();
303 let need_alpha_change =
304 from.alpha() != to.alpha() && from.alpha().is_some() && to.alpha().is_some();
305
306 let need_depth_or_tf = need_depth_change || from.transfer() != to.transfer();
310
311 if need_layout_change {
313 let src_ch = from.layout().channels();
320 let dst_ch = to.layout().channels();
321 let involves_oklab =
322 matches!(from.layout(), ChannelLayout::Oklab | ChannelLayout::OklabA)
323 || matches!(to.layout(), ChannelLayout::Oklab | ChannelLayout::OklabA);
324
325 if involves_oklab && from.primaries == ColorPrimaries::Unknown {
327 return Err(whereat::at!(ConvertError::NoPath { from, to }));
328 }
329
330 let depth_first = need_depth_or_tf
331 && (dst_ch > src_ch || (involves_oklab && from.channel_type() != ChannelType::F32));
332
333 if depth_first {
334 steps.extend(
336 depth_steps(
337 from.channel_type(),
338 to.channel_type(),
339 from.transfer(),
340 to.transfer(),
341 )
342 .map_err(|e| whereat::at!(e))?,
343 );
344 steps.extend(layout_steps(from.layout(), to.layout()));
345 } else {
346 steps.extend(layout_steps(from.layout(), to.layout()));
348 if need_depth_or_tf {
349 steps.extend(
350 depth_steps(
351 from.channel_type(),
352 to.channel_type(),
353 from.transfer(),
354 to.transfer(),
355 )
356 .map_err(|e| whereat::at!(e))?,
357 );
358 }
359 }
360 } else if need_depth_or_tf {
361 steps.extend(
362 depth_steps(
363 from.channel_type(),
364 to.channel_type(),
365 from.transfer(),
366 to.transfer(),
367 )
368 .map_err(|e| whereat::at!(e))?,
369 );
370 }
371
372 if need_alpha_change {
374 match (from.alpha(), to.alpha()) {
375 (Some(AlphaMode::Straight), Some(AlphaMode::Premultiplied)) => {
376 steps.push(ConvertStep::StraightToPremul);
377 }
378 (Some(AlphaMode::Premultiplied), Some(AlphaMode::Straight)) => {
379 steps.push(ConvertStep::PremulToStraight);
380 }
381 _ => {}
382 }
383 }
384
385 let need_primaries = from.primaries != to.primaries
388 && from.primaries != ColorPrimaries::Unknown
389 && to.primaries != ColorPrimaries::Unknown;
390
391 if need_primaries
392 && let Some(matrix) = crate::gamut::conversion_matrix(from.primaries, to.primaries)
393 {
394 let flat = [
396 matrix[0][0],
397 matrix[0][1],
398 matrix[0][2],
399 matrix[1][0],
400 matrix[1][1],
401 matrix[1][2],
402 matrix[2][0],
403 matrix[2][1],
404 matrix[2][2],
405 ];
406
407 let mut goes_through_linear = false;
410 {
411 let mut desc = from;
412 for step in &steps {
413 desc = intermediate_desc(desc, step);
414 if desc.channel_type() == ChannelType::F32
415 && desc.transfer() == TransferFunction::Linear
416 {
417 goes_through_linear = true;
418 }
419 }
420 }
421
422 if goes_through_linear {
423 let mut insert_pos = 0;
427 let mut desc = from;
428 for (i, step) in steps.iter().enumerate() {
429 desc = intermediate_desc(desc, step);
430 if desc.channel_type() == ChannelType::F32
431 && desc.transfer() == TransferFunction::Linear
432 {
433 insert_pos = i + 1;
434 break;
435 }
436 }
437 let gamut_step = if desc.layout().has_alpha() {
438 ConvertStep::GamutMatrixRgbaF32(flat)
439 } else {
440 ConvertStep::GamutMatrixRgbF32(flat)
441 };
442 steps.insert(insert_pos, gamut_step);
443 } else {
444 let has_alpha = from.layout().has_alpha() || to.layout().has_alpha();
447 let mut desc = from;
449 for step in &steps {
450 desc = intermediate_desc(desc, step);
451 }
452 let gamut_step = if desc.layout().has_alpha() || has_alpha {
453 ConvertStep::GamutMatrixRgbaF32(flat)
454 } else {
455 ConvertStep::GamutMatrixRgbF32(flat)
456 };
457
458 let linearize = match desc.transfer() {
461 TransferFunction::Srgb => ConvertStep::SrgbF32ToLinearF32,
462 TransferFunction::Bt709 => ConvertStep::Bt709F32ToLinearF32,
463 TransferFunction::Pq => ConvertStep::PqF32ToLinearF32,
464 TransferFunction::Hlg => ConvertStep::HlgF32ToLinearF32,
465 TransferFunction::Gamma22 => ConvertStep::Gamma22F32ToLinearF32,
466 TransferFunction::Linear => ConvertStep::Identity,
467 _ => ConvertStep::SrgbF32ToLinearF32, };
469 let to_target_tf = match to.transfer() {
470 TransferFunction::Srgb => ConvertStep::LinearF32ToSrgbF32,
471 TransferFunction::Bt709 => ConvertStep::LinearF32ToBt709F32,
472 TransferFunction::Pq => ConvertStep::LinearF32ToPqF32,
473 TransferFunction::Hlg => ConvertStep::LinearF32ToHlgF32,
474 TransferFunction::Gamma22 => ConvertStep::LinearF32ToGamma22F32,
475 TransferFunction::Linear => ConvertStep::Identity,
476 _ => ConvertStep::LinearF32ToSrgbF32, };
478
479 let mut gamut_steps = Vec::new();
481 if desc.channel_type() == ChannelType::U16
483 && desc.transfer() == TransferFunction::Srgb
484 && to.channel_type() == ChannelType::U16
485 && to.transfer() == TransferFunction::Srgb
486 && !desc.layout().has_alpha()
487 && !to.layout().has_alpha()
488 {
489 gamut_steps.push(ConvertStep::FusedSrgbU16GamutRgb(flat));
491 steps.extend(gamut_steps);
492 if steps.is_empty() {
493 steps.push(ConvertStep::Identity);
494 }
495 fuse_matlut_patterns(&mut steps);
496 return Ok(Self { from, to, steps });
497 }
498 if desc.channel_type() == ChannelType::U8
499 && matches!(desc.transfer(), TransferFunction::Srgb)
500 && to.channel_type() == ChannelType::F32
501 && to.transfer() == TransferFunction::Linear
502 && !desc.layout().has_alpha()
503 && !to.layout().has_alpha()
504 {
505 gamut_steps.push(ConvertStep::FusedSrgbU8ToLinearF32Rgb(flat));
507 steps.extend(gamut_steps);
508 if steps.is_empty() {
509 steps.push(ConvertStep::Identity);
510 }
511 fuse_matlut_patterns(&mut steps);
512 return Ok(Self { from, to, steps });
513 }
514 if desc.channel_type() == ChannelType::F32
515 && desc.transfer() == TransferFunction::Linear
516 && to.channel_type() == ChannelType::U8
517 && to.transfer() == TransferFunction::Srgb
518 && !desc.layout().has_alpha()
519 && !to.layout().has_alpha()
520 {
521 gamut_steps.push(ConvertStep::FusedLinearF32ToSrgbU8Rgb(flat));
523 steps.extend(gamut_steps);
524 if steps.is_empty() {
525 steps.push(ConvertStep::Identity);
526 }
527 fuse_matlut_patterns(&mut steps);
528 return Ok(Self { from, to, steps });
529 }
530 if desc.channel_type() != ChannelType::F32 {
531 if desc.channel_type() == ChannelType::U8
533 && matches!(
534 desc.transfer(),
535 TransferFunction::Srgb
536 | TransferFunction::Bt709
537 | TransferFunction::Unknown
538 )
539 {
540 gamut_steps.push(ConvertStep::SrgbU8ToLinearF32);
541 gamut_steps.push(gamut_step);
543 gamut_steps.push(ConvertStep::LinearF32ToSrgbU8);
544 } else if desc.channel_type() == ChannelType::U16
545 && desc.transfer() == TransferFunction::Pq
546 {
547 gamut_steps.push(ConvertStep::PqU16ToLinearF32);
548 gamut_steps.push(gamut_step);
549 gamut_steps.push(ConvertStep::LinearF32ToPqU16);
550 } else if desc.channel_type() == ChannelType::U16
551 && desc.transfer() == TransferFunction::Hlg
552 {
553 gamut_steps.push(ConvertStep::HlgU16ToLinearF32);
554 gamut_steps.push(gamut_step);
555 gamut_steps.push(ConvertStep::LinearF32ToHlgU16);
556 } else {
557 gamut_steps.push(ConvertStep::NaiveU8ToF32);
559 if !matches!(linearize, ConvertStep::Identity) {
560 gamut_steps.push(linearize);
561 }
562 gamut_steps.push(gamut_step);
563 if !matches!(to_target_tf, ConvertStep::Identity) {
564 gamut_steps.push(to_target_tf);
565 }
566 gamut_steps.push(ConvertStep::NaiveF32ToU8);
567 }
568 } else {
569 if !matches!(linearize, ConvertStep::Identity) {
571 gamut_steps.push(linearize);
572 }
573 gamut_steps.push(gamut_step);
574 if !matches!(to_target_tf, ConvertStep::Identity) {
575 gamut_steps.push(to_target_tf);
576 }
577 }
578
579 steps.extend(gamut_steps);
580 }
581 }
582
583 if steps.is_empty() {
584 steps.push(ConvertStep::Identity);
586 }
587
588 fuse_matlut_patterns(&mut steps);
591
592 Ok(Self { from, to, steps })
593 }
594
595 #[track_caller]
606 pub fn new_explicit(
607 from: PixelDescriptor,
608 to: PixelDescriptor,
609 options: &ConvertOptions,
610 ) -> Result<Self, At<ConvertError>> {
611 assert_not_cmyk(&from);
612 assert_not_cmyk(&to);
613 let drops_alpha = from.alpha().is_some() && to.alpha().is_none();
615 if drops_alpha && options.alpha_policy == AlphaPolicy::Forbid {
616 return Err(whereat::at!(ConvertError::AlphaRemovalForbidden));
617 }
618
619 let reduces_depth = crate::negotiate::channel_bits(from.channel_type())
624 > crate::negotiate::channel_bits(to.channel_type());
625 if reduces_depth && options.depth_policy == DepthPolicy::Forbid {
626 return Err(whereat::at!(ConvertError::DepthReductionForbidden));
627 }
628
629 let src_is_rgb = matches!(
631 from.layout(),
632 ChannelLayout::Rgb | ChannelLayout::Rgba | ChannelLayout::Bgra
633 );
634 let dst_is_gray = matches!(to.layout(), ChannelLayout::Gray | ChannelLayout::GrayAlpha);
635 if src_is_rgb && dst_is_gray && options.luma.is_none() {
636 return Err(whereat::at!(ConvertError::RgbToGray));
637 }
638
639 let mut plan = Self::new(from, to).at()?;
640
641 if drops_alpha && let AlphaPolicy::CompositeOnto { r, g, b } = options.alpha_policy {
661 let src_is_premul = from.alpha() == Some(AlphaMode::Premultiplied);
662 let mut idx = 0;
663 while idx < plan.steps.len() {
664 if matches!(plan.steps[idx], ConvertStep::DropAlpha) {
665 plan.steps[idx] = ConvertStep::MatteComposite { r, g, b };
666 if src_is_premul {
667 plan.steps.insert(idx, ConvertStep::PremulToStraight);
668 idx += 1;
669 }
670 }
671 idx += 1;
672 }
673 }
674
675 if !options.clip_out_of_gamut {
680 for step in &mut plan.steps {
681 match step {
682 ConvertStep::SrgbF32ToLinearF32 => {
683 *step = ConvertStep::SrgbF32ToLinearF32Extended;
684 }
685 ConvertStep::LinearF32ToSrgbF32 => {
686 *step = ConvertStep::LinearF32ToSrgbF32Extended;
687 }
688 _ => {}
689 }
690 }
691 }
692
693 let user_luma = options.luma.unwrap_or(LumaCoefficients::Bt709);
699 for step in &mut plan.steps {
700 match step {
701 ConvertStep::RgbToGray { coefficients }
702 | ConvertStep::RgbaToGray { coefficients } => {
703 *coefficients = user_luma;
704 }
705 _ => {}
706 }
707 }
708
709 Ok(plan)
710 }
711
712 pub(crate) fn identity(from: PixelDescriptor, to: PixelDescriptor) -> Self {
718 Self {
719 from,
720 to,
721 steps: vec![ConvertStep::Identity],
722 }
723 }
724
725 pub fn compose(&self, other: &Self) -> Option<Self> {
734 if self.to != other.from {
735 return None;
736 }
737
738 let mut steps = self.steps.clone();
739
740 for step in &other.steps {
742 if matches!(step, ConvertStep::Identity) {
743 continue;
744 }
745 steps.push(step.clone());
746 }
747
748 let mut changed = true;
750 while changed {
751 changed = false;
752 let mut i = 0;
753 while i + 1 < steps.len() {
754 if are_inverse(&steps[i], &steps[i + 1]) {
755 steps.remove(i + 1);
756 steps.remove(i);
757 changed = true;
758 } else {
760 i += 1;
761 }
762 }
763 }
764
765 if steps.is_empty() {
767 steps.push(ConvertStep::Identity);
768 }
769
770 if steps.len() > 1 {
772 steps.retain(|s| !matches!(s, ConvertStep::Identity));
773 if steps.is_empty() {
774 steps.push(ConvertStep::Identity);
775 }
776 }
777
778 Some(Self {
779 from: self.from,
780 to: other.to,
781 steps,
782 })
783 }
784
785 #[must_use]
787 pub fn is_identity(&self) -> bool {
788 self.steps.len() == 1 && matches!(self.steps[0], ConvertStep::Identity)
789 }
790
791 pub(crate) fn max_intermediate_bpp(&self) -> usize {
795 let mut desc = self.from;
796 let mut max_bpp = desc.bytes_per_pixel();
797 for step in &self.steps {
798 desc = intermediate_desc(desc, step);
799 max_bpp = max_bpp.max(desc.bytes_per_pixel());
800 }
801 max_bpp
802 }
803
804 pub fn from(&self) -> PixelDescriptor {
806 self.from
807 }
808
809 pub fn to(&self) -> PixelDescriptor {
811 self.to
812 }
813}
814
815fn layout_steps(from: ChannelLayout, to: ChannelLayout) -> Vec<ConvertStep> {
820 if from == to {
821 return Vec::new();
822 }
823 match (from, to) {
824 (ChannelLayout::Bgra, ChannelLayout::Rgba) | (ChannelLayout::Rgba, ChannelLayout::Bgra) => {
825 vec![ConvertStep::SwizzleBgraRgba]
826 }
827 (ChannelLayout::Rgb, ChannelLayout::Rgba) => vec![ConvertStep::AddAlpha],
828 (ChannelLayout::Rgb, ChannelLayout::Bgra) => {
829 vec![ConvertStep::RgbToBgra]
832 }
833 (ChannelLayout::Rgba, ChannelLayout::Rgb) => vec![ConvertStep::DropAlpha],
834 (ChannelLayout::Bgra, ChannelLayout::Rgb) => {
835 vec![ConvertStep::SwizzleBgraRgba, ConvertStep::DropAlpha]
837 }
838 (ChannelLayout::Gray, ChannelLayout::Rgb) => vec![ConvertStep::GrayToRgb],
839 (ChannelLayout::Gray, ChannelLayout::Rgba) => vec![ConvertStep::GrayToRgba],
840 (ChannelLayout::Gray, ChannelLayout::Bgra) => {
841 vec![ConvertStep::GrayToRgba, ConvertStep::SwizzleBgraRgba]
843 }
844 (ChannelLayout::Rgb, ChannelLayout::Gray) => vec![ConvertStep::RgbToGray {
845 coefficients: LumaCoefficients::Bt709,
846 }],
847 (ChannelLayout::Rgba, ChannelLayout::Gray) => vec![ConvertStep::RgbaToGray {
848 coefficients: LumaCoefficients::Bt709,
849 }],
850 (ChannelLayout::Bgra, ChannelLayout::Gray) => {
851 vec![
853 ConvertStep::SwizzleBgraRgba,
854 ConvertStep::RgbaToGray {
855 coefficients: LumaCoefficients::Bt709,
856 },
857 ]
858 }
859 (ChannelLayout::GrayAlpha, ChannelLayout::Rgba) => vec![ConvertStep::GrayAlphaToRgba],
860 (ChannelLayout::GrayAlpha, ChannelLayout::Bgra) => {
861 vec![ConvertStep::GrayAlphaToRgba, ConvertStep::SwizzleBgraRgba]
863 }
864 (ChannelLayout::GrayAlpha, ChannelLayout::Rgb) => vec![ConvertStep::GrayAlphaToRgb],
865 (ChannelLayout::Gray, ChannelLayout::GrayAlpha) => vec![ConvertStep::GrayToGrayAlpha],
866 (ChannelLayout::GrayAlpha, ChannelLayout::Gray) => vec![ConvertStep::GrayAlphaToGray],
867
868 (ChannelLayout::Rgb, ChannelLayout::Oklab) => vec![ConvertStep::LinearRgbToOklab],
870 (ChannelLayout::Oklab, ChannelLayout::Rgb) => vec![ConvertStep::OklabToLinearRgb],
871 (ChannelLayout::Rgba, ChannelLayout::OklabA) => vec![ConvertStep::LinearRgbaToOklaba],
872 (ChannelLayout::OklabA, ChannelLayout::Rgba) => vec![ConvertStep::OklabaToLinearRgba],
873
874 (ChannelLayout::Rgb, ChannelLayout::OklabA) => {
876 vec![ConvertStep::AddAlpha, ConvertStep::LinearRgbaToOklaba]
877 }
878 (ChannelLayout::OklabA, ChannelLayout::Rgb) => {
879 vec![ConvertStep::OklabaToLinearRgba, ConvertStep::DropAlpha]
880 }
881 (ChannelLayout::Oklab, ChannelLayout::Rgba) => {
882 vec![ConvertStep::OklabToLinearRgb, ConvertStep::AddAlpha]
883 }
884 (ChannelLayout::Rgba, ChannelLayout::Oklab) => {
885 vec![ConvertStep::DropAlpha, ConvertStep::LinearRgbToOklab]
886 }
887
888 (ChannelLayout::Bgra, ChannelLayout::OklabA) => {
890 vec![
891 ConvertStep::SwizzleBgraRgba,
892 ConvertStep::LinearRgbaToOklaba,
893 ]
894 }
895 (ChannelLayout::OklabA, ChannelLayout::Bgra) => {
896 vec![
897 ConvertStep::OklabaToLinearRgba,
898 ConvertStep::SwizzleBgraRgba,
899 ]
900 }
901 (ChannelLayout::Bgra, ChannelLayout::Oklab) => {
902 vec![
903 ConvertStep::SwizzleBgraRgba,
904 ConvertStep::DropAlpha,
905 ConvertStep::LinearRgbToOklab,
906 ]
907 }
908 (ChannelLayout::Oklab, ChannelLayout::Bgra) => {
909 vec![
910 ConvertStep::OklabToLinearRgb,
911 ConvertStep::AddAlpha,
912 ConvertStep::SwizzleBgraRgba,
913 ]
914 }
915
916 (ChannelLayout::Gray, ChannelLayout::Oklab) => {
918 vec![ConvertStep::GrayToRgb, ConvertStep::LinearRgbToOklab]
919 }
920 (ChannelLayout::Oklab, ChannelLayout::Gray) => {
921 vec![
922 ConvertStep::OklabToLinearRgb,
923 ConvertStep::RgbToGray {
924 coefficients: LumaCoefficients::Bt709,
925 },
926 ]
927 }
928 (ChannelLayout::Gray, ChannelLayout::OklabA) => {
929 vec![ConvertStep::GrayToRgba, ConvertStep::LinearRgbaToOklaba]
930 }
931 (ChannelLayout::OklabA, ChannelLayout::Gray) => {
932 vec![
933 ConvertStep::OklabaToLinearRgba,
934 ConvertStep::RgbaToGray {
935 coefficients: LumaCoefficients::Bt709,
936 },
937 ]
938 }
939 (ChannelLayout::GrayAlpha, ChannelLayout::OklabA) => {
940 vec![
941 ConvertStep::GrayAlphaToRgba,
942 ConvertStep::LinearRgbaToOklaba,
943 ]
944 }
945 (ChannelLayout::OklabA, ChannelLayout::GrayAlpha) => {
946 vec![
949 ConvertStep::OklabaToLinearRgba,
950 ConvertStep::RgbaToGray {
951 coefficients: LumaCoefficients::Bt709,
952 },
953 ConvertStep::GrayToGrayAlpha,
954 ]
955 }
956 (ChannelLayout::GrayAlpha, ChannelLayout::Oklab) => {
957 vec![ConvertStep::GrayAlphaToRgb, ConvertStep::LinearRgbToOklab]
958 }
959 (ChannelLayout::Oklab, ChannelLayout::GrayAlpha) => {
960 vec![
961 ConvertStep::OklabToLinearRgb,
962 ConvertStep::RgbToGray {
963 coefficients: LumaCoefficients::Bt709,
964 },
965 ConvertStep::GrayToGrayAlpha,
966 ]
967 }
968
969 (ChannelLayout::Oklab, ChannelLayout::OklabA) => vec![ConvertStep::AddAlpha],
971 (ChannelLayout::OklabA, ChannelLayout::Oklab) => vec![ConvertStep::DropAlpha],
972
973 _ => Vec::new(), }
975}
976
977fn f32_linearize_step(tf: TransferFunction) -> Option<ConvertStep> {
980 match tf {
981 TransferFunction::Linear => None,
982 TransferFunction::Srgb => Some(ConvertStep::SrgbF32ToLinearF32),
983 TransferFunction::Bt709 => Some(ConvertStep::Bt709F32ToLinearF32),
984 TransferFunction::Pq => Some(ConvertStep::PqF32ToLinearF32),
985 TransferFunction::Hlg => Some(ConvertStep::HlgF32ToLinearF32),
986 TransferFunction::Gamma22 => Some(ConvertStep::Gamma22F32ToLinearF32),
987 TransferFunction::Unknown => None,
988 _ => None,
989 }
990}
991
992fn f32_encode_step(tf: TransferFunction) -> Option<ConvertStep> {
995 match tf {
996 TransferFunction::Linear => None,
997 TransferFunction::Srgb => Some(ConvertStep::LinearF32ToSrgbF32),
998 TransferFunction::Bt709 => Some(ConvertStep::LinearF32ToBt709F32),
999 TransferFunction::Pq => Some(ConvertStep::LinearF32ToPqF32),
1000 TransferFunction::Hlg => Some(ConvertStep::LinearF32ToHlgF32),
1001 TransferFunction::Gamma22 => Some(ConvertStep::LinearF32ToGamma22F32),
1002 TransferFunction::Unknown => None,
1003 _ => None,
1004 }
1005}
1006
1007fn f32_tf_pair_steps(from: TransferFunction, to: TransferFunction) -> Vec<ConvertStep> {
1015 if from == to || from == TransferFunction::Unknown || to == TransferFunction::Unknown {
1016 return Vec::new();
1017 }
1018 let mut steps = Vec::with_capacity(2);
1019 if let Some(s) = f32_linearize_step(from) {
1020 steps.push(s);
1021 }
1022 if let Some(s) = f32_encode_step(to) {
1023 steps.push(s);
1024 }
1025 steps
1026}
1027
1028fn to_f32_step(ct: ChannelType) -> ConvertStep {
1031 match ct {
1032 ChannelType::U8 => ConvertStep::NaiveU8ToF32,
1033 ChannelType::U16 => ConvertStep::U16ToF32,
1034 ChannelType::F16 => ConvertStep::F16ToF32,
1035 _ => unreachable!("to_f32_step called with F32 or unsupported channel type"),
1036 }
1037}
1038
1039fn f32_to_depth_step(ct: ChannelType) -> ConvertStep {
1041 match ct {
1042 ChannelType::U8 => ConvertStep::NaiveF32ToU8,
1043 ChannelType::U16 => ConvertStep::F32ToU16,
1044 ChannelType::F16 => ConvertStep::F32ToF16,
1045 _ => unreachable!("f32_to_depth_step called with F32 or unsupported channel type"),
1046 }
1047}
1048
1049fn depth_steps(
1057 from: ChannelType,
1058 to: ChannelType,
1059 from_tf: TransferFunction,
1060 to_tf: TransferFunction,
1061) -> Result<Vec<ConvertStep>, ConvertError> {
1062 if from == to && from_tf == to_tf {
1063 return Ok(Vec::new());
1064 }
1065
1066 if from == to && from == ChannelType::F32 {
1068 return Ok(f32_tf_pair_steps(from_tf, to_tf));
1069 }
1070
1071 if from == to && from != ChannelType::F32 {
1080 if from_tf == TransferFunction::Unknown || to_tf == TransferFunction::Unknown {
1081 return Ok(Vec::new());
1082 }
1083 let mut steps = Vec::with_capacity(4);
1084 steps.push(to_f32_step(from));
1085 steps.extend(f32_tf_pair_steps(from_tf, to_tf));
1086 steps.push(f32_to_depth_step(to));
1087 return Ok(steps);
1088 }
1089
1090 match (from, to) {
1091 (ChannelType::U8, ChannelType::F32) => {
1092 if from_tf == TransferFunction::Srgb && to_tf == TransferFunction::Linear {
1096 Ok(vec![ConvertStep::SrgbU8ToLinearF32])
1097 } else if from_tf == to_tf {
1098 Ok(vec![ConvertStep::NaiveU8ToF32])
1099 } else {
1100 let mut steps = Vec::with_capacity(3);
1105 steps.push(ConvertStep::NaiveU8ToF32);
1106 steps.extend(f32_tf_pair_steps(from_tf, to_tf));
1107 Ok(steps)
1108 }
1109 }
1110 (ChannelType::F32, ChannelType::U8) => {
1111 if from_tf == TransferFunction::Linear && to_tf == TransferFunction::Srgb {
1113 Ok(vec![ConvertStep::LinearF32ToSrgbU8])
1114 } else if from_tf == to_tf {
1115 Ok(vec![ConvertStep::NaiveF32ToU8])
1116 } else {
1117 let mut steps = f32_tf_pair_steps(from_tf, to_tf);
1119 steps.push(ConvertStep::NaiveF32ToU8);
1120 Ok(steps)
1121 }
1122 }
1123 (ChannelType::U16, ChannelType::F32) => {
1124 match (from_tf, to_tf) {
1126 (TransferFunction::Pq, TransferFunction::Linear) => {
1127 Ok(vec![ConvertStep::PqU16ToLinearF32])
1128 }
1129 (TransferFunction::Hlg, TransferFunction::Linear) => {
1130 Ok(vec![ConvertStep::HlgU16ToLinearF32])
1131 }
1132 (a, b) if a == b => Ok(vec![ConvertStep::U16ToF32]),
1133 _ => {
1134 let mut steps = Vec::with_capacity(3);
1135 steps.push(ConvertStep::U16ToF32);
1136 steps.extend(f32_tf_pair_steps(from_tf, to_tf));
1137 Ok(steps)
1138 }
1139 }
1140 }
1141 (ChannelType::F32, ChannelType::U16) => {
1142 match (from_tf, to_tf) {
1144 (TransferFunction::Linear, TransferFunction::Pq) => {
1145 Ok(vec![ConvertStep::LinearF32ToPqU16])
1146 }
1147 (TransferFunction::Linear, TransferFunction::Hlg) => {
1148 Ok(vec![ConvertStep::LinearF32ToHlgU16])
1149 }
1150 (a, b) if a == b => Ok(vec![ConvertStep::F32ToU16]),
1151 _ => {
1152 let mut steps = f32_tf_pair_steps(from_tf, to_tf);
1153 steps.push(ConvertStep::F32ToU16);
1154 Ok(steps)
1155 }
1156 }
1157 }
1158 (ChannelType::U16, ChannelType::U8) => {
1159 if from_tf == TransferFunction::Pq && to_tf == TransferFunction::Srgb {
1161 Ok(vec![
1162 ConvertStep::PqU16ToLinearF32,
1163 ConvertStep::LinearF32ToSrgbU8,
1164 ])
1165 } else if from_tf == TransferFunction::Hlg && to_tf == TransferFunction::Srgb {
1166 Ok(vec![
1167 ConvertStep::HlgU16ToLinearF32,
1168 ConvertStep::LinearF32ToSrgbU8,
1169 ])
1170 } else if from_tf == to_tf {
1171 Ok(vec![ConvertStep::U16ToU8])
1172 } else {
1173 let mut steps = Vec::with_capacity(4);
1174 steps.push(ConvertStep::U16ToF32);
1175 steps.extend(f32_tf_pair_steps(from_tf, to_tf));
1176 steps.push(ConvertStep::NaiveF32ToU8);
1177 Ok(steps)
1178 }
1179 }
1180 (ChannelType::U8, ChannelType::U16) => {
1181 if from_tf == to_tf {
1182 Ok(vec![ConvertStep::U8ToU16])
1183 } else {
1184 let mut steps = Vec::with_capacity(4);
1185 steps.push(ConvertStep::NaiveU8ToF32);
1186 steps.extend(f32_tf_pair_steps(from_tf, to_tf));
1187 steps.push(ConvertStep::F32ToU16);
1188 Ok(steps)
1189 }
1190 }
1191 (ChannelType::F16, ChannelType::F32) => {
1194 let mut steps = Vec::with_capacity(3);
1195 steps.push(ConvertStep::F16ToF32);
1196 if from_tf != to_tf {
1197 steps.extend(f32_tf_pair_steps(from_tf, to_tf));
1198 }
1199 Ok(steps)
1200 }
1201 (ChannelType::F32, ChannelType::F16) => {
1202 let mut steps = Vec::with_capacity(3);
1203 if from_tf != to_tf {
1204 steps.extend(f32_tf_pair_steps(from_tf, to_tf));
1205 }
1206 steps.push(ConvertStep::F32ToF16);
1207 Ok(steps)
1208 }
1209 (ChannelType::F16, ChannelType::U8) => {
1210 let mut steps = Vec::with_capacity(4);
1211 steps.push(ConvertStep::F16ToF32);
1212 if from_tf == TransferFunction::Linear && to_tf == TransferFunction::Srgb {
1213 steps.push(ConvertStep::LinearF32ToSrgbU8);
1214 } else if from_tf == to_tf {
1215 steps.push(ConvertStep::NaiveF32ToU8);
1216 } else {
1217 steps.extend(f32_tf_pair_steps(from_tf, to_tf));
1218 steps.push(ConvertStep::NaiveF32ToU8);
1219 }
1220 Ok(steps)
1221 }
1222 (ChannelType::U8, ChannelType::F16) => {
1223 let mut steps = Vec::with_capacity(4);
1224 if from_tf == TransferFunction::Srgb && to_tf == TransferFunction::Linear {
1225 steps.push(ConvertStep::SrgbU8ToLinearF32);
1226 } else if from_tf == to_tf {
1227 steps.push(ConvertStep::NaiveU8ToF32);
1228 } else {
1229 steps.push(ConvertStep::NaiveU8ToF32);
1230 steps.extend(f32_tf_pair_steps(from_tf, to_tf));
1231 }
1232 steps.push(ConvertStep::F32ToF16);
1233 Ok(steps)
1234 }
1235 (ChannelType::F16, ChannelType::U16) => {
1236 let mut steps = Vec::with_capacity(4);
1237 steps.push(ConvertStep::F16ToF32);
1238 if from_tf == TransferFunction::Linear && to_tf == TransferFunction::Pq {
1239 steps.push(ConvertStep::LinearF32ToPqU16);
1240 } else if from_tf == TransferFunction::Linear && to_tf == TransferFunction::Hlg {
1241 steps.push(ConvertStep::LinearF32ToHlgU16);
1242 } else if from_tf == to_tf {
1243 steps.push(ConvertStep::F32ToU16);
1244 } else {
1245 steps.extend(f32_tf_pair_steps(from_tf, to_tf));
1246 steps.push(ConvertStep::F32ToU16);
1247 }
1248 Ok(steps)
1249 }
1250 (ChannelType::U16, ChannelType::F16) => {
1251 let mut steps = Vec::with_capacity(4);
1252 if from_tf == TransferFunction::Pq && to_tf == TransferFunction::Linear {
1253 steps.push(ConvertStep::PqU16ToLinearF32);
1254 } else if from_tf == TransferFunction::Hlg && to_tf == TransferFunction::Linear {
1255 steps.push(ConvertStep::HlgU16ToLinearF32);
1256 } else if from_tf == to_tf {
1257 steps.push(ConvertStep::U16ToF32);
1258 } else {
1259 steps.push(ConvertStep::U16ToF32);
1260 steps.extend(f32_tf_pair_steps(from_tf, to_tf));
1261 }
1262 steps.push(ConvertStep::F32ToF16);
1263 Ok(steps)
1264 }
1265 _ => Err(ConvertError::NoPath {
1266 from: PixelDescriptor::new(from, ChannelLayout::Rgb, None, from_tf),
1267 to: PixelDescriptor::new(to, ChannelLayout::Rgb, None, to_tf),
1268 }),
1269 }
1270}
1271
1272pub(crate) struct ConvertScratch {
1282 buf: Vec<u32>,
1286}
1287
1288impl ConvertScratch {
1289 pub(crate) fn new() -> Self {
1291 Self { buf: Vec::new() }
1292 }
1293
1294 fn ensure_capacity(&mut self, plan: &ConvertPlan, width: u32) {
1297 let half_bytes = (width as usize) * plan.max_intermediate_bpp();
1298 let total_u32 = (half_bytes * 2).div_ceil(4);
1299 if self.buf.len() < total_u32 {
1300 self.buf.resize(total_u32, 0);
1301 }
1302 }
1303}
1304
1305impl core::fmt::Debug for ConvertScratch {
1306 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1307 f.debug_struct("ConvertScratch")
1308 .field("capacity", &self.buf.capacity())
1309 .finish()
1310 }
1311}
1312
1313pub fn convert_row(plan: &ConvertPlan, src: &[u8], dst: &mut [u8], width: u32) {
1319 if plan.is_identity() {
1320 let len = min(src.len(), dst.len());
1321 dst[..len].copy_from_slice(&src[..len]);
1322 return;
1323 }
1324
1325 if plan.steps.len() == 1 {
1326 apply_step_u8(&plan.steps[0], src, dst, width, plan.from, plan.to);
1327 return;
1328 }
1329
1330 let mut scratch = ConvertScratch::new();
1332 convert_row_buffered(plan, src, dst, width, &mut scratch);
1333}
1334
1335pub(crate) fn convert_row_buffered(
1340 plan: &ConvertPlan,
1341 src: &[u8],
1342 dst: &mut [u8],
1343 width: u32,
1344 scratch: &mut ConvertScratch,
1345) {
1346 if plan.is_identity() {
1347 let len = min(src.len(), dst.len());
1348 dst[..len].copy_from_slice(&src[..len]);
1349 return;
1350 }
1351
1352 if plan.steps.len() == 1 {
1353 apply_step_u8(&plan.steps[0], src, dst, width, plan.from, plan.to);
1354 return;
1355 }
1356
1357 scratch.ensure_capacity(plan, width);
1358
1359 let buf_bytes: &mut [u8] = bytemuck::cast_slice_mut(&mut scratch.buf);
1360 let half = buf_bytes.len() / 2;
1361 let (buf_a, buf_b) = buf_bytes.split_at_mut(half);
1362
1363 let num_steps = plan.steps.len();
1364 let mut current_desc = plan.from;
1365
1366 for (i, step) in plan.steps.iter().enumerate() {
1367 let is_last = i == num_steps - 1;
1368 let next_desc = if is_last {
1369 plan.to
1370 } else {
1371 intermediate_desc(current_desc, step)
1372 };
1373
1374 let next_len = (width as usize) * next_desc.bytes_per_pixel();
1375 let curr_len = (width as usize) * current_desc.bytes_per_pixel();
1376
1377 if i % 2 == 0 {
1381 let input = if i == 0 { src } else { &buf_b[..curr_len] };
1382 if is_last {
1383 apply_step_u8(step, input, dst, width, current_desc, next_desc);
1384 } else {
1385 apply_step_u8(
1386 step,
1387 input,
1388 &mut buf_a[..next_len],
1389 width,
1390 current_desc,
1391 next_desc,
1392 );
1393 }
1394 } else {
1395 let input = &buf_a[..curr_len];
1396 if is_last {
1397 apply_step_u8(step, input, dst, width, current_desc, next_desc);
1398 } else {
1399 apply_step_u8(
1400 step,
1401 input,
1402 &mut buf_b[..next_len],
1403 width,
1404 current_desc,
1405 next_desc,
1406 );
1407 }
1408 }
1409
1410 current_desc = next_desc;
1411 }
1412}
1413
1414fn fuse_matlut_patterns(steps: &mut Vec<ConvertStep>) {
1418 let mut i = 0;
1419 while i + 2 < steps.len() {
1420 let rewrite = match (&steps[i], &steps[i + 1], &steps[i + 2]) {
1421 (
1422 ConvertStep::SrgbU8ToLinearF32,
1423 ConvertStep::GamutMatrixRgbF32(m),
1424 ConvertStep::LinearF32ToSrgbU8,
1425 ) => Some(ConvertStep::FusedSrgbU8GamutRgb(*m)),
1426 (
1427 ConvertStep::SrgbU8ToLinearF32,
1428 ConvertStep::GamutMatrixRgbaF32(m),
1429 ConvertStep::LinearF32ToSrgbU8,
1430 ) => Some(ConvertStep::FusedSrgbU8GamutRgba(*m)),
1431 _ => None,
1432 };
1433 if let Some(fused) = rewrite {
1434 steps[i] = fused;
1435 steps.drain(i + 1..i + 3);
1436 continue;
1437 }
1438 i += 1;
1439 }
1440}
1441
1442fn are_inverse(a: &ConvertStep, b: &ConvertStep) -> bool {
1443 matches!(
1444 (a, b),
1445 (ConvertStep::SwizzleBgraRgba, ConvertStep::SwizzleBgraRgba)
1447 | (ConvertStep::AddAlpha, ConvertStep::DropAlpha)
1449 | (ConvertStep::SrgbF32ToLinearF32, ConvertStep::LinearF32ToSrgbF32)
1451 | (ConvertStep::LinearF32ToSrgbF32, ConvertStep::SrgbF32ToLinearF32)
1452 | (ConvertStep::PqF32ToLinearF32, ConvertStep::LinearF32ToPqF32)
1453 | (ConvertStep::LinearF32ToPqF32, ConvertStep::PqF32ToLinearF32)
1454 | (ConvertStep::HlgF32ToLinearF32, ConvertStep::LinearF32ToHlgF32)
1455 | (ConvertStep::LinearF32ToHlgF32, ConvertStep::HlgF32ToLinearF32)
1456 | (ConvertStep::Bt709F32ToLinearF32, ConvertStep::LinearF32ToBt709F32)
1457 | (ConvertStep::LinearF32ToBt709F32, ConvertStep::Bt709F32ToLinearF32)
1458 | (ConvertStep::Gamma22F32ToLinearF32, ConvertStep::LinearF32ToGamma22F32)
1459 | (ConvertStep::LinearF32ToGamma22F32, ConvertStep::Gamma22F32ToLinearF32)
1460 | (ConvertStep::StraightToPremul, ConvertStep::PremulToStraight)
1462 | (ConvertStep::PremulToStraight, ConvertStep::StraightToPremul)
1463 | (ConvertStep::LinearRgbToOklab, ConvertStep::OklabToLinearRgb)
1465 | (ConvertStep::OklabToLinearRgb, ConvertStep::LinearRgbToOklab)
1466 | (ConvertStep::LinearRgbaToOklaba, ConvertStep::OklabaToLinearRgba)
1467 | (ConvertStep::OklabaToLinearRgba, ConvertStep::LinearRgbaToOklaba)
1468 | (ConvertStep::NaiveU8ToF32, ConvertStep::NaiveF32ToU8)
1470 | (ConvertStep::NaiveF32ToU8, ConvertStep::NaiveU8ToF32)
1471 | (ConvertStep::U8ToU16, ConvertStep::U16ToU8)
1472 | (ConvertStep::U16ToU8, ConvertStep::U8ToU16)
1473 | (ConvertStep::U16ToF32, ConvertStep::F32ToU16)
1474 | (ConvertStep::F32ToU16, ConvertStep::U16ToF32)
1475 | (ConvertStep::F16ToF32, ConvertStep::F32ToF16)
1476 | (ConvertStep::F32ToF16, ConvertStep::F16ToF32)
1477 | (ConvertStep::SrgbU8ToLinearF32, ConvertStep::LinearF32ToSrgbU8)
1479 | (ConvertStep::LinearF32ToSrgbU8, ConvertStep::SrgbU8ToLinearF32)
1480 | (ConvertStep::PqU16ToLinearF32, ConvertStep::LinearF32ToPqU16)
1481 | (ConvertStep::LinearF32ToPqU16, ConvertStep::PqU16ToLinearF32)
1482 | (ConvertStep::HlgU16ToLinearF32, ConvertStep::LinearF32ToHlgU16)
1483 | (ConvertStep::LinearF32ToHlgU16, ConvertStep::HlgU16ToLinearF32)
1484 | (ConvertStep::SrgbF32ToLinearF32Extended, ConvertStep::LinearF32ToSrgbF32Extended)
1486 | (ConvertStep::LinearF32ToSrgbF32Extended, ConvertStep::SrgbF32ToLinearF32Extended)
1487 )
1488}
1489
1490fn intermediate_desc(current: PixelDescriptor, step: &ConvertStep) -> PixelDescriptor {
1492 match step {
1493 ConvertStep::Identity => current,
1494 ConvertStep::SwizzleBgraRgba => {
1495 let new_layout = match current.layout() {
1496 ChannelLayout::Bgra => ChannelLayout::Rgba,
1497 ChannelLayout::Rgba => ChannelLayout::Bgra,
1498 other => other,
1499 };
1500 PixelDescriptor::new(
1501 current.channel_type(),
1502 new_layout,
1503 current.alpha(),
1504 current.transfer(),
1505 )
1506 }
1507 ConvertStep::AddAlpha => PixelDescriptor::new(
1508 current.channel_type(),
1509 ChannelLayout::Rgba,
1510 Some(AlphaMode::Straight),
1511 current.transfer(),
1512 ),
1513 ConvertStep::RgbToBgra => PixelDescriptor::new(
1514 current.channel_type(),
1515 ChannelLayout::Bgra,
1516 Some(AlphaMode::Straight),
1517 current.transfer(),
1518 ),
1519 ConvertStep::DropAlpha | ConvertStep::MatteComposite { .. } => PixelDescriptor::new(
1520 current.channel_type(),
1521 ChannelLayout::Rgb,
1522 None,
1523 current.transfer(),
1524 ),
1525 ConvertStep::GrayToRgb => PixelDescriptor::new(
1526 current.channel_type(),
1527 ChannelLayout::Rgb,
1528 None,
1529 current.transfer(),
1530 ),
1531 ConvertStep::GrayToRgba => PixelDescriptor::new(
1532 current.channel_type(),
1533 ChannelLayout::Rgba,
1534 Some(AlphaMode::Straight),
1535 current.transfer(),
1536 ),
1537 ConvertStep::RgbToGray { .. } | ConvertStep::RgbaToGray { .. } => PixelDescriptor::new(
1538 current.channel_type(),
1539 ChannelLayout::Gray,
1540 None,
1541 current.transfer(),
1542 ),
1543 ConvertStep::GrayAlphaToRgba => PixelDescriptor::new(
1544 current.channel_type(),
1545 ChannelLayout::Rgba,
1546 current.alpha(),
1547 current.transfer(),
1548 ),
1549 ConvertStep::GrayAlphaToRgb => PixelDescriptor::new(
1550 current.channel_type(),
1551 ChannelLayout::Rgb,
1552 None,
1553 current.transfer(),
1554 ),
1555 ConvertStep::GrayToGrayAlpha => PixelDescriptor::new(
1556 current.channel_type(),
1557 ChannelLayout::GrayAlpha,
1558 Some(AlphaMode::Straight),
1559 current.transfer(),
1560 ),
1561 ConvertStep::GrayAlphaToGray => PixelDescriptor::new(
1562 current.channel_type(),
1563 ChannelLayout::Gray,
1564 None,
1565 current.transfer(),
1566 ),
1567 ConvertStep::SrgbU8ToLinearF32
1568 | ConvertStep::NaiveU8ToF32
1569 | ConvertStep::U16ToF32
1570 | ConvertStep::PqU16ToLinearF32
1571 | ConvertStep::HlgU16ToLinearF32
1572 | ConvertStep::PqF32ToLinearF32
1573 | ConvertStep::HlgF32ToLinearF32
1574 | ConvertStep::SrgbF32ToLinearF32
1575 | ConvertStep::SrgbF32ToLinearF32Extended
1576 | ConvertStep::Bt709F32ToLinearF32
1577 | ConvertStep::Gamma22F32ToLinearF32 => PixelDescriptor::new(
1578 ChannelType::F32,
1579 current.layout(),
1580 current.alpha(),
1581 TransferFunction::Linear,
1582 ),
1583 ConvertStep::LinearF32ToSrgbU8 | ConvertStep::NaiveF32ToU8 | ConvertStep::U16ToU8 => {
1584 PixelDescriptor::new(
1585 ChannelType::U8,
1586 current.layout(),
1587 current.alpha(),
1588 TransferFunction::Srgb,
1589 )
1590 }
1591 ConvertStep::U8ToU16 => PixelDescriptor::new(
1592 ChannelType::U16,
1593 current.layout(),
1594 current.alpha(),
1595 current.transfer(),
1596 ),
1597 ConvertStep::F32ToU16 | ConvertStep::LinearF32ToPqU16 | ConvertStep::LinearF32ToHlgU16 => {
1598 let tf = match step {
1599 ConvertStep::LinearF32ToPqU16 => TransferFunction::Pq,
1600 ConvertStep::LinearF32ToHlgU16 => TransferFunction::Hlg,
1601 _ => current.transfer(),
1602 };
1603 PixelDescriptor::new(ChannelType::U16, current.layout(), current.alpha(), tf)
1604 }
1605 ConvertStep::LinearF32ToPqF32 => PixelDescriptor::new(
1606 ChannelType::F32,
1607 current.layout(),
1608 current.alpha(),
1609 TransferFunction::Pq,
1610 ),
1611 ConvertStep::LinearF32ToHlgF32 => PixelDescriptor::new(
1612 ChannelType::F32,
1613 current.layout(),
1614 current.alpha(),
1615 TransferFunction::Hlg,
1616 ),
1617 ConvertStep::LinearF32ToSrgbF32 | ConvertStep::LinearF32ToSrgbF32Extended => {
1618 PixelDescriptor::new(
1619 ChannelType::F32,
1620 current.layout(),
1621 current.alpha(),
1622 TransferFunction::Srgb,
1623 )
1624 }
1625 ConvertStep::LinearF32ToBt709F32 => PixelDescriptor::new(
1626 ChannelType::F32,
1627 current.layout(),
1628 current.alpha(),
1629 TransferFunction::Bt709,
1630 ),
1631 ConvertStep::LinearF32ToGamma22F32 => PixelDescriptor::new(
1632 ChannelType::F32,
1633 current.layout(),
1634 current.alpha(),
1635 TransferFunction::Gamma22,
1636 ),
1637 ConvertStep::StraightToPremul => PixelDescriptor::new(
1638 current.channel_type(),
1639 current.layout(),
1640 Some(AlphaMode::Premultiplied),
1641 current.transfer(),
1642 ),
1643 ConvertStep::PremulToStraight => PixelDescriptor::new(
1644 current.channel_type(),
1645 current.layout(),
1646 Some(AlphaMode::Straight),
1647 current.transfer(),
1648 ),
1649 ConvertStep::LinearRgbToOklab => PixelDescriptor::new(
1650 ChannelType::F32,
1651 ChannelLayout::Oklab,
1652 None,
1653 TransferFunction::Unknown,
1654 )
1655 .with_primaries(current.primaries),
1656 ConvertStep::OklabToLinearRgb => PixelDescriptor::new(
1657 ChannelType::F32,
1658 ChannelLayout::Rgb,
1659 None,
1660 TransferFunction::Linear,
1661 )
1662 .with_primaries(current.primaries),
1663 ConvertStep::LinearRgbaToOklaba => PixelDescriptor::new(
1664 ChannelType::F32,
1665 ChannelLayout::OklabA,
1666 Some(AlphaMode::Straight),
1667 TransferFunction::Unknown,
1668 )
1669 .with_primaries(current.primaries),
1670 ConvertStep::OklabaToLinearRgba => PixelDescriptor::new(
1671 ChannelType::F32,
1672 ChannelLayout::Rgba,
1673 current.alpha(),
1674 TransferFunction::Linear,
1675 )
1676 .with_primaries(current.primaries),
1677
1678 ConvertStep::GamutMatrixRgbF32(_) => PixelDescriptor::new(
1683 ChannelType::F32,
1684 current.layout(),
1685 current.alpha(),
1686 TransferFunction::Linear,
1687 ),
1688 ConvertStep::GamutMatrixRgbaF32(_) => PixelDescriptor::new(
1689 ChannelType::F32,
1690 current.layout(),
1691 current.alpha(),
1692 TransferFunction::Linear,
1693 ),
1694 ConvertStep::FusedSrgbU8GamutRgb(_) | ConvertStep::FusedSrgbU8GamutRgba(_) => {
1696 PixelDescriptor::new(
1697 ChannelType::U8,
1698 current.layout(),
1699 current.alpha(),
1700 TransferFunction::Srgb,
1701 )
1702 }
1703 ConvertStep::FusedSrgbU16GamutRgb(_) => PixelDescriptor::new(
1704 ChannelType::U16,
1705 current.layout(),
1706 current.alpha(),
1707 TransferFunction::Srgb,
1708 ),
1709 ConvertStep::FusedSrgbU8ToLinearF32Rgb(_) => PixelDescriptor::new(
1710 ChannelType::F32,
1711 current.layout(),
1712 current.alpha(),
1713 TransferFunction::Linear,
1714 ),
1715 ConvertStep::FusedLinearF32ToSrgbU8Rgb(_) => PixelDescriptor::new(
1716 ChannelType::U8,
1717 current.layout(),
1718 current.alpha(),
1719 TransferFunction::Srgb,
1720 ),
1721 ConvertStep::F16ToF32 => PixelDescriptor::new(
1723 ChannelType::F32,
1724 current.layout(),
1725 current.alpha(),
1726 current.transfer(),
1727 ),
1728 ConvertStep::F32ToF16 => PixelDescriptor::new(
1729 ChannelType::F16,
1730 current.layout(),
1731 current.alpha(),
1732 current.transfer(),
1733 ),
1734 }
1735}
1736
1737#[path = "convert_kernels.rs"]
1738mod convert_kernels;
1739use convert_kernels::apply_step_u8;
1740pub(crate) use convert_kernels::{hlg_eotf, hlg_oetf, pq_eotf, pq_oetf};