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 pub(crate) pq_anchor_scale: f32,
36}
37
38#[derive(Clone)]
46pub(crate) enum ConvertStep {
47 Identity,
49 SwizzleBgraRgba,
51 RgbToBgra,
55 AddAlpha,
57 DropAlpha,
59 MatteComposite { r: u8, g: u8, b: u8 },
70 GrayToRgb,
72 GrayToRgba,
74 RgbToGray { coefficients: LumaCoefficients },
86 RgbaToGray { coefficients: LumaCoefficients },
89 GrayAlphaToRgba,
91 GrayAlphaToRgb,
93 GrayToGrayAlpha,
95 GrayAlphaToGray,
97 SrgbU8ToLinearF32,
99 LinearF32ToSrgbU8,
101 NaiveU8ToF32,
103 NaiveF32ToU8,
105 U16ToU8,
107 U8ToU16,
109 U16ToF32,
111 F32ToU16,
113 F16ToF32,
115 F32ToF16,
117 PqU16ToLinearF32,
119 LinearF32ToPqU16,
121 PqF32ToLinearF32,
123 LinearF32ToPqF32,
125 HlgU16ToLinearF32,
127 LinearF32ToHlgU16,
129 HlgF32ToLinearF32,
131 LinearF32ToHlgF32,
133 SrgbF32ToLinearF32,
135 LinearF32ToSrgbF32,
137 SrgbF32ToLinearF32Extended,
140 LinearF32ToSrgbF32Extended,
142 Bt709F32ToLinearF32,
144 LinearF32ToBt709F32,
146 Gamma22F32ToLinearF32,
149 LinearF32ToGamma22F32,
151 StraightToPremul,
153 PremulToStraight,
155 LinearRgbToOklab,
157 OklabToLinearRgb,
159 LinearRgbaToOklaba,
161 OklabaToLinearRgba,
163 GamutMatrixRgbF32([f32; 9]),
169 GamutMatrixRgbaF32([f32; 9]),
171 FusedSrgbU8GamutRgb([f32; 9]),
175 FusedSrgbU8GamutRgba([f32; 9]),
177 FusedSrgbU16GamutRgb([f32; 9]),
179 FusedSrgbU8ToLinearF32Rgb([f32; 9]),
182 FusedLinearF32ToSrgbU8Rgb([f32; 9]),
185}
186
187impl core::fmt::Debug for ConvertStep {
188 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
189 match self {
190 Self::Identity => f.write_str("Identity"),
191 Self::SwizzleBgraRgba => f.write_str("SwizzleBgraRgba"),
192 Self::RgbToBgra => f.write_str("RgbToBgra"),
193 Self::AddAlpha => f.write_str("AddAlpha"),
194 Self::DropAlpha => f.write_str("DropAlpha"),
195 Self::MatteComposite { r, g, b } => f
196 .debug_struct("MatteComposite")
197 .field("r", r)
198 .field("g", g)
199 .field("b", b)
200 .finish(),
201 Self::GrayToRgb => f.write_str("GrayToRgb"),
202 Self::GrayToRgba => f.write_str("GrayToRgba"),
203 Self::RgbToGray { coefficients } => f
204 .debug_struct("RgbToGray")
205 .field("coefficients", coefficients)
206 .finish(),
207 Self::RgbaToGray { coefficients } => f
208 .debug_struct("RgbaToGray")
209 .field("coefficients", coefficients)
210 .finish(),
211 Self::GrayAlphaToRgba => f.write_str("GrayAlphaToRgba"),
212 Self::GrayAlphaToRgb => f.write_str("GrayAlphaToRgb"),
213 Self::GrayToGrayAlpha => f.write_str("GrayToGrayAlpha"),
214 Self::GrayAlphaToGray => f.write_str("GrayAlphaToGray"),
215 Self::SrgbU8ToLinearF32 => f.write_str("SrgbU8ToLinearF32"),
216 Self::LinearF32ToSrgbU8 => f.write_str("LinearF32ToSrgbU8"),
217 Self::NaiveU8ToF32 => f.write_str("NaiveU8ToF32"),
218 Self::NaiveF32ToU8 => f.write_str("NaiveF32ToU8"),
219 Self::U16ToU8 => f.write_str("U16ToU8"),
220 Self::U8ToU16 => f.write_str("U8ToU16"),
221 Self::U16ToF32 => f.write_str("U16ToF32"),
222 Self::F32ToU16 => f.write_str("F32ToU16"),
223 Self::F16ToF32 => f.write_str("F16ToF32"),
224 Self::F32ToF16 => f.write_str("F32ToF16"),
225 Self::PqU16ToLinearF32 => f.write_str("PqU16ToLinearF32"),
226 Self::LinearF32ToPqU16 => f.write_str("LinearF32ToPqU16"),
227 Self::PqF32ToLinearF32 => f.write_str("PqF32ToLinearF32"),
228 Self::LinearF32ToPqF32 => f.write_str("LinearF32ToPqF32"),
229 Self::HlgU16ToLinearF32 => f.write_str("HlgU16ToLinearF32"),
230 Self::LinearF32ToHlgU16 => f.write_str("LinearF32ToHlgU16"),
231 Self::HlgF32ToLinearF32 => f.write_str("HlgF32ToLinearF32"),
232 Self::LinearF32ToHlgF32 => f.write_str("LinearF32ToHlgF32"),
233 Self::SrgbF32ToLinearF32 => f.write_str("SrgbF32ToLinearF32"),
234 Self::LinearF32ToSrgbF32 => f.write_str("LinearF32ToSrgbF32"),
235 Self::SrgbF32ToLinearF32Extended => f.write_str("SrgbF32ToLinearF32Extended"),
236 Self::LinearF32ToSrgbF32Extended => f.write_str("LinearF32ToSrgbF32Extended"),
237 Self::Bt709F32ToLinearF32 => f.write_str("Bt709F32ToLinearF32"),
238 Self::LinearF32ToBt709F32 => f.write_str("LinearF32ToBt709F32"),
239 Self::Gamma22F32ToLinearF32 => f.write_str("Gamma22F32ToLinearF32"),
240 Self::LinearF32ToGamma22F32 => f.write_str("LinearF32ToGamma22F32"),
241 Self::StraightToPremul => f.write_str("StraightToPremul"),
242 Self::PremulToStraight => f.write_str("PremulToStraight"),
243 Self::LinearRgbToOklab => f.write_str("LinearRgbToOklab"),
244 Self::OklabToLinearRgb => f.write_str("OklabToLinearRgb"),
245 Self::LinearRgbaToOklaba => f.write_str("LinearRgbaToOklaba"),
246 Self::OklabaToLinearRgba => f.write_str("OklabaToLinearRgba"),
247 Self::GamutMatrixRgbF32(m) => f.debug_tuple("GamutMatrixRgbF32").field(m).finish(),
248 Self::GamutMatrixRgbaF32(m) => f.debug_tuple("GamutMatrixRgbaF32").field(m).finish(),
249 Self::FusedSrgbU8GamutRgb(m) => f.debug_tuple("FusedSrgbU8GamutRgb").field(m).finish(),
250 Self::FusedSrgbU8GamutRgba(m) => {
251 f.debug_tuple("FusedSrgbU8GamutRgba").field(m).finish()
252 }
253 Self::FusedSrgbU16GamutRgb(m) => {
254 f.debug_tuple("FusedSrgbU16GamutRgb").field(m).finish()
255 }
256 Self::FusedSrgbU8ToLinearF32Rgb(m) => {
257 f.debug_tuple("FusedSrgbU8ToLinearF32Rgb").field(m).finish()
258 }
259 Self::FusedLinearF32ToSrgbU8Rgb(m) => {
260 f.debug_tuple("FusedLinearF32ToSrgbU8Rgb").field(m).finish()
261 }
262 }
263 }
264}
265
266fn assert_not_cmyk(desc: &PixelDescriptor) {
271 assert!(
272 desc.color_model() != crate::ColorModel::Cmyk,
273 "CMYK pixel data cannot be processed by zenpixels-convert. \
274 Use a CMS (e.g., moxcms) with an ICC profile for CMYK↔RGB conversion."
275 );
276}
277
278impl ConvertPlan {
279 fn build(from: PixelDescriptor, to: PixelDescriptor, steps: Vec<ConvertStep>) -> Self {
283 Self {
284 from,
285 to,
286 steps,
287 pq_anchor_scale: 1.0,
288 }
289 }
290
291 #[must_use]
303 pub(crate) fn with_pq_anchor(mut self, anchor: zenpixels::hdr::DiffuseWhite) -> Self {
304 let diffuse_white_nits = f64::from(anchor.nits());
307 const PQ_PEAK_NITS: f64 = 10_000.0;
308 self.pq_anchor_scale = (diffuse_white_nits / PQ_PEAK_NITS) as f32;
309 self
310 }
311
312 #[track_caller]
325 pub fn new(from: PixelDescriptor, to: PixelDescriptor) -> Result<Self, At<ConvertError>> {
326 assert_not_cmyk(&from);
327 assert_not_cmyk(&to);
328 if from == to {
329 return Ok(Self::build(from, to, vec![ConvertStep::Identity]));
330 }
331
332 if from.signal_range != to.signal_range {
341 return Err(whereat::at!(ConvertError::NoPath { from, to }));
342 }
343
344 if matches!(
355 (from.transfer(), to.transfer()),
356 (TransferFunction::Hlg, TransferFunction::Pq)
357 | (TransferFunction::Pq, TransferFunction::Hlg)
358 ) {
359 return Err(whereat::at!(ConvertError::NoPath { from, to }));
360 }
361
362 let mut steps = Vec::with_capacity(3);
363
364 let need_depth_change = from.channel_type() != to.channel_type();
373 let need_layout_change = from.layout() != to.layout();
374 let need_alpha_change =
375 from.alpha() != to.alpha() && from.alpha().is_some() && to.alpha().is_some();
376
377 let need_depth_or_tf = need_depth_change || from.transfer() != to.transfer();
381
382 if need_layout_change {
384 let src_ch = from.layout().channels();
391 let dst_ch = to.layout().channels();
392 let involves_oklab =
393 matches!(from.layout(), ChannelLayout::Oklab | ChannelLayout::OklabA)
394 || matches!(to.layout(), ChannelLayout::Oklab | ChannelLayout::OklabA);
395
396 if involves_oklab && from.primaries == ColorPrimaries::Unknown {
398 return Err(whereat::at!(ConvertError::NoPath { from, to }));
399 }
400
401 let depth_first = need_depth_or_tf
402 && (dst_ch > src_ch || (involves_oklab && from.channel_type() != ChannelType::F32));
403
404 if depth_first {
405 steps.extend(
407 depth_steps(
408 from.channel_type(),
409 to.channel_type(),
410 from.transfer(),
411 to.transfer(),
412 )
413 .map_err(|e| whereat::at!(e))?,
414 );
415 steps.extend(layout_steps(from.layout(), to.layout()));
416 } else {
417 steps.extend(layout_steps(from.layout(), to.layout()));
419 if need_depth_or_tf {
420 steps.extend(
421 depth_steps(
422 from.channel_type(),
423 to.channel_type(),
424 from.transfer(),
425 to.transfer(),
426 )
427 .map_err(|e| whereat::at!(e))?,
428 );
429 }
430 }
431 } else if need_depth_or_tf {
432 steps.extend(
433 depth_steps(
434 from.channel_type(),
435 to.channel_type(),
436 from.transfer(),
437 to.transfer(),
438 )
439 .map_err(|e| whereat::at!(e))?,
440 );
441 }
442
443 if need_alpha_change {
445 match (from.alpha(), to.alpha()) {
446 (Some(AlphaMode::Straight), Some(AlphaMode::Premultiplied)) => {
447 steps.push(ConvertStep::StraightToPremul);
448 }
449 (Some(AlphaMode::Premultiplied), Some(AlphaMode::Straight)) => {
450 steps.push(ConvertStep::PremulToStraight);
451 }
452 _ => {}
453 }
454 }
455
456 let need_primaries = from.primaries != to.primaries
459 && from.primaries != ColorPrimaries::Unknown
460 && to.primaries != ColorPrimaries::Unknown;
461
462 if need_primaries
463 && let Some(matrix) = crate::gamut::conversion_matrix(from.primaries, to.primaries)
464 {
465 let flat = [
467 matrix[0][0],
468 matrix[0][1],
469 matrix[0][2],
470 matrix[1][0],
471 matrix[1][1],
472 matrix[1][2],
473 matrix[2][0],
474 matrix[2][1],
475 matrix[2][2],
476 ];
477
478 let mut goes_through_linear = false;
481 {
482 let mut desc = from;
483 for step in &steps {
484 desc = intermediate_desc(desc, step);
485 if desc.channel_type() == ChannelType::F32
486 && desc.transfer() == TransferFunction::Linear
487 {
488 goes_through_linear = true;
489 }
490 }
491 }
492
493 if goes_through_linear {
494 let mut insert_pos = 0;
498 let mut desc = from;
499 for (i, step) in steps.iter().enumerate() {
500 desc = intermediate_desc(desc, step);
501 if desc.channel_type() == ChannelType::F32
502 && desc.transfer() == TransferFunction::Linear
503 {
504 insert_pos = i + 1;
505 break;
506 }
507 }
508 let gamut_step = if desc.layout().has_alpha() {
509 ConvertStep::GamutMatrixRgbaF32(flat)
510 } else {
511 ConvertStep::GamutMatrixRgbF32(flat)
512 };
513 steps.insert(insert_pos, gamut_step);
514 } else {
515 let has_alpha = from.layout().has_alpha() || to.layout().has_alpha();
518 let mut desc = from;
520 for step in &steps {
521 desc = intermediate_desc(desc, step);
522 }
523 let gamut_step = if desc.layout().has_alpha() || has_alpha {
524 ConvertStep::GamutMatrixRgbaF32(flat)
525 } else {
526 ConvertStep::GamutMatrixRgbF32(flat)
527 };
528
529 let linearize = match desc.transfer() {
532 TransferFunction::Srgb => ConvertStep::SrgbF32ToLinearF32,
533 TransferFunction::Bt709 => ConvertStep::Bt709F32ToLinearF32,
534 TransferFunction::Pq => ConvertStep::PqF32ToLinearF32,
535 TransferFunction::Hlg => ConvertStep::HlgF32ToLinearF32,
536 TransferFunction::Gamma22 => ConvertStep::Gamma22F32ToLinearF32,
537 TransferFunction::Linear => ConvertStep::Identity,
538 _ => ConvertStep::SrgbF32ToLinearF32, };
540 let to_target_tf = match to.transfer() {
541 TransferFunction::Srgb => ConvertStep::LinearF32ToSrgbF32,
542 TransferFunction::Bt709 => ConvertStep::LinearF32ToBt709F32,
543 TransferFunction::Pq => ConvertStep::LinearF32ToPqF32,
544 TransferFunction::Hlg => ConvertStep::LinearF32ToHlgF32,
545 TransferFunction::Gamma22 => ConvertStep::LinearF32ToGamma22F32,
546 TransferFunction::Linear => ConvertStep::Identity,
547 _ => ConvertStep::LinearF32ToSrgbF32, };
549
550 let mut gamut_steps = Vec::new();
552 if desc.channel_type() == ChannelType::U16
554 && desc.transfer() == TransferFunction::Srgb
555 && to.channel_type() == ChannelType::U16
556 && to.transfer() == TransferFunction::Srgb
557 && !desc.layout().has_alpha()
558 && !to.layout().has_alpha()
559 {
560 gamut_steps.push(ConvertStep::FusedSrgbU16GamutRgb(flat));
562 steps.extend(gamut_steps);
563 if steps.is_empty() {
564 steps.push(ConvertStep::Identity);
565 }
566 fuse_matlut_patterns(&mut steps);
567 return Ok(Self::build(from, to, steps));
568 }
569 if desc.channel_type() == ChannelType::U8
570 && matches!(desc.transfer(), TransferFunction::Srgb)
571 && to.channel_type() == ChannelType::F32
572 && to.transfer() == TransferFunction::Linear
573 && !desc.layout().has_alpha()
574 && !to.layout().has_alpha()
575 {
576 gamut_steps.push(ConvertStep::FusedSrgbU8ToLinearF32Rgb(flat));
578 steps.extend(gamut_steps);
579 if steps.is_empty() {
580 steps.push(ConvertStep::Identity);
581 }
582 fuse_matlut_patterns(&mut steps);
583 return Ok(Self::build(from, to, steps));
584 }
585 if desc.channel_type() == ChannelType::F32
586 && desc.transfer() == TransferFunction::Linear
587 && to.channel_type() == ChannelType::U8
588 && to.transfer() == TransferFunction::Srgb
589 && !desc.layout().has_alpha()
590 && !to.layout().has_alpha()
591 {
592 gamut_steps.push(ConvertStep::FusedLinearF32ToSrgbU8Rgb(flat));
594 steps.extend(gamut_steps);
595 if steps.is_empty() {
596 steps.push(ConvertStep::Identity);
597 }
598 fuse_matlut_patterns(&mut steps);
599 return Ok(Self::build(from, to, steps));
600 }
601 if desc.channel_type() != ChannelType::F32 {
602 if desc.channel_type() == ChannelType::U8
604 && matches!(
605 desc.transfer(),
606 TransferFunction::Srgb
607 | TransferFunction::Bt709
608 | TransferFunction::Unknown
609 )
610 {
611 gamut_steps.push(ConvertStep::SrgbU8ToLinearF32);
612 gamut_steps.push(gamut_step);
614 gamut_steps.push(ConvertStep::LinearF32ToSrgbU8);
615 } else if desc.channel_type() == ChannelType::U16
616 && desc.transfer() == TransferFunction::Pq
617 {
618 gamut_steps.push(ConvertStep::PqU16ToLinearF32);
619 gamut_steps.push(gamut_step);
620 gamut_steps.push(ConvertStep::LinearF32ToPqU16);
621 } else if desc.channel_type() == ChannelType::U16
622 && desc.transfer() == TransferFunction::Hlg
623 {
624 gamut_steps.push(ConvertStep::HlgU16ToLinearF32);
625 gamut_steps.push(gamut_step);
626 gamut_steps.push(ConvertStep::LinearF32ToHlgU16);
627 } else {
628 gamut_steps.push(ConvertStep::NaiveU8ToF32);
630 if !matches!(linearize, ConvertStep::Identity) {
631 gamut_steps.push(linearize);
632 }
633 gamut_steps.push(gamut_step);
634 if !matches!(to_target_tf, ConvertStep::Identity) {
635 gamut_steps.push(to_target_tf);
636 }
637 gamut_steps.push(ConvertStep::NaiveF32ToU8);
638 }
639 } else {
640 if !matches!(linearize, ConvertStep::Identity) {
642 gamut_steps.push(linearize);
643 }
644 gamut_steps.push(gamut_step);
645 if !matches!(to_target_tf, ConvertStep::Identity) {
646 gamut_steps.push(to_target_tf);
647 }
648 }
649
650 steps.extend(gamut_steps);
651 }
652 }
653
654 if steps.is_empty() {
655 steps.push(ConvertStep::Identity);
657 }
658
659 fuse_matlut_patterns(&mut steps);
662
663 Ok(Self::build(from, to, steps))
664 }
665
666 #[track_caller]
677 pub fn new_explicit(
678 from: PixelDescriptor,
679 to: PixelDescriptor,
680 options: &ConvertOptions,
681 ) -> Result<Self, At<ConvertError>> {
682 assert_not_cmyk(&from);
683 assert_not_cmyk(&to);
684 let drops_alpha = from.alpha().is_some() && to.alpha().is_none();
686 if drops_alpha && options.alpha_policy == AlphaPolicy::Forbid {
687 return Err(whereat::at!(ConvertError::AlphaRemovalForbidden));
688 }
689
690 let reduces_depth = crate::negotiate::channel_bits(from.channel_type())
695 > crate::negotiate::channel_bits(to.channel_type());
696 if reduces_depth && options.depth_policy == DepthPolicy::Forbid {
697 return Err(whereat::at!(ConvertError::DepthReductionForbidden));
698 }
699
700 let src_is_rgb = matches!(
702 from.layout(),
703 ChannelLayout::Rgb | ChannelLayout::Rgba | ChannelLayout::Bgra
704 );
705 let dst_is_gray = matches!(to.layout(), ChannelLayout::Gray | ChannelLayout::GrayAlpha);
706 if src_is_rgb && dst_is_gray && options.luma.is_none() {
707 return Err(whereat::at!(ConvertError::RgbToGray));
708 }
709
710 let mut plan = Self::new(from, to).at()?;
711
712 if drops_alpha && let AlphaPolicy::CompositeOnto { r, g, b } = options.alpha_policy {
732 let src_is_premul = from.alpha() == Some(AlphaMode::Premultiplied);
733 let mut idx = 0;
734 while idx < plan.steps.len() {
735 if matches!(plan.steps[idx], ConvertStep::DropAlpha) {
736 plan.steps[idx] = ConvertStep::MatteComposite { r, g, b };
737 if src_is_premul {
738 plan.steps.insert(idx, ConvertStep::PremulToStraight);
739 idx += 1;
740 }
741 }
742 idx += 1;
743 }
744 }
745
746 if !options.clip_out_of_gamut {
751 for step in &mut plan.steps {
752 match step {
753 ConvertStep::SrgbF32ToLinearF32 => {
754 *step = ConvertStep::SrgbF32ToLinearF32Extended;
755 }
756 ConvertStep::LinearF32ToSrgbF32 => {
757 *step = ConvertStep::LinearF32ToSrgbF32Extended;
758 }
759 _ => {}
760 }
761 }
762 }
763
764 let user_luma = options.luma.unwrap_or(LumaCoefficients::Bt709);
770 for step in &mut plan.steps {
771 match step {
772 ConvertStep::RgbToGray { coefficients }
773 | ConvertStep::RgbaToGray { coefficients } => {
774 *coefficients = user_luma;
775 }
776 _ => {}
777 }
778 }
779
780 Ok(plan)
781 }
782
783 pub(crate) fn identity(from: PixelDescriptor, to: PixelDescriptor) -> Self {
789 Self::build(from, to, vec![ConvertStep::Identity])
790 }
791
792 pub fn compose(&self, other: &Self) -> Option<Self> {
801 if self.to != other.from {
802 return None;
803 }
804
805 let mut steps = self.steps.clone();
806
807 for step in &other.steps {
809 if matches!(step, ConvertStep::Identity) {
810 continue;
811 }
812 steps.push(step.clone());
813 }
814
815 let mut changed = true;
817 while changed {
818 changed = false;
819 let mut i = 0;
820 while i + 1 < steps.len() {
821 if are_inverse(&steps[i], &steps[i + 1]) {
822 steps.remove(i + 1);
823 steps.remove(i);
824 changed = true;
825 } else {
827 i += 1;
828 }
829 }
830 }
831
832 if steps.is_empty() {
834 steps.push(ConvertStep::Identity);
835 }
836
837 if steps.len() > 1 {
839 steps.retain(|s| !matches!(s, ConvertStep::Identity));
840 if steps.is_empty() {
841 steps.push(ConvertStep::Identity);
842 }
843 }
844
845 Some(Self::build(self.from, other.to, steps))
849 }
850
851 #[must_use]
853 pub fn is_identity(&self) -> bool {
854 self.steps.len() == 1 && matches!(self.steps[0], ConvertStep::Identity)
855 }
856
857 pub(crate) fn max_intermediate_bpp(&self) -> usize {
861 let mut desc = self.from;
862 let mut max_bpp = desc.bytes_per_pixel();
863 for step in &self.steps {
864 desc = intermediate_desc(desc, step);
865 max_bpp = max_bpp.max(desc.bytes_per_pixel());
866 }
867 max_bpp
868 }
869
870 pub fn from(&self) -> PixelDescriptor {
872 self.from
873 }
874
875 pub fn to(&self) -> PixelDescriptor {
877 self.to
878 }
879}
880
881fn layout_steps(from: ChannelLayout, to: ChannelLayout) -> Vec<ConvertStep> {
886 if from == to {
887 return Vec::new();
888 }
889 match (from, to) {
890 (ChannelLayout::Bgra, ChannelLayout::Rgba) | (ChannelLayout::Rgba, ChannelLayout::Bgra) => {
891 vec![ConvertStep::SwizzleBgraRgba]
892 }
893 (ChannelLayout::Rgb, ChannelLayout::Rgba) => vec![ConvertStep::AddAlpha],
894 (ChannelLayout::Rgb, ChannelLayout::Bgra) => {
895 vec![ConvertStep::RgbToBgra]
898 }
899 (ChannelLayout::Rgba, ChannelLayout::Rgb) => vec![ConvertStep::DropAlpha],
900 (ChannelLayout::Bgra, ChannelLayout::Rgb) => {
901 vec![ConvertStep::SwizzleBgraRgba, ConvertStep::DropAlpha]
903 }
904 (ChannelLayout::Gray, ChannelLayout::Rgb) => vec![ConvertStep::GrayToRgb],
905 (ChannelLayout::Gray, ChannelLayout::Rgba) => vec![ConvertStep::GrayToRgba],
906 (ChannelLayout::Gray, ChannelLayout::Bgra) => {
907 vec![ConvertStep::GrayToRgba, ConvertStep::SwizzleBgraRgba]
909 }
910 (ChannelLayout::Rgb, ChannelLayout::Gray) => vec![ConvertStep::RgbToGray {
911 coefficients: LumaCoefficients::Bt709,
912 }],
913 (ChannelLayout::Rgba, ChannelLayout::Gray) => vec![ConvertStep::RgbaToGray {
914 coefficients: LumaCoefficients::Bt709,
915 }],
916 (ChannelLayout::Bgra, ChannelLayout::Gray) => {
917 vec![
919 ConvertStep::SwizzleBgraRgba,
920 ConvertStep::RgbaToGray {
921 coefficients: LumaCoefficients::Bt709,
922 },
923 ]
924 }
925 (ChannelLayout::GrayAlpha, ChannelLayout::Rgba) => vec![ConvertStep::GrayAlphaToRgba],
926 (ChannelLayout::GrayAlpha, ChannelLayout::Bgra) => {
927 vec![ConvertStep::GrayAlphaToRgba, ConvertStep::SwizzleBgraRgba]
929 }
930 (ChannelLayout::GrayAlpha, ChannelLayout::Rgb) => vec![ConvertStep::GrayAlphaToRgb],
931 (ChannelLayout::Gray, ChannelLayout::GrayAlpha) => vec![ConvertStep::GrayToGrayAlpha],
932 (ChannelLayout::GrayAlpha, ChannelLayout::Gray) => vec![ConvertStep::GrayAlphaToGray],
933
934 (ChannelLayout::Rgb, ChannelLayout::Oklab) => vec![ConvertStep::LinearRgbToOklab],
936 (ChannelLayout::Oklab, ChannelLayout::Rgb) => vec![ConvertStep::OklabToLinearRgb],
937 (ChannelLayout::Rgba, ChannelLayout::OklabA) => vec![ConvertStep::LinearRgbaToOklaba],
938 (ChannelLayout::OklabA, ChannelLayout::Rgba) => vec![ConvertStep::OklabaToLinearRgba],
939
940 (ChannelLayout::Rgb, ChannelLayout::OklabA) => {
942 vec![ConvertStep::AddAlpha, ConvertStep::LinearRgbaToOklaba]
943 }
944 (ChannelLayout::OklabA, ChannelLayout::Rgb) => {
945 vec![ConvertStep::OklabaToLinearRgba, ConvertStep::DropAlpha]
946 }
947 (ChannelLayout::Oklab, ChannelLayout::Rgba) => {
948 vec![ConvertStep::OklabToLinearRgb, ConvertStep::AddAlpha]
949 }
950 (ChannelLayout::Rgba, ChannelLayout::Oklab) => {
951 vec![ConvertStep::DropAlpha, ConvertStep::LinearRgbToOklab]
952 }
953
954 (ChannelLayout::Bgra, ChannelLayout::OklabA) => {
956 vec![
957 ConvertStep::SwizzleBgraRgba,
958 ConvertStep::LinearRgbaToOklaba,
959 ]
960 }
961 (ChannelLayout::OklabA, ChannelLayout::Bgra) => {
962 vec![
963 ConvertStep::OklabaToLinearRgba,
964 ConvertStep::SwizzleBgraRgba,
965 ]
966 }
967 (ChannelLayout::Bgra, ChannelLayout::Oklab) => {
968 vec![
969 ConvertStep::SwizzleBgraRgba,
970 ConvertStep::DropAlpha,
971 ConvertStep::LinearRgbToOklab,
972 ]
973 }
974 (ChannelLayout::Oklab, ChannelLayout::Bgra) => {
975 vec![
976 ConvertStep::OklabToLinearRgb,
977 ConvertStep::AddAlpha,
978 ConvertStep::SwizzleBgraRgba,
979 ]
980 }
981
982 (ChannelLayout::Gray, ChannelLayout::Oklab) => {
984 vec![ConvertStep::GrayToRgb, ConvertStep::LinearRgbToOklab]
985 }
986 (ChannelLayout::Oklab, ChannelLayout::Gray) => {
987 vec![
988 ConvertStep::OklabToLinearRgb,
989 ConvertStep::RgbToGray {
990 coefficients: LumaCoefficients::Bt709,
991 },
992 ]
993 }
994 (ChannelLayout::Gray, ChannelLayout::OklabA) => {
995 vec![ConvertStep::GrayToRgba, ConvertStep::LinearRgbaToOklaba]
996 }
997 (ChannelLayout::OklabA, ChannelLayout::Gray) => {
998 vec![
999 ConvertStep::OklabaToLinearRgba,
1000 ConvertStep::RgbaToGray {
1001 coefficients: LumaCoefficients::Bt709,
1002 },
1003 ]
1004 }
1005 (ChannelLayout::GrayAlpha, ChannelLayout::OklabA) => {
1006 vec![
1007 ConvertStep::GrayAlphaToRgba,
1008 ConvertStep::LinearRgbaToOklaba,
1009 ]
1010 }
1011 (ChannelLayout::OklabA, ChannelLayout::GrayAlpha) => {
1012 vec![
1015 ConvertStep::OklabaToLinearRgba,
1016 ConvertStep::RgbaToGray {
1017 coefficients: LumaCoefficients::Bt709,
1018 },
1019 ConvertStep::GrayToGrayAlpha,
1020 ]
1021 }
1022 (ChannelLayout::GrayAlpha, ChannelLayout::Oklab) => {
1023 vec![ConvertStep::GrayAlphaToRgb, ConvertStep::LinearRgbToOklab]
1024 }
1025 (ChannelLayout::Oklab, ChannelLayout::GrayAlpha) => {
1026 vec![
1027 ConvertStep::OklabToLinearRgb,
1028 ConvertStep::RgbToGray {
1029 coefficients: LumaCoefficients::Bt709,
1030 },
1031 ConvertStep::GrayToGrayAlpha,
1032 ]
1033 }
1034
1035 (ChannelLayout::Oklab, ChannelLayout::OklabA) => vec![ConvertStep::AddAlpha],
1037 (ChannelLayout::OklabA, ChannelLayout::Oklab) => vec![ConvertStep::DropAlpha],
1038
1039 _ => Vec::new(), }
1041}
1042
1043fn f32_linearize_step(tf: TransferFunction) -> Option<ConvertStep> {
1046 match tf {
1047 TransferFunction::Linear => None,
1048 TransferFunction::Srgb => Some(ConvertStep::SrgbF32ToLinearF32),
1049 TransferFunction::Bt709 => Some(ConvertStep::Bt709F32ToLinearF32),
1050 TransferFunction::Pq => Some(ConvertStep::PqF32ToLinearF32),
1051 TransferFunction::Hlg => Some(ConvertStep::HlgF32ToLinearF32),
1052 TransferFunction::Gamma22 => Some(ConvertStep::Gamma22F32ToLinearF32),
1053 TransferFunction::Unknown => None,
1054 _ => None,
1055 }
1056}
1057
1058fn f32_encode_step(tf: TransferFunction) -> Option<ConvertStep> {
1061 match tf {
1062 TransferFunction::Linear => None,
1063 TransferFunction::Srgb => Some(ConvertStep::LinearF32ToSrgbF32),
1064 TransferFunction::Bt709 => Some(ConvertStep::LinearF32ToBt709F32),
1065 TransferFunction::Pq => Some(ConvertStep::LinearF32ToPqF32),
1066 TransferFunction::Hlg => Some(ConvertStep::LinearF32ToHlgF32),
1067 TransferFunction::Gamma22 => Some(ConvertStep::LinearF32ToGamma22F32),
1068 TransferFunction::Unknown => None,
1069 _ => None,
1070 }
1071}
1072
1073fn f32_tf_pair_steps(from: TransferFunction, to: TransferFunction) -> Vec<ConvertStep> {
1081 if from == to || from == TransferFunction::Unknown || to == TransferFunction::Unknown {
1082 return Vec::new();
1083 }
1084 let mut steps = Vec::with_capacity(2);
1085 if let Some(s) = f32_linearize_step(from) {
1086 steps.push(s);
1087 }
1088 if let Some(s) = f32_encode_step(to) {
1089 steps.push(s);
1090 }
1091 steps
1092}
1093
1094fn to_f32_step(ct: ChannelType) -> ConvertStep {
1097 match ct {
1098 ChannelType::U8 => ConvertStep::NaiveU8ToF32,
1099 ChannelType::U16 => ConvertStep::U16ToF32,
1100 ChannelType::F16 => ConvertStep::F16ToF32,
1101 _ => unreachable!("to_f32_step called with F32 or unsupported channel type"),
1102 }
1103}
1104
1105fn f32_to_depth_step(ct: ChannelType) -> ConvertStep {
1107 match ct {
1108 ChannelType::U8 => ConvertStep::NaiveF32ToU8,
1109 ChannelType::U16 => ConvertStep::F32ToU16,
1110 ChannelType::F16 => ConvertStep::F32ToF16,
1111 _ => unreachable!("f32_to_depth_step called with F32 or unsupported channel type"),
1112 }
1113}
1114
1115fn depth_steps(
1123 from: ChannelType,
1124 to: ChannelType,
1125 from_tf: TransferFunction,
1126 to_tf: TransferFunction,
1127) -> Result<Vec<ConvertStep>, ConvertError> {
1128 if from == to && from_tf == to_tf {
1129 return Ok(Vec::new());
1130 }
1131
1132 if from == to && from == ChannelType::F32 {
1134 return Ok(f32_tf_pair_steps(from_tf, to_tf));
1135 }
1136
1137 if from == to && from != ChannelType::F32 {
1146 if from_tf == TransferFunction::Unknown || to_tf == TransferFunction::Unknown {
1147 return Ok(Vec::new());
1148 }
1149 let mut steps = Vec::with_capacity(4);
1150 steps.push(to_f32_step(from));
1151 steps.extend(f32_tf_pair_steps(from_tf, to_tf));
1152 steps.push(f32_to_depth_step(to));
1153 return Ok(steps);
1154 }
1155
1156 match (from, to) {
1157 (ChannelType::U8, ChannelType::F32) => {
1158 if from_tf == TransferFunction::Srgb && to_tf == TransferFunction::Linear {
1162 Ok(vec![ConvertStep::SrgbU8ToLinearF32])
1163 } else if from_tf == to_tf {
1164 Ok(vec![ConvertStep::NaiveU8ToF32])
1165 } else {
1166 let mut steps = Vec::with_capacity(3);
1171 steps.push(ConvertStep::NaiveU8ToF32);
1172 steps.extend(f32_tf_pair_steps(from_tf, to_tf));
1173 Ok(steps)
1174 }
1175 }
1176 (ChannelType::F32, ChannelType::U8) => {
1177 if from_tf == TransferFunction::Linear && to_tf == TransferFunction::Srgb {
1179 Ok(vec![ConvertStep::LinearF32ToSrgbU8])
1180 } else if from_tf == to_tf {
1181 Ok(vec![ConvertStep::NaiveF32ToU8])
1182 } else {
1183 let mut steps = f32_tf_pair_steps(from_tf, to_tf);
1185 steps.push(ConvertStep::NaiveF32ToU8);
1186 Ok(steps)
1187 }
1188 }
1189 (ChannelType::U16, ChannelType::F32) => {
1190 match (from_tf, to_tf) {
1192 (TransferFunction::Pq, TransferFunction::Linear) => {
1193 Ok(vec![ConvertStep::PqU16ToLinearF32])
1194 }
1195 (TransferFunction::Hlg, TransferFunction::Linear) => {
1196 Ok(vec![ConvertStep::HlgU16ToLinearF32])
1197 }
1198 (a, b) if a == b => Ok(vec![ConvertStep::U16ToF32]),
1199 _ => {
1200 let mut steps = Vec::with_capacity(3);
1201 steps.push(ConvertStep::U16ToF32);
1202 steps.extend(f32_tf_pair_steps(from_tf, to_tf));
1203 Ok(steps)
1204 }
1205 }
1206 }
1207 (ChannelType::F32, ChannelType::U16) => {
1208 match (from_tf, to_tf) {
1210 (TransferFunction::Linear, TransferFunction::Pq) => {
1211 Ok(vec![ConvertStep::LinearF32ToPqU16])
1212 }
1213 (TransferFunction::Linear, TransferFunction::Hlg) => {
1214 Ok(vec![ConvertStep::LinearF32ToHlgU16])
1215 }
1216 (a, b) if a == b => Ok(vec![ConvertStep::F32ToU16]),
1217 _ => {
1218 let mut steps = f32_tf_pair_steps(from_tf, to_tf);
1219 steps.push(ConvertStep::F32ToU16);
1220 Ok(steps)
1221 }
1222 }
1223 }
1224 (ChannelType::U16, ChannelType::U8) => {
1225 if from_tf == TransferFunction::Pq && to_tf == TransferFunction::Srgb {
1227 Ok(vec![
1228 ConvertStep::PqU16ToLinearF32,
1229 ConvertStep::LinearF32ToSrgbU8,
1230 ])
1231 } else if from_tf == TransferFunction::Hlg && to_tf == TransferFunction::Srgb {
1232 Ok(vec![
1233 ConvertStep::HlgU16ToLinearF32,
1234 ConvertStep::LinearF32ToSrgbU8,
1235 ])
1236 } else if from_tf == to_tf {
1237 Ok(vec![ConvertStep::U16ToU8])
1238 } else {
1239 let mut steps = Vec::with_capacity(4);
1240 steps.push(ConvertStep::U16ToF32);
1241 steps.extend(f32_tf_pair_steps(from_tf, to_tf));
1242 steps.push(ConvertStep::NaiveF32ToU8);
1243 Ok(steps)
1244 }
1245 }
1246 (ChannelType::U8, ChannelType::U16) => {
1247 if from_tf == to_tf {
1248 Ok(vec![ConvertStep::U8ToU16])
1249 } else {
1250 let mut steps = Vec::with_capacity(4);
1251 steps.push(ConvertStep::NaiveU8ToF32);
1252 steps.extend(f32_tf_pair_steps(from_tf, to_tf));
1253 steps.push(ConvertStep::F32ToU16);
1254 Ok(steps)
1255 }
1256 }
1257 (ChannelType::F16, ChannelType::F32) => {
1260 let mut steps = Vec::with_capacity(3);
1261 steps.push(ConvertStep::F16ToF32);
1262 if from_tf != to_tf {
1263 steps.extend(f32_tf_pair_steps(from_tf, to_tf));
1264 }
1265 Ok(steps)
1266 }
1267 (ChannelType::F32, ChannelType::F16) => {
1268 let mut steps = Vec::with_capacity(3);
1269 if from_tf != to_tf {
1270 steps.extend(f32_tf_pair_steps(from_tf, to_tf));
1271 }
1272 steps.push(ConvertStep::F32ToF16);
1273 Ok(steps)
1274 }
1275 (ChannelType::F16, ChannelType::U8) => {
1276 let mut steps = Vec::with_capacity(4);
1277 steps.push(ConvertStep::F16ToF32);
1278 if from_tf == TransferFunction::Linear && to_tf == TransferFunction::Srgb {
1279 steps.push(ConvertStep::LinearF32ToSrgbU8);
1280 } else if from_tf == to_tf {
1281 steps.push(ConvertStep::NaiveF32ToU8);
1282 } else {
1283 steps.extend(f32_tf_pair_steps(from_tf, to_tf));
1284 steps.push(ConvertStep::NaiveF32ToU8);
1285 }
1286 Ok(steps)
1287 }
1288 (ChannelType::U8, ChannelType::F16) => {
1289 let mut steps = Vec::with_capacity(4);
1290 if from_tf == TransferFunction::Srgb && to_tf == TransferFunction::Linear {
1291 steps.push(ConvertStep::SrgbU8ToLinearF32);
1292 } else if from_tf == to_tf {
1293 steps.push(ConvertStep::NaiveU8ToF32);
1294 } else {
1295 steps.push(ConvertStep::NaiveU8ToF32);
1296 steps.extend(f32_tf_pair_steps(from_tf, to_tf));
1297 }
1298 steps.push(ConvertStep::F32ToF16);
1299 Ok(steps)
1300 }
1301 (ChannelType::F16, ChannelType::U16) => {
1302 let mut steps = Vec::with_capacity(4);
1303 steps.push(ConvertStep::F16ToF32);
1304 if from_tf == TransferFunction::Linear && to_tf == TransferFunction::Pq {
1305 steps.push(ConvertStep::LinearF32ToPqU16);
1306 } else if from_tf == TransferFunction::Linear && to_tf == TransferFunction::Hlg {
1307 steps.push(ConvertStep::LinearF32ToHlgU16);
1308 } else if from_tf == to_tf {
1309 steps.push(ConvertStep::F32ToU16);
1310 } else {
1311 steps.extend(f32_tf_pair_steps(from_tf, to_tf));
1312 steps.push(ConvertStep::F32ToU16);
1313 }
1314 Ok(steps)
1315 }
1316 (ChannelType::U16, ChannelType::F16) => {
1317 let mut steps = Vec::with_capacity(4);
1318 if from_tf == TransferFunction::Pq && to_tf == TransferFunction::Linear {
1319 steps.push(ConvertStep::PqU16ToLinearF32);
1320 } else if from_tf == TransferFunction::Hlg && to_tf == TransferFunction::Linear {
1321 steps.push(ConvertStep::HlgU16ToLinearF32);
1322 } else if from_tf == to_tf {
1323 steps.push(ConvertStep::U16ToF32);
1324 } else {
1325 steps.push(ConvertStep::U16ToF32);
1326 steps.extend(f32_tf_pair_steps(from_tf, to_tf));
1327 }
1328 steps.push(ConvertStep::F32ToF16);
1329 Ok(steps)
1330 }
1331 _ => Err(ConvertError::NoPath {
1332 from: PixelDescriptor::new(from, ChannelLayout::Rgb, None, from_tf),
1333 to: PixelDescriptor::new(to, ChannelLayout::Rgb, None, to_tf),
1334 }),
1335 }
1336}
1337
1338pub(crate) struct ConvertScratch {
1348 buf: Vec<u32>,
1352}
1353
1354impl ConvertScratch {
1355 pub(crate) fn new() -> Self {
1357 Self { buf: Vec::new() }
1358 }
1359
1360 fn ensure_capacity(&mut self, plan: &ConvertPlan, width: u32) {
1363 let half_bytes = (width as usize) * plan.max_intermediate_bpp();
1364 let total_u32 = (half_bytes * 2).div_ceil(4);
1365 if self.buf.len() < total_u32 {
1366 self.buf.resize(total_u32, 0);
1367 }
1368 }
1369}
1370
1371impl core::fmt::Debug for ConvertScratch {
1372 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1373 f.debug_struct("ConvertScratch")
1374 .field("capacity", &self.buf.capacity())
1375 .finish()
1376 }
1377}
1378
1379pub fn convert_row(plan: &ConvertPlan, src: &[u8], dst: &mut [u8], width: u32) {
1385 if plan.is_identity() {
1386 let len = min(src.len(), dst.len());
1387 dst[..len].copy_from_slice(&src[..len]);
1388 return;
1389 }
1390
1391 if plan.steps.len() == 1 {
1392 apply_step_u8(
1393 &plan.steps[0],
1394 src,
1395 dst,
1396 width,
1397 plan.from,
1398 plan.to,
1399 plan.pq_anchor_scale,
1400 );
1401 return;
1402 }
1403
1404 let mut scratch = ConvertScratch::new();
1406 convert_row_buffered(plan, src, dst, width, &mut scratch);
1407}
1408
1409pub(crate) fn convert_row_buffered(
1414 plan: &ConvertPlan,
1415 src: &[u8],
1416 dst: &mut [u8],
1417 width: u32,
1418 scratch: &mut ConvertScratch,
1419) {
1420 if plan.is_identity() {
1421 let len = min(src.len(), dst.len());
1422 dst[..len].copy_from_slice(&src[..len]);
1423 return;
1424 }
1425
1426 if plan.steps.len() == 1 {
1427 apply_step_u8(
1428 &plan.steps[0],
1429 src,
1430 dst,
1431 width,
1432 plan.from,
1433 plan.to,
1434 plan.pq_anchor_scale,
1435 );
1436 return;
1437 }
1438
1439 scratch.ensure_capacity(plan, width);
1440
1441 let buf_bytes: &mut [u8] = bytemuck::cast_slice_mut(&mut scratch.buf);
1442 let half = buf_bytes.len() / 2;
1443 let (buf_a, buf_b) = buf_bytes.split_at_mut(half);
1444
1445 let num_steps = plan.steps.len();
1446 let mut current_desc = plan.from;
1447
1448 for (i, step) in plan.steps.iter().enumerate() {
1449 let is_last = i == num_steps - 1;
1450 let next_desc = if is_last {
1451 plan.to
1452 } else {
1453 intermediate_desc(current_desc, step)
1454 };
1455
1456 let next_len = (width as usize) * next_desc.bytes_per_pixel();
1457 let curr_len = (width as usize) * current_desc.bytes_per_pixel();
1458
1459 if i % 2 == 0 {
1463 let input = if i == 0 { src } else { &buf_b[..curr_len] };
1464 if is_last {
1465 apply_step_u8(
1466 step,
1467 input,
1468 dst,
1469 width,
1470 current_desc,
1471 next_desc,
1472 plan.pq_anchor_scale,
1473 );
1474 } else {
1475 apply_step_u8(
1476 step,
1477 input,
1478 &mut buf_a[..next_len],
1479 width,
1480 current_desc,
1481 next_desc,
1482 plan.pq_anchor_scale,
1483 );
1484 }
1485 } else {
1486 let input = &buf_a[..curr_len];
1487 if is_last {
1488 apply_step_u8(
1489 step,
1490 input,
1491 dst,
1492 width,
1493 current_desc,
1494 next_desc,
1495 plan.pq_anchor_scale,
1496 );
1497 } else {
1498 apply_step_u8(
1499 step,
1500 input,
1501 &mut buf_b[..next_len],
1502 width,
1503 current_desc,
1504 next_desc,
1505 plan.pq_anchor_scale,
1506 );
1507 }
1508 }
1509
1510 current_desc = next_desc;
1511 }
1512}
1513
1514fn fuse_matlut_patterns(steps: &mut Vec<ConvertStep>) {
1518 let mut i = 0;
1519 while i + 2 < steps.len() {
1520 let rewrite = match (&steps[i], &steps[i + 1], &steps[i + 2]) {
1521 (
1522 ConvertStep::SrgbU8ToLinearF32,
1523 ConvertStep::GamutMatrixRgbF32(m),
1524 ConvertStep::LinearF32ToSrgbU8,
1525 ) => Some(ConvertStep::FusedSrgbU8GamutRgb(*m)),
1526 (
1527 ConvertStep::SrgbU8ToLinearF32,
1528 ConvertStep::GamutMatrixRgbaF32(m),
1529 ConvertStep::LinearF32ToSrgbU8,
1530 ) => Some(ConvertStep::FusedSrgbU8GamutRgba(*m)),
1531 _ => None,
1532 };
1533 if let Some(fused) = rewrite {
1534 steps[i] = fused;
1535 steps.drain(i + 1..i + 3);
1536 continue;
1537 }
1538 i += 1;
1539 }
1540}
1541
1542fn are_inverse(a: &ConvertStep, b: &ConvertStep) -> bool {
1543 matches!(
1544 (a, b),
1545 (ConvertStep::SwizzleBgraRgba, ConvertStep::SwizzleBgraRgba)
1547 | (ConvertStep::AddAlpha, ConvertStep::DropAlpha)
1549 | (ConvertStep::SrgbF32ToLinearF32, ConvertStep::LinearF32ToSrgbF32)
1551 | (ConvertStep::LinearF32ToSrgbF32, ConvertStep::SrgbF32ToLinearF32)
1552 | (ConvertStep::PqF32ToLinearF32, ConvertStep::LinearF32ToPqF32)
1553 | (ConvertStep::LinearF32ToPqF32, ConvertStep::PqF32ToLinearF32)
1554 | (ConvertStep::HlgF32ToLinearF32, ConvertStep::LinearF32ToHlgF32)
1555 | (ConvertStep::LinearF32ToHlgF32, ConvertStep::HlgF32ToLinearF32)
1556 | (ConvertStep::Bt709F32ToLinearF32, ConvertStep::LinearF32ToBt709F32)
1557 | (ConvertStep::LinearF32ToBt709F32, ConvertStep::Bt709F32ToLinearF32)
1558 | (ConvertStep::Gamma22F32ToLinearF32, ConvertStep::LinearF32ToGamma22F32)
1559 | (ConvertStep::LinearF32ToGamma22F32, ConvertStep::Gamma22F32ToLinearF32)
1560 | (ConvertStep::StraightToPremul, ConvertStep::PremulToStraight)
1562 | (ConvertStep::PremulToStraight, ConvertStep::StraightToPremul)
1563 | (ConvertStep::LinearRgbToOklab, ConvertStep::OklabToLinearRgb)
1565 | (ConvertStep::OklabToLinearRgb, ConvertStep::LinearRgbToOklab)
1566 | (ConvertStep::LinearRgbaToOklaba, ConvertStep::OklabaToLinearRgba)
1567 | (ConvertStep::OklabaToLinearRgba, ConvertStep::LinearRgbaToOklaba)
1568 | (ConvertStep::NaiveU8ToF32, ConvertStep::NaiveF32ToU8)
1570 | (ConvertStep::NaiveF32ToU8, ConvertStep::NaiveU8ToF32)
1571 | (ConvertStep::U8ToU16, ConvertStep::U16ToU8)
1572 | (ConvertStep::U16ToU8, ConvertStep::U8ToU16)
1573 | (ConvertStep::U16ToF32, ConvertStep::F32ToU16)
1574 | (ConvertStep::F32ToU16, ConvertStep::U16ToF32)
1575 | (ConvertStep::F16ToF32, ConvertStep::F32ToF16)
1576 | (ConvertStep::F32ToF16, ConvertStep::F16ToF32)
1577 | (ConvertStep::SrgbU8ToLinearF32, ConvertStep::LinearF32ToSrgbU8)
1579 | (ConvertStep::LinearF32ToSrgbU8, ConvertStep::SrgbU8ToLinearF32)
1580 | (ConvertStep::PqU16ToLinearF32, ConvertStep::LinearF32ToPqU16)
1581 | (ConvertStep::LinearF32ToPqU16, ConvertStep::PqU16ToLinearF32)
1582 | (ConvertStep::HlgU16ToLinearF32, ConvertStep::LinearF32ToHlgU16)
1583 | (ConvertStep::LinearF32ToHlgU16, ConvertStep::HlgU16ToLinearF32)
1584 | (ConvertStep::SrgbF32ToLinearF32Extended, ConvertStep::LinearF32ToSrgbF32Extended)
1586 | (ConvertStep::LinearF32ToSrgbF32Extended, ConvertStep::SrgbF32ToLinearF32Extended)
1587 )
1588}
1589
1590fn intermediate_desc(current: PixelDescriptor, step: &ConvertStep) -> PixelDescriptor {
1592 match step {
1593 ConvertStep::Identity => current,
1594 ConvertStep::SwizzleBgraRgba => {
1595 let new_layout = match current.layout() {
1596 ChannelLayout::Bgra => ChannelLayout::Rgba,
1597 ChannelLayout::Rgba => ChannelLayout::Bgra,
1598 other => other,
1599 };
1600 PixelDescriptor::new(
1601 current.channel_type(),
1602 new_layout,
1603 current.alpha(),
1604 current.transfer(),
1605 )
1606 }
1607 ConvertStep::AddAlpha => PixelDescriptor::new(
1608 current.channel_type(),
1609 ChannelLayout::Rgba,
1610 Some(AlphaMode::Straight),
1611 current.transfer(),
1612 ),
1613 ConvertStep::RgbToBgra => PixelDescriptor::new(
1614 current.channel_type(),
1615 ChannelLayout::Bgra,
1616 Some(AlphaMode::Straight),
1617 current.transfer(),
1618 ),
1619 ConvertStep::DropAlpha | ConvertStep::MatteComposite { .. } => PixelDescriptor::new(
1620 current.channel_type(),
1621 ChannelLayout::Rgb,
1622 None,
1623 current.transfer(),
1624 ),
1625 ConvertStep::GrayToRgb => PixelDescriptor::new(
1626 current.channel_type(),
1627 ChannelLayout::Rgb,
1628 None,
1629 current.transfer(),
1630 ),
1631 ConvertStep::GrayToRgba => PixelDescriptor::new(
1632 current.channel_type(),
1633 ChannelLayout::Rgba,
1634 Some(AlphaMode::Straight),
1635 current.transfer(),
1636 ),
1637 ConvertStep::RgbToGray { .. } | ConvertStep::RgbaToGray { .. } => PixelDescriptor::new(
1638 current.channel_type(),
1639 ChannelLayout::Gray,
1640 None,
1641 current.transfer(),
1642 ),
1643 ConvertStep::GrayAlphaToRgba => PixelDescriptor::new(
1644 current.channel_type(),
1645 ChannelLayout::Rgba,
1646 current.alpha(),
1647 current.transfer(),
1648 ),
1649 ConvertStep::GrayAlphaToRgb => PixelDescriptor::new(
1650 current.channel_type(),
1651 ChannelLayout::Rgb,
1652 None,
1653 current.transfer(),
1654 ),
1655 ConvertStep::GrayToGrayAlpha => PixelDescriptor::new(
1656 current.channel_type(),
1657 ChannelLayout::GrayAlpha,
1658 Some(AlphaMode::Straight),
1659 current.transfer(),
1660 ),
1661 ConvertStep::GrayAlphaToGray => PixelDescriptor::new(
1662 current.channel_type(),
1663 ChannelLayout::Gray,
1664 None,
1665 current.transfer(),
1666 ),
1667 ConvertStep::SrgbU8ToLinearF32
1668 | ConvertStep::NaiveU8ToF32
1669 | ConvertStep::U16ToF32
1670 | ConvertStep::PqU16ToLinearF32
1671 | ConvertStep::HlgU16ToLinearF32
1672 | ConvertStep::PqF32ToLinearF32
1673 | ConvertStep::HlgF32ToLinearF32
1674 | ConvertStep::SrgbF32ToLinearF32
1675 | ConvertStep::SrgbF32ToLinearF32Extended
1676 | ConvertStep::Bt709F32ToLinearF32
1677 | ConvertStep::Gamma22F32ToLinearF32 => PixelDescriptor::new(
1678 ChannelType::F32,
1679 current.layout(),
1680 current.alpha(),
1681 TransferFunction::Linear,
1682 ),
1683 ConvertStep::LinearF32ToSrgbU8 | ConvertStep::NaiveF32ToU8 | ConvertStep::U16ToU8 => {
1684 PixelDescriptor::new(
1685 ChannelType::U8,
1686 current.layout(),
1687 current.alpha(),
1688 TransferFunction::Srgb,
1689 )
1690 }
1691 ConvertStep::U8ToU16 => PixelDescriptor::new(
1692 ChannelType::U16,
1693 current.layout(),
1694 current.alpha(),
1695 current.transfer(),
1696 ),
1697 ConvertStep::F32ToU16 | ConvertStep::LinearF32ToPqU16 | ConvertStep::LinearF32ToHlgU16 => {
1698 let tf = match step {
1699 ConvertStep::LinearF32ToPqU16 => TransferFunction::Pq,
1700 ConvertStep::LinearF32ToHlgU16 => TransferFunction::Hlg,
1701 _ => current.transfer(),
1702 };
1703 PixelDescriptor::new(ChannelType::U16, current.layout(), current.alpha(), tf)
1704 }
1705 ConvertStep::LinearF32ToPqF32 => PixelDescriptor::new(
1706 ChannelType::F32,
1707 current.layout(),
1708 current.alpha(),
1709 TransferFunction::Pq,
1710 ),
1711 ConvertStep::LinearF32ToHlgF32 => PixelDescriptor::new(
1712 ChannelType::F32,
1713 current.layout(),
1714 current.alpha(),
1715 TransferFunction::Hlg,
1716 ),
1717 ConvertStep::LinearF32ToSrgbF32 | ConvertStep::LinearF32ToSrgbF32Extended => {
1718 PixelDescriptor::new(
1719 ChannelType::F32,
1720 current.layout(),
1721 current.alpha(),
1722 TransferFunction::Srgb,
1723 )
1724 }
1725 ConvertStep::LinearF32ToBt709F32 => PixelDescriptor::new(
1726 ChannelType::F32,
1727 current.layout(),
1728 current.alpha(),
1729 TransferFunction::Bt709,
1730 ),
1731 ConvertStep::LinearF32ToGamma22F32 => PixelDescriptor::new(
1732 ChannelType::F32,
1733 current.layout(),
1734 current.alpha(),
1735 TransferFunction::Gamma22,
1736 ),
1737 ConvertStep::StraightToPremul => PixelDescriptor::new(
1738 current.channel_type(),
1739 current.layout(),
1740 Some(AlphaMode::Premultiplied),
1741 current.transfer(),
1742 ),
1743 ConvertStep::PremulToStraight => PixelDescriptor::new(
1744 current.channel_type(),
1745 current.layout(),
1746 Some(AlphaMode::Straight),
1747 current.transfer(),
1748 ),
1749 ConvertStep::LinearRgbToOklab => PixelDescriptor::new(
1750 ChannelType::F32,
1751 ChannelLayout::Oklab,
1752 None,
1753 TransferFunction::Unknown,
1754 )
1755 .with_primaries(current.primaries),
1756 ConvertStep::OklabToLinearRgb => PixelDescriptor::new(
1757 ChannelType::F32,
1758 ChannelLayout::Rgb,
1759 None,
1760 TransferFunction::Linear,
1761 )
1762 .with_primaries(current.primaries),
1763 ConvertStep::LinearRgbaToOklaba => PixelDescriptor::new(
1764 ChannelType::F32,
1765 ChannelLayout::OklabA,
1766 Some(AlphaMode::Straight),
1767 TransferFunction::Unknown,
1768 )
1769 .with_primaries(current.primaries),
1770 ConvertStep::OklabaToLinearRgba => PixelDescriptor::new(
1771 ChannelType::F32,
1772 ChannelLayout::Rgba,
1773 current.alpha(),
1774 TransferFunction::Linear,
1775 )
1776 .with_primaries(current.primaries),
1777
1778 ConvertStep::GamutMatrixRgbF32(_) => PixelDescriptor::new(
1783 ChannelType::F32,
1784 current.layout(),
1785 current.alpha(),
1786 TransferFunction::Linear,
1787 ),
1788 ConvertStep::GamutMatrixRgbaF32(_) => PixelDescriptor::new(
1789 ChannelType::F32,
1790 current.layout(),
1791 current.alpha(),
1792 TransferFunction::Linear,
1793 ),
1794 ConvertStep::FusedSrgbU8GamutRgb(_) | ConvertStep::FusedSrgbU8GamutRgba(_) => {
1796 PixelDescriptor::new(
1797 ChannelType::U8,
1798 current.layout(),
1799 current.alpha(),
1800 TransferFunction::Srgb,
1801 )
1802 }
1803 ConvertStep::FusedSrgbU16GamutRgb(_) => PixelDescriptor::new(
1804 ChannelType::U16,
1805 current.layout(),
1806 current.alpha(),
1807 TransferFunction::Srgb,
1808 ),
1809 ConvertStep::FusedSrgbU8ToLinearF32Rgb(_) => PixelDescriptor::new(
1810 ChannelType::F32,
1811 current.layout(),
1812 current.alpha(),
1813 TransferFunction::Linear,
1814 ),
1815 ConvertStep::FusedLinearF32ToSrgbU8Rgb(_) => PixelDescriptor::new(
1816 ChannelType::U8,
1817 current.layout(),
1818 current.alpha(),
1819 TransferFunction::Srgb,
1820 ),
1821 ConvertStep::F16ToF32 => PixelDescriptor::new(
1823 ChannelType::F32,
1824 current.layout(),
1825 current.alpha(),
1826 current.transfer(),
1827 ),
1828 ConvertStep::F32ToF16 => PixelDescriptor::new(
1829 ChannelType::F16,
1830 current.layout(),
1831 current.alpha(),
1832 current.transfer(),
1833 ),
1834 }
1835}
1836
1837#[path = "convert_kernels.rs"]
1838mod convert_kernels;
1839use convert_kernels::apply_step_u8;
1840pub(crate) use convert_kernels::{hlg_eotf, hlg_oetf, pq_eotf, pq_oetf};