1use alloc::vec;
9use alloc::vec::Vec;
10use core::cmp::min;
11
12use crate::policy::{AlphaPolicy, ConvertOptions, DepthPolicy};
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, Copy, Debug, PartialEq)]
32pub(crate) enum ConvertStep {
33 Identity,
35 SwizzleBgraRgba,
37 AddAlpha,
39 DropAlpha,
41 MatteComposite { r: u8, g: u8, b: u8 },
47 GrayToRgb,
49 GrayToRgba,
51 RgbToGray,
53 RgbaToGray,
55 GrayAlphaToRgba,
57 GrayAlphaToRgb,
59 GrayToGrayAlpha,
61 GrayAlphaToGray,
63 SrgbU8ToLinearF32,
65 LinearF32ToSrgbU8,
67 NaiveU8ToF32,
69 NaiveF32ToU8,
71 U16ToU8,
73 U8ToU16,
75 U16ToF32,
77 F32ToU16,
79 PqU16ToLinearF32,
81 LinearF32ToPqU16,
83 PqF32ToLinearF32,
85 LinearF32ToPqF32,
87 HlgU16ToLinearF32,
89 LinearF32ToHlgU16,
91 HlgF32ToLinearF32,
93 LinearF32ToHlgF32,
95 SrgbF32ToLinearF32,
97 LinearF32ToSrgbF32,
99 Bt709F32ToLinearF32,
101 LinearF32ToBt709F32,
103 StraightToPremul,
105 PremulToStraight,
107 LinearRgbToOklab,
109 OklabToLinearRgb,
111 LinearRgbaToOklaba,
113 OklabaToLinearRgba,
115 GamutMatrixRgbF32([f32; 9]),
121 GamutMatrixRgbaF32([f32; 9]),
123}
124
125impl ConvertPlan {
126 #[track_caller]
130 pub fn new(from: PixelDescriptor, to: PixelDescriptor) -> Result<Self, At<ConvertError>> {
131 if from == to {
132 return Ok(Self {
133 from,
134 to,
135 steps: vec![ConvertStep::Identity],
136 });
137 }
138
139 let mut steps = Vec::with_capacity(3);
140
141 let need_depth_change = from.channel_type() != to.channel_type();
150 let need_layout_change = from.layout() != to.layout();
151 let need_alpha_change =
152 from.alpha() != to.alpha() && from.alpha().is_some() && to.alpha().is_some();
153
154 let need_depth_or_tf = need_depth_change
157 || (from.channel_type() == ChannelType::F32 && from.transfer() != to.transfer());
158
159 if need_layout_change {
161 let src_ch = from.layout().channels();
168 let dst_ch = to.layout().channels();
169 let involves_oklab =
170 matches!(from.layout(), ChannelLayout::Oklab | ChannelLayout::OklabA)
171 || matches!(to.layout(), ChannelLayout::Oklab | ChannelLayout::OklabA);
172
173 if involves_oklab && from.primaries == ColorPrimaries::Unknown {
175 return Err(whereat::at!(ConvertError::NoPath { from, to }));
176 }
177
178 let depth_first = need_depth_or_tf
179 && (dst_ch > src_ch || (involves_oklab && from.channel_type() != ChannelType::F32));
180
181 if depth_first {
182 steps.extend(
184 depth_steps(
185 from.channel_type(),
186 to.channel_type(),
187 from.transfer(),
188 to.transfer(),
189 )
190 .map_err(|e| whereat::at!(e))?,
191 );
192 steps.extend(layout_steps(from.layout(), to.layout()));
193 } else {
194 steps.extend(layout_steps(from.layout(), to.layout()));
196 if need_depth_or_tf {
197 steps.extend(
198 depth_steps(
199 from.channel_type(),
200 to.channel_type(),
201 from.transfer(),
202 to.transfer(),
203 )
204 .map_err(|e| whereat::at!(e))?,
205 );
206 }
207 }
208 } else if need_depth_or_tf {
209 steps.extend(
210 depth_steps(
211 from.channel_type(),
212 to.channel_type(),
213 from.transfer(),
214 to.transfer(),
215 )
216 .map_err(|e| whereat::at!(e))?,
217 );
218 }
219
220 if need_alpha_change {
222 match (from.alpha(), to.alpha()) {
223 (Some(AlphaMode::Straight), Some(AlphaMode::Premultiplied)) => {
224 steps.push(ConvertStep::StraightToPremul);
225 }
226 (Some(AlphaMode::Premultiplied), Some(AlphaMode::Straight)) => {
227 steps.push(ConvertStep::PremulToStraight);
228 }
229 _ => {}
230 }
231 }
232
233 let need_primaries = from.primaries != to.primaries
236 && from.primaries != ColorPrimaries::Unknown
237 && to.primaries != ColorPrimaries::Unknown;
238
239 if need_primaries
240 && let Some(matrix) = crate::gamut::conversion_matrix(from.primaries, to.primaries)
241 {
242 let flat = [
244 matrix[0][0],
245 matrix[0][1],
246 matrix[0][2],
247 matrix[1][0],
248 matrix[1][1],
249 matrix[1][2],
250 matrix[2][0],
251 matrix[2][1],
252 matrix[2][2],
253 ];
254
255 let mut goes_through_linear = false;
258 {
259 let mut desc = from;
260 for &step in &steps {
261 desc = intermediate_desc(desc, step);
262 if desc.channel_type() == ChannelType::F32
263 && desc.transfer() == TransferFunction::Linear
264 {
265 goes_through_linear = true;
266 }
267 }
268 }
269
270 if goes_through_linear {
271 let mut insert_pos = 0;
275 let mut desc = from;
276 for (i, &step) in steps.iter().enumerate() {
277 desc = intermediate_desc(desc, step);
278 if desc.channel_type() == ChannelType::F32
279 && desc.transfer() == TransferFunction::Linear
280 {
281 insert_pos = i + 1;
282 break;
283 }
284 }
285 let gamut_step = if desc.layout().has_alpha() {
286 ConvertStep::GamutMatrixRgbaF32(flat)
287 } else {
288 ConvertStep::GamutMatrixRgbF32(flat)
289 };
290 steps.insert(insert_pos, gamut_step);
291 } else {
292 let has_alpha = from.layout().has_alpha() || to.layout().has_alpha();
295 let mut desc = from;
297 for &step in &steps {
298 desc = intermediate_desc(desc, step);
299 }
300 let gamut_step = if desc.layout().has_alpha() || has_alpha {
301 ConvertStep::GamutMatrixRgbaF32(flat)
302 } else {
303 ConvertStep::GamutMatrixRgbF32(flat)
304 };
305
306 let linearize = match desc.transfer() {
309 TransferFunction::Srgb => ConvertStep::SrgbF32ToLinearF32,
310 TransferFunction::Bt709 => ConvertStep::Bt709F32ToLinearF32,
311 TransferFunction::Pq => ConvertStep::PqF32ToLinearF32,
312 TransferFunction::Hlg => ConvertStep::HlgF32ToLinearF32,
313 TransferFunction::Linear => ConvertStep::Identity,
314 _ => ConvertStep::SrgbF32ToLinearF32, };
316 let to_target_tf = match to.transfer() {
317 TransferFunction::Srgb => ConvertStep::LinearF32ToSrgbF32,
318 TransferFunction::Bt709 => ConvertStep::LinearF32ToBt709F32,
319 TransferFunction::Pq => ConvertStep::LinearF32ToPqF32,
320 TransferFunction::Hlg => ConvertStep::LinearF32ToHlgF32,
321 TransferFunction::Linear => ConvertStep::Identity,
322 _ => ConvertStep::LinearF32ToSrgbF32, };
324
325 let mut gamut_steps = Vec::new();
327 if desc.channel_type() != ChannelType::F32 {
328 if desc.channel_type() == ChannelType::U8
330 && matches!(
331 desc.transfer(),
332 TransferFunction::Srgb
333 | TransferFunction::Bt709
334 | TransferFunction::Unknown
335 )
336 {
337 gamut_steps.push(ConvertStep::SrgbU8ToLinearF32);
338 gamut_steps.push(gamut_step);
340 gamut_steps.push(ConvertStep::LinearF32ToSrgbU8);
341 } else if desc.channel_type() == ChannelType::U16
342 && desc.transfer() == TransferFunction::Pq
343 {
344 gamut_steps.push(ConvertStep::PqU16ToLinearF32);
345 gamut_steps.push(gamut_step);
346 gamut_steps.push(ConvertStep::LinearF32ToPqU16);
347 } else if desc.channel_type() == ChannelType::U16
348 && desc.transfer() == TransferFunction::Hlg
349 {
350 gamut_steps.push(ConvertStep::HlgU16ToLinearF32);
351 gamut_steps.push(gamut_step);
352 gamut_steps.push(ConvertStep::LinearF32ToHlgU16);
353 } else {
354 gamut_steps.push(ConvertStep::NaiveU8ToF32);
356 if linearize != ConvertStep::Identity {
357 gamut_steps.push(linearize);
358 }
359 gamut_steps.push(gamut_step);
360 if to_target_tf != ConvertStep::Identity {
361 gamut_steps.push(to_target_tf);
362 }
363 gamut_steps.push(ConvertStep::NaiveF32ToU8);
364 }
365 } else {
366 if linearize != ConvertStep::Identity {
368 gamut_steps.push(linearize);
369 }
370 gamut_steps.push(gamut_step);
371 if to_target_tf != ConvertStep::Identity {
372 gamut_steps.push(to_target_tf);
373 }
374 }
375
376 steps.extend(gamut_steps);
377 }
378 }
379
380 if steps.is_empty() {
381 steps.push(ConvertStep::Identity);
383 }
384
385 Ok(Self { from, to, steps })
386 }
387
388 #[track_caller]
394 pub fn new_explicit(
395 from: PixelDescriptor,
396 to: PixelDescriptor,
397 options: &ConvertOptions,
398 ) -> Result<Self, At<ConvertError>> {
399 let drops_alpha = from.alpha().is_some() && to.alpha().is_none();
401 if drops_alpha && options.alpha_policy == AlphaPolicy::Forbid {
402 return Err(whereat::at!(ConvertError::AlphaRemovalForbidden));
403 }
404
405 let reduces_depth = from.channel_type().byte_size() > to.channel_type().byte_size();
407 if reduces_depth && options.depth_policy == DepthPolicy::Forbid {
408 return Err(whereat::at!(ConvertError::DepthReductionForbidden));
409 }
410
411 let src_is_rgb = matches!(
413 from.layout(),
414 ChannelLayout::Rgb | ChannelLayout::Rgba | ChannelLayout::Bgra
415 );
416 let dst_is_gray = matches!(to.layout(), ChannelLayout::Gray | ChannelLayout::GrayAlpha);
417 if src_is_rgb && dst_is_gray && options.luma.is_none() {
418 return Err(whereat::at!(ConvertError::RgbToGray));
419 }
420
421 let mut plan = Self::new(from, to).at()?;
422
423 if drops_alpha && let AlphaPolicy::CompositeOnto { r, g, b } = options.alpha_policy {
425 for step in &mut plan.steps {
426 if matches!(step, ConvertStep::DropAlpha) {
427 *step = ConvertStep::MatteComposite { r, g, b };
428 }
429 }
430 }
431
432 Ok(plan)
433 }
434
435 pub fn compose(&self, other: &Self) -> Option<Self> {
444 if self.to != other.from {
445 return None;
446 }
447
448 let mut steps = self.steps.clone();
449
450 for &step in &other.steps {
452 if step == ConvertStep::Identity {
453 continue;
454 }
455 steps.push(step);
456 }
457
458 let mut changed = true;
460 while changed {
461 changed = false;
462 let mut i = 0;
463 while i + 1 < steps.len() {
464 if are_inverse(steps[i], steps[i + 1]) {
465 steps.remove(i + 1);
466 steps.remove(i);
467 changed = true;
468 } else {
470 i += 1;
471 }
472 }
473 }
474
475 if steps.is_empty() {
477 steps.push(ConvertStep::Identity);
478 }
479
480 if steps.len() > 1 {
482 steps.retain(|s| *s != ConvertStep::Identity);
483 if steps.is_empty() {
484 steps.push(ConvertStep::Identity);
485 }
486 }
487
488 Some(Self {
489 from: self.from,
490 to: other.to,
491 steps,
492 })
493 }
494
495 #[must_use]
497 pub fn is_identity(&self) -> bool {
498 self.steps.len() == 1 && self.steps[0] == ConvertStep::Identity
499 }
500
501 pub(crate) fn max_intermediate_bpp(&self) -> usize {
505 let mut desc = self.from;
506 let mut max_bpp = desc.bytes_per_pixel();
507 for &step in &self.steps {
508 desc = intermediate_desc(desc, step);
509 max_bpp = max_bpp.max(desc.bytes_per_pixel());
510 }
511 max_bpp
512 }
513
514 pub fn from(&self) -> PixelDescriptor {
516 self.from
517 }
518
519 pub fn to(&self) -> PixelDescriptor {
521 self.to
522 }
523}
524
525fn layout_steps(from: ChannelLayout, to: ChannelLayout) -> Vec<ConvertStep> {
530 if from == to {
531 return Vec::new();
532 }
533 match (from, to) {
534 (ChannelLayout::Bgra, ChannelLayout::Rgba) | (ChannelLayout::Rgba, ChannelLayout::Bgra) => {
535 vec![ConvertStep::SwizzleBgraRgba]
536 }
537 (ChannelLayout::Rgb, ChannelLayout::Rgba) => vec![ConvertStep::AddAlpha],
538 (ChannelLayout::Rgb, ChannelLayout::Bgra) => {
539 vec![ConvertStep::AddAlpha, ConvertStep::SwizzleBgraRgba]
541 }
542 (ChannelLayout::Rgba, ChannelLayout::Rgb) => vec![ConvertStep::DropAlpha],
543 (ChannelLayout::Bgra, ChannelLayout::Rgb) => {
544 vec![ConvertStep::SwizzleBgraRgba, ConvertStep::DropAlpha]
546 }
547 (ChannelLayout::Gray, ChannelLayout::Rgb) => vec![ConvertStep::GrayToRgb],
548 (ChannelLayout::Gray, ChannelLayout::Rgba) => vec![ConvertStep::GrayToRgba],
549 (ChannelLayout::Gray, ChannelLayout::Bgra) => {
550 vec![ConvertStep::GrayToRgba, ConvertStep::SwizzleBgraRgba]
552 }
553 (ChannelLayout::Rgb, ChannelLayout::Gray) => vec![ConvertStep::RgbToGray],
554 (ChannelLayout::Rgba, ChannelLayout::Gray) => vec![ConvertStep::RgbaToGray],
555 (ChannelLayout::Bgra, ChannelLayout::Gray) => {
556 vec![ConvertStep::SwizzleBgraRgba, ConvertStep::RgbaToGray]
558 }
559 (ChannelLayout::GrayAlpha, ChannelLayout::Rgba) => vec![ConvertStep::GrayAlphaToRgba],
560 (ChannelLayout::GrayAlpha, ChannelLayout::Bgra) => {
561 vec![ConvertStep::GrayAlphaToRgba, ConvertStep::SwizzleBgraRgba]
563 }
564 (ChannelLayout::GrayAlpha, ChannelLayout::Rgb) => vec![ConvertStep::GrayAlphaToRgb],
565 (ChannelLayout::Gray, ChannelLayout::GrayAlpha) => vec![ConvertStep::GrayToGrayAlpha],
566 (ChannelLayout::GrayAlpha, ChannelLayout::Gray) => vec![ConvertStep::GrayAlphaToGray],
567
568 (ChannelLayout::Rgb, ChannelLayout::Oklab) => vec![ConvertStep::LinearRgbToOklab],
570 (ChannelLayout::Oklab, ChannelLayout::Rgb) => vec![ConvertStep::OklabToLinearRgb],
571 (ChannelLayout::Rgba, ChannelLayout::OklabA) => vec![ConvertStep::LinearRgbaToOklaba],
572 (ChannelLayout::OklabA, ChannelLayout::Rgba) => vec![ConvertStep::OklabaToLinearRgba],
573
574 (ChannelLayout::Rgb, ChannelLayout::OklabA) => {
576 vec![ConvertStep::AddAlpha, ConvertStep::LinearRgbaToOklaba]
577 }
578 (ChannelLayout::OklabA, ChannelLayout::Rgb) => {
579 vec![ConvertStep::OklabaToLinearRgba, ConvertStep::DropAlpha]
580 }
581 (ChannelLayout::Oklab, ChannelLayout::Rgba) => {
582 vec![ConvertStep::OklabToLinearRgb, ConvertStep::AddAlpha]
583 }
584 (ChannelLayout::Rgba, ChannelLayout::Oklab) => {
585 vec![ConvertStep::DropAlpha, ConvertStep::LinearRgbToOklab]
586 }
587
588 (ChannelLayout::Bgra, ChannelLayout::OklabA) => {
590 vec![
591 ConvertStep::SwizzleBgraRgba,
592 ConvertStep::LinearRgbaToOklaba,
593 ]
594 }
595 (ChannelLayout::OklabA, ChannelLayout::Bgra) => {
596 vec![
597 ConvertStep::OklabaToLinearRgba,
598 ConvertStep::SwizzleBgraRgba,
599 ]
600 }
601 (ChannelLayout::Bgra, ChannelLayout::Oklab) => {
602 vec![
603 ConvertStep::SwizzleBgraRgba,
604 ConvertStep::DropAlpha,
605 ConvertStep::LinearRgbToOklab,
606 ]
607 }
608 (ChannelLayout::Oklab, ChannelLayout::Bgra) => {
609 vec![
610 ConvertStep::OklabToLinearRgb,
611 ConvertStep::AddAlpha,
612 ConvertStep::SwizzleBgraRgba,
613 ]
614 }
615
616 (ChannelLayout::Gray, ChannelLayout::Oklab) => {
618 vec![ConvertStep::GrayToRgb, ConvertStep::LinearRgbToOklab]
619 }
620 (ChannelLayout::Oklab, ChannelLayout::Gray) => {
621 vec![ConvertStep::OklabToLinearRgb, ConvertStep::RgbToGray]
622 }
623 (ChannelLayout::Gray, ChannelLayout::OklabA) => {
624 vec![ConvertStep::GrayToRgba, ConvertStep::LinearRgbaToOklaba]
625 }
626 (ChannelLayout::OklabA, ChannelLayout::Gray) => {
627 vec![ConvertStep::OklabaToLinearRgba, ConvertStep::RgbaToGray]
628 }
629 (ChannelLayout::GrayAlpha, ChannelLayout::OklabA) => {
630 vec![
631 ConvertStep::GrayAlphaToRgba,
632 ConvertStep::LinearRgbaToOklaba,
633 ]
634 }
635 (ChannelLayout::OklabA, ChannelLayout::GrayAlpha) => {
636 vec![
639 ConvertStep::OklabaToLinearRgba,
640 ConvertStep::RgbaToGray,
641 ConvertStep::GrayToGrayAlpha,
642 ]
643 }
644 (ChannelLayout::GrayAlpha, ChannelLayout::Oklab) => {
645 vec![ConvertStep::GrayAlphaToRgb, ConvertStep::LinearRgbToOklab]
646 }
647 (ChannelLayout::Oklab, ChannelLayout::GrayAlpha) => {
648 vec![
649 ConvertStep::OklabToLinearRgb,
650 ConvertStep::RgbToGray,
651 ConvertStep::GrayToGrayAlpha,
652 ]
653 }
654
655 (ChannelLayout::Oklab, ChannelLayout::OklabA) => vec![ConvertStep::AddAlpha],
657 (ChannelLayout::OklabA, ChannelLayout::Oklab) => vec![ConvertStep::DropAlpha],
658
659 _ => Vec::new(), }
661}
662
663fn depth_steps(
669 from: ChannelType,
670 to: ChannelType,
671 from_tf: TransferFunction,
672 to_tf: TransferFunction,
673) -> Result<Vec<ConvertStep>, ConvertError> {
674 if from == to && from_tf == to_tf {
675 return Ok(Vec::new());
676 }
677
678 if from == to && from != ChannelType::F32 {
682 return Ok(Vec::new());
683 }
684
685 if from == to && from == ChannelType::F32 {
686 return match (from_tf, to_tf) {
687 (TransferFunction::Pq, TransferFunction::Linear) => {
688 Ok(vec![ConvertStep::PqF32ToLinearF32])
689 }
690 (TransferFunction::Linear, TransferFunction::Pq) => {
691 Ok(vec![ConvertStep::LinearF32ToPqF32])
692 }
693 (TransferFunction::Hlg, TransferFunction::Linear) => {
694 Ok(vec![ConvertStep::HlgF32ToLinearF32])
695 }
696 (TransferFunction::Linear, TransferFunction::Hlg) => {
697 Ok(vec![ConvertStep::LinearF32ToHlgF32])
698 }
699 (TransferFunction::Pq, TransferFunction::Hlg) => Ok(vec![
701 ConvertStep::PqF32ToLinearF32,
702 ConvertStep::LinearF32ToHlgF32,
703 ]),
704 (TransferFunction::Hlg, TransferFunction::Pq) => Ok(vec![
705 ConvertStep::HlgF32ToLinearF32,
706 ConvertStep::LinearF32ToPqF32,
707 ]),
708 (TransferFunction::Srgb, TransferFunction::Linear) => {
709 Ok(vec![ConvertStep::SrgbF32ToLinearF32])
710 }
711 (TransferFunction::Linear, TransferFunction::Srgb) => {
712 Ok(vec![ConvertStep::LinearF32ToSrgbF32])
713 }
714 (TransferFunction::Bt709, TransferFunction::Linear) => {
715 Ok(vec![ConvertStep::Bt709F32ToLinearF32])
716 }
717 (TransferFunction::Linear, TransferFunction::Bt709) => {
718 Ok(vec![ConvertStep::LinearF32ToBt709F32])
719 }
720 (TransferFunction::Srgb, TransferFunction::Bt709) => Ok(vec![
722 ConvertStep::SrgbF32ToLinearF32,
723 ConvertStep::LinearF32ToBt709F32,
724 ]),
725 (TransferFunction::Bt709, TransferFunction::Srgb) => Ok(vec![
726 ConvertStep::Bt709F32ToLinearF32,
727 ConvertStep::LinearF32ToSrgbF32,
728 ]),
729 (TransferFunction::Srgb, TransferFunction::Pq) => Ok(vec![
731 ConvertStep::SrgbF32ToLinearF32,
732 ConvertStep::LinearF32ToPqF32,
733 ]),
734 (TransferFunction::Srgb, TransferFunction::Hlg) => Ok(vec![
735 ConvertStep::SrgbF32ToLinearF32,
736 ConvertStep::LinearF32ToHlgF32,
737 ]),
738 (TransferFunction::Pq, TransferFunction::Srgb) => Ok(vec![
739 ConvertStep::PqF32ToLinearF32,
740 ConvertStep::LinearF32ToSrgbF32,
741 ]),
742 (TransferFunction::Hlg, TransferFunction::Srgb) => Ok(vec![
743 ConvertStep::HlgF32ToLinearF32,
744 ConvertStep::LinearF32ToSrgbF32,
745 ]),
746 (TransferFunction::Bt709, TransferFunction::Pq) => Ok(vec![
747 ConvertStep::Bt709F32ToLinearF32,
748 ConvertStep::LinearF32ToPqF32,
749 ]),
750 (TransferFunction::Bt709, TransferFunction::Hlg) => Ok(vec![
751 ConvertStep::Bt709F32ToLinearF32,
752 ConvertStep::LinearF32ToHlgF32,
753 ]),
754 (TransferFunction::Pq, TransferFunction::Bt709) => Ok(vec![
755 ConvertStep::PqF32ToLinearF32,
756 ConvertStep::LinearF32ToBt709F32,
757 ]),
758 (TransferFunction::Hlg, TransferFunction::Bt709) => Ok(vec![
759 ConvertStep::HlgF32ToLinearF32,
760 ConvertStep::LinearF32ToBt709F32,
761 ]),
762 _ => Ok(Vec::new()),
763 };
764 }
765
766 match (from, to) {
767 (ChannelType::U8, ChannelType::F32) => {
768 if (from_tf == TransferFunction::Srgb || from_tf == TransferFunction::Bt709)
769 && to_tf == TransferFunction::Linear
770 {
771 Ok(vec![ConvertStep::SrgbU8ToLinearF32])
772 } else {
773 Ok(vec![ConvertStep::NaiveU8ToF32])
774 }
775 }
776 (ChannelType::F32, ChannelType::U8) => {
777 if from_tf == TransferFunction::Linear
778 && (to_tf == TransferFunction::Srgb || to_tf == TransferFunction::Bt709)
779 {
780 Ok(vec![ConvertStep::LinearF32ToSrgbU8])
781 } else {
782 Ok(vec![ConvertStep::NaiveF32ToU8])
783 }
784 }
785 (ChannelType::U16, ChannelType::F32) => {
786 match (from_tf, to_tf) {
788 (TransferFunction::Pq, TransferFunction::Linear) => {
789 Ok(vec![ConvertStep::PqU16ToLinearF32])
790 }
791 (TransferFunction::Hlg, TransferFunction::Linear) => {
792 Ok(vec![ConvertStep::HlgU16ToLinearF32])
793 }
794 _ => Ok(vec![ConvertStep::U16ToF32]),
795 }
796 }
797 (ChannelType::F32, ChannelType::U16) => {
798 match (from_tf, to_tf) {
800 (TransferFunction::Linear, TransferFunction::Pq) => {
801 Ok(vec![ConvertStep::LinearF32ToPqU16])
802 }
803 (TransferFunction::Linear, TransferFunction::Hlg) => {
804 Ok(vec![ConvertStep::LinearF32ToHlgU16])
805 }
806 _ => Ok(vec![ConvertStep::F32ToU16]),
807 }
808 }
809 (ChannelType::U16, ChannelType::U8) => {
810 if from_tf == TransferFunction::Pq && to_tf == TransferFunction::Srgb {
812 Ok(vec![
813 ConvertStep::PqU16ToLinearF32,
814 ConvertStep::LinearF32ToSrgbU8,
815 ])
816 } else if from_tf == TransferFunction::Hlg && to_tf == TransferFunction::Srgb {
817 Ok(vec![
818 ConvertStep::HlgU16ToLinearF32,
819 ConvertStep::LinearF32ToSrgbU8,
820 ])
821 } else {
822 Ok(vec![ConvertStep::U16ToU8])
823 }
824 }
825 (ChannelType::U8, ChannelType::U16) => Ok(vec![ConvertStep::U8ToU16]),
826 _ => Err(ConvertError::NoPath {
827 from: PixelDescriptor::new(from, ChannelLayout::Rgb, None, from_tf),
828 to: PixelDescriptor::new(to, ChannelLayout::Rgb, None, to_tf),
829 }),
830 }
831}
832
833pub(crate) struct ConvertScratch {
843 buf: Vec<u32>,
847}
848
849impl ConvertScratch {
850 pub(crate) fn new() -> Self {
852 Self { buf: Vec::new() }
853 }
854
855 fn ensure_capacity(&mut self, plan: &ConvertPlan, width: u32) {
858 let half_bytes = (width as usize) * plan.max_intermediate_bpp();
859 let total_u32 = (half_bytes * 2).div_ceil(4);
860 if self.buf.len() < total_u32 {
861 self.buf.resize(total_u32, 0);
862 }
863 }
864}
865
866impl core::fmt::Debug for ConvertScratch {
867 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
868 f.debug_struct("ConvertScratch")
869 .field("capacity", &self.buf.capacity())
870 .finish()
871 }
872}
873
874pub fn convert_row(plan: &ConvertPlan, src: &[u8], dst: &mut [u8], width: u32) {
880 if plan.is_identity() {
881 let len = min(src.len(), dst.len());
882 dst[..len].copy_from_slice(&src[..len]);
883 return;
884 }
885
886 if plan.steps.len() == 1 {
887 apply_step_u8(plan.steps[0], src, dst, width, plan.from, plan.to);
888 return;
889 }
890
891 let mut scratch = ConvertScratch::new();
893 convert_row_buffered(plan, src, dst, width, &mut scratch);
894}
895
896pub(crate) fn convert_row_buffered(
901 plan: &ConvertPlan,
902 src: &[u8],
903 dst: &mut [u8],
904 width: u32,
905 scratch: &mut ConvertScratch,
906) {
907 if plan.is_identity() {
908 let len = min(src.len(), dst.len());
909 dst[..len].copy_from_slice(&src[..len]);
910 return;
911 }
912
913 if plan.steps.len() == 1 {
914 apply_step_u8(plan.steps[0], src, dst, width, plan.from, plan.to);
915 return;
916 }
917
918 scratch.ensure_capacity(plan, width);
919
920 let buf_bytes: &mut [u8] = bytemuck::cast_slice_mut(&mut scratch.buf);
921 let half = buf_bytes.len() / 2;
922 let (buf_a, buf_b) = buf_bytes.split_at_mut(half);
923
924 let num_steps = plan.steps.len();
925 let mut current_desc = plan.from;
926
927 for (i, &step) in plan.steps.iter().enumerate() {
928 let is_last = i == num_steps - 1;
929 let next_desc = if is_last {
930 plan.to
931 } else {
932 intermediate_desc(current_desc, step)
933 };
934
935 let next_len = (width as usize) * next_desc.bytes_per_pixel();
936 let curr_len = (width as usize) * current_desc.bytes_per_pixel();
937
938 if i % 2 == 0 {
942 let input = if i == 0 { src } else { &buf_b[..curr_len] };
943 if is_last {
944 apply_step_u8(step, input, dst, width, current_desc, next_desc);
945 } else {
946 apply_step_u8(
947 step,
948 input,
949 &mut buf_a[..next_len],
950 width,
951 current_desc,
952 next_desc,
953 );
954 }
955 } else {
956 let input = &buf_a[..curr_len];
957 if is_last {
958 apply_step_u8(step, input, dst, width, current_desc, next_desc);
959 } else {
960 apply_step_u8(
961 step,
962 input,
963 &mut buf_b[..next_len],
964 width,
965 current_desc,
966 next_desc,
967 );
968 }
969 }
970
971 current_desc = next_desc;
972 }
973}
974
975fn are_inverse(a: ConvertStep, b: ConvertStep) -> bool {
977 matches!(
978 (a, b),
979 (ConvertStep::SwizzleBgraRgba, ConvertStep::SwizzleBgraRgba)
981 | (ConvertStep::AddAlpha, ConvertStep::DropAlpha)
983 | (ConvertStep::SrgbF32ToLinearF32, ConvertStep::LinearF32ToSrgbF32)
985 | (ConvertStep::LinearF32ToSrgbF32, ConvertStep::SrgbF32ToLinearF32)
986 | (ConvertStep::PqF32ToLinearF32, ConvertStep::LinearF32ToPqF32)
987 | (ConvertStep::LinearF32ToPqF32, ConvertStep::PqF32ToLinearF32)
988 | (ConvertStep::HlgF32ToLinearF32, ConvertStep::LinearF32ToHlgF32)
989 | (ConvertStep::LinearF32ToHlgF32, ConvertStep::HlgF32ToLinearF32)
990 | (ConvertStep::Bt709F32ToLinearF32, ConvertStep::LinearF32ToBt709F32)
991 | (ConvertStep::LinearF32ToBt709F32, ConvertStep::Bt709F32ToLinearF32)
992 | (ConvertStep::StraightToPremul, ConvertStep::PremulToStraight)
994 | (ConvertStep::PremulToStraight, ConvertStep::StraightToPremul)
995 | (ConvertStep::LinearRgbToOklab, ConvertStep::OklabToLinearRgb)
997 | (ConvertStep::OklabToLinearRgb, ConvertStep::LinearRgbToOklab)
998 | (ConvertStep::LinearRgbaToOklaba, ConvertStep::OklabaToLinearRgba)
999 | (ConvertStep::OklabaToLinearRgba, ConvertStep::LinearRgbaToOklaba)
1000 | (ConvertStep::NaiveU8ToF32, ConvertStep::NaiveF32ToU8)
1002 | (ConvertStep::NaiveF32ToU8, ConvertStep::NaiveU8ToF32)
1003 | (ConvertStep::U8ToU16, ConvertStep::U16ToU8)
1004 | (ConvertStep::U16ToU8, ConvertStep::U8ToU16)
1005 | (ConvertStep::U16ToF32, ConvertStep::F32ToU16)
1006 | (ConvertStep::F32ToU16, ConvertStep::U16ToF32)
1007 | (ConvertStep::SrgbU8ToLinearF32, ConvertStep::LinearF32ToSrgbU8)
1009 | (ConvertStep::LinearF32ToSrgbU8, ConvertStep::SrgbU8ToLinearF32)
1010 | (ConvertStep::PqU16ToLinearF32, ConvertStep::LinearF32ToPqU16)
1011 | (ConvertStep::LinearF32ToPqU16, ConvertStep::PqU16ToLinearF32)
1012 | (ConvertStep::HlgU16ToLinearF32, ConvertStep::LinearF32ToHlgU16)
1013 | (ConvertStep::LinearF32ToHlgU16, ConvertStep::HlgU16ToLinearF32)
1014 )
1015}
1016
1017fn intermediate_desc(current: PixelDescriptor, step: ConvertStep) -> PixelDescriptor {
1019 match step {
1020 ConvertStep::Identity => current,
1021 ConvertStep::SwizzleBgraRgba => {
1022 let new_layout = match current.layout() {
1023 ChannelLayout::Bgra => ChannelLayout::Rgba,
1024 ChannelLayout::Rgba => ChannelLayout::Bgra,
1025 other => other,
1026 };
1027 PixelDescriptor::new(
1028 current.channel_type(),
1029 new_layout,
1030 current.alpha(),
1031 current.transfer(),
1032 )
1033 }
1034 ConvertStep::AddAlpha => PixelDescriptor::new(
1035 current.channel_type(),
1036 ChannelLayout::Rgba,
1037 Some(AlphaMode::Straight),
1038 current.transfer(),
1039 ),
1040 ConvertStep::DropAlpha | ConvertStep::MatteComposite { .. } => PixelDescriptor::new(
1041 current.channel_type(),
1042 ChannelLayout::Rgb,
1043 None,
1044 current.transfer(),
1045 ),
1046 ConvertStep::GrayToRgb => PixelDescriptor::new(
1047 current.channel_type(),
1048 ChannelLayout::Rgb,
1049 None,
1050 current.transfer(),
1051 ),
1052 ConvertStep::GrayToRgba => PixelDescriptor::new(
1053 current.channel_type(),
1054 ChannelLayout::Rgba,
1055 Some(AlphaMode::Straight),
1056 current.transfer(),
1057 ),
1058 ConvertStep::RgbToGray | ConvertStep::RgbaToGray => PixelDescriptor::new(
1059 current.channel_type(),
1060 ChannelLayout::Gray,
1061 None,
1062 current.transfer(),
1063 ),
1064 ConvertStep::GrayAlphaToRgba => PixelDescriptor::new(
1065 current.channel_type(),
1066 ChannelLayout::Rgba,
1067 current.alpha(),
1068 current.transfer(),
1069 ),
1070 ConvertStep::GrayAlphaToRgb => PixelDescriptor::new(
1071 current.channel_type(),
1072 ChannelLayout::Rgb,
1073 None,
1074 current.transfer(),
1075 ),
1076 ConvertStep::GrayToGrayAlpha => PixelDescriptor::new(
1077 current.channel_type(),
1078 ChannelLayout::GrayAlpha,
1079 Some(AlphaMode::Straight),
1080 current.transfer(),
1081 ),
1082 ConvertStep::GrayAlphaToGray => PixelDescriptor::new(
1083 current.channel_type(),
1084 ChannelLayout::Gray,
1085 None,
1086 current.transfer(),
1087 ),
1088 ConvertStep::SrgbU8ToLinearF32
1089 | ConvertStep::NaiveU8ToF32
1090 | ConvertStep::U16ToF32
1091 | ConvertStep::PqU16ToLinearF32
1092 | ConvertStep::HlgU16ToLinearF32
1093 | ConvertStep::PqF32ToLinearF32
1094 | ConvertStep::HlgF32ToLinearF32
1095 | ConvertStep::SrgbF32ToLinearF32
1096 | ConvertStep::Bt709F32ToLinearF32 => PixelDescriptor::new(
1097 ChannelType::F32,
1098 current.layout(),
1099 current.alpha(),
1100 TransferFunction::Linear,
1101 ),
1102 ConvertStep::LinearF32ToSrgbU8 | ConvertStep::NaiveF32ToU8 | ConvertStep::U16ToU8 => {
1103 PixelDescriptor::new(
1104 ChannelType::U8,
1105 current.layout(),
1106 current.alpha(),
1107 TransferFunction::Srgb,
1108 )
1109 }
1110 ConvertStep::U8ToU16 => PixelDescriptor::new(
1111 ChannelType::U16,
1112 current.layout(),
1113 current.alpha(),
1114 current.transfer(),
1115 ),
1116 ConvertStep::F32ToU16 | ConvertStep::LinearF32ToPqU16 | ConvertStep::LinearF32ToHlgU16 => {
1117 let tf = match step {
1118 ConvertStep::LinearF32ToPqU16 => TransferFunction::Pq,
1119 ConvertStep::LinearF32ToHlgU16 => TransferFunction::Hlg,
1120 _ => current.transfer(),
1121 };
1122 PixelDescriptor::new(ChannelType::U16, current.layout(), current.alpha(), tf)
1123 }
1124 ConvertStep::LinearF32ToPqF32 => PixelDescriptor::new(
1125 ChannelType::F32,
1126 current.layout(),
1127 current.alpha(),
1128 TransferFunction::Pq,
1129 ),
1130 ConvertStep::LinearF32ToHlgF32 => PixelDescriptor::new(
1131 ChannelType::F32,
1132 current.layout(),
1133 current.alpha(),
1134 TransferFunction::Hlg,
1135 ),
1136 ConvertStep::LinearF32ToSrgbF32 => PixelDescriptor::new(
1137 ChannelType::F32,
1138 current.layout(),
1139 current.alpha(),
1140 TransferFunction::Srgb,
1141 ),
1142 ConvertStep::LinearF32ToBt709F32 => PixelDescriptor::new(
1143 ChannelType::F32,
1144 current.layout(),
1145 current.alpha(),
1146 TransferFunction::Bt709,
1147 ),
1148 ConvertStep::StraightToPremul => PixelDescriptor::new(
1149 current.channel_type(),
1150 current.layout(),
1151 Some(AlphaMode::Premultiplied),
1152 current.transfer(),
1153 ),
1154 ConvertStep::PremulToStraight => PixelDescriptor::new(
1155 current.channel_type(),
1156 current.layout(),
1157 Some(AlphaMode::Straight),
1158 current.transfer(),
1159 ),
1160 ConvertStep::LinearRgbToOklab => PixelDescriptor::new(
1161 ChannelType::F32,
1162 ChannelLayout::Oklab,
1163 None,
1164 TransferFunction::Unknown,
1165 )
1166 .with_primaries(current.primaries),
1167 ConvertStep::OklabToLinearRgb => PixelDescriptor::new(
1168 ChannelType::F32,
1169 ChannelLayout::Rgb,
1170 None,
1171 TransferFunction::Linear,
1172 )
1173 .with_primaries(current.primaries),
1174 ConvertStep::LinearRgbaToOklaba => PixelDescriptor::new(
1175 ChannelType::F32,
1176 ChannelLayout::OklabA,
1177 Some(AlphaMode::Straight),
1178 TransferFunction::Unknown,
1179 )
1180 .with_primaries(current.primaries),
1181 ConvertStep::OklabaToLinearRgba => PixelDescriptor::new(
1182 ChannelType::F32,
1183 ChannelLayout::Rgba,
1184 current.alpha(),
1185 TransferFunction::Linear,
1186 )
1187 .with_primaries(current.primaries),
1188
1189 ConvertStep::GamutMatrixRgbF32(_) => PixelDescriptor::new(
1194 ChannelType::F32,
1195 current.layout(),
1196 current.alpha(),
1197 TransferFunction::Linear,
1198 ),
1199 ConvertStep::GamutMatrixRgbaF32(_) => PixelDescriptor::new(
1200 ChannelType::F32,
1201 current.layout(),
1202 current.alpha(),
1203 TransferFunction::Linear,
1204 ),
1205 }
1206}
1207
1208#[path = "convert_kernels.rs"]
1209mod convert_kernels;
1210use convert_kernels::apply_step_u8;
1211pub(crate) use convert_kernels::{hlg_eotf, hlg_oetf, pq_eotf, pq_oetf};