use oxideav_core::{
ColorRange, ColorSignal, Error, ExecutionContext, MatrixCoefficients, PixelFormat, Result,
VideoFrame, VideoPlane,
};
use crate::cmyk;
use crate::float;
use crate::gray;
use crate::pal8;
use crate::palette::Palette;
use crate::planar8::{self, chroma_dims, tight_plane, Packed8, Planar8};
use crate::rgb;
use crate::yuv::{self, YuvMatrix};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FrameInfo {
pub format: PixelFormat,
pub width: u32,
pub height: u32,
}
impl FrameInfo {
pub const fn new(format: PixelFormat, width: u32, height: u32) -> Self {
Self {
format,
width,
height,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum Dither {
#[default]
None,
Bayer8x8,
FloydSteinberg,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum ColorSpace {
#[default]
Bt601Limited,
Bt601Full,
Bt709Limited,
Bt709Full,
Bt2020Limited,
Bt2020Full,
}
#[derive(Clone, Debug, Default)]
pub struct ConvertOptions {
pub dither: Dither,
pub palette: Option<Palette>,
pub color_space: ColorSpace,
}
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct ConvertContext {
pub signal: ColorSignal,
pub execution: ExecutionContext,
}
impl ConvertContext {
pub fn new() -> Self {
Self::default()
}
pub fn with_signal(mut self, signal: ColorSignal) -> Self {
self.signal = signal;
self
}
pub fn with_range(mut self, range: ColorRange) -> Self {
self.signal.range = range;
self
}
pub fn with_matrix(mut self, matrix: MatrixCoefficients) -> Self {
self.signal.matrix = matrix;
self
}
pub fn with_execution(mut self, execution: ExecutionContext) -> Self {
self.execution = execution;
self
}
pub fn with_threads(self, threads: usize) -> Self {
self.with_execution(ExecutionContext::with_threads(threads))
}
}
struct Resolved {
range: ColorRange,
kr_kb: Option<(f32, f32)>,
execution: ExecutionContext,
}
impl Resolved {
fn limited(&self, format_limited: bool) -> bool {
match self.range {
ColorRange::Limited => true,
ColorRange::Full => false,
_ => format_limited,
}
}
fn workers(&self, h: usize) -> usize {
self.execution
.effective_workers(h.div_ceil(planar8::MIN_BAND_ROWS))
}
fn primaries(&self, opts: &ConvertOptions) -> YuvMatrix {
match self.kr_kb {
Some((kr, kb)) => YuvMatrix {
kr,
kb,
limited: true,
},
None => YuvMatrix::from_color_space(opts.color_space),
}
}
}
fn matrix_weights(m: MatrixCoefficients) -> Result<Option<(f32, f32)>> {
Ok(Some(match m.code_point() {
2 => return Ok(None),
1 => (0.2126, 0.0722),
4 => (0.30, 0.11),
5 | 6 => (0.299, 0.114),
7 => (0.212, 0.087),
9 => (0.2627, 0.0593),
_ => {
return Err(Error::unsupported(format!(
"pixfmt: matrix coefficients {} ({}) are not implemented",
m.code_point(),
m.name().unwrap_or("reserved")
)))
}
}))
}
pub fn convert_in_place_if_same(
src: &VideoFrame,
src_info: FrameInfo,
dst_format: PixelFormat,
) -> Option<&VideoFrame> {
if src_info.format == dst_format {
Some(src)
} else {
None
}
}
pub fn convert(
src: &VideoFrame,
src_info: FrameInfo,
dst_format: PixelFormat,
opts: &ConvertOptions,
) -> Result<VideoFrame> {
convert_with(src, src_info, dst_format, opts, &ConvertContext::default())
}
pub fn convert_with(
src: &VideoFrame,
src_info: FrameInfo,
dst_format: PixelFormat,
opts: &ConvertOptions,
ctx: &ConvertContext,
) -> Result<VideoFrame> {
if src_info.format == dst_format {
return Ok(src.clone());
}
let normalized = normalize_significant_bits(src, src_info)?;
let src = normalized.as_ref().unwrap_or(src);
let src_yuv = is_yuv_carriage(src_info.format);
let dst_yuv = is_yuv_carriage(dst_format);
let frame_signal = if src_yuv {
src.color_signal().unwrap_or_default()
} else {
ColorSignal::unspecified()
};
let signal = ctx.signal.or(frame_signal);
let crosses = src_yuv != dst_yuv;
if crosses && signal.matrix == MatrixCoefficients::IDENTITY {
return identity_route(src, src_info, dst_format, opts, signal.range, ctx);
}
let kr_kb = if crosses {
matrix_weights(signal.matrix)?
} else {
None
};
let resolved = Resolved {
range: signal.range,
kr_kb,
execution: ctx.execution.clone(),
};
if let Some(op) = lookup_any(src_info.format, dst_format) {
return op.apply(src, src_info, opts, &resolved);
}
if let Some((first, pivot, second)) = lookup_staged(src_info.format, dst_format) {
let mid = first.apply(src, src_info, opts, &resolved)?;
let mid_info = FrameInfo::new(pivot, src_info.width, src_info.height);
return second.apply(&mid, mid_info, opts, &resolved);
}
Err(Error::unsupported(format!(
"pixfmt: conversion {:?} → {:?} not implemented",
src_info.format, dst_format
)))
}
fn identity_route(
src: &VideoFrame,
src_info: FrameInfo,
dst_format: PixelFormat,
opts: &ConvertOptions,
range: ColorRange,
ctx: &ConvertContext,
) -> Result<VideoFrame> {
let plain = ConvertContext::new().with_execution(ctx.execution.clone());
let (w, h) = (src_info.width, src_info.height);
if is_yuv_carriage(src_info.format) {
let (f444, bits, alpha, full_label) = identity_carrier(src_info.format);
let mut p444 = if src_info.format == f444 {
src.clone()
} else {
convert_with(src, src_info, f444, opts, &plain)?
};
let limited = match range {
ColorRange::Limited => true,
ColorRange::Full => false,
_ => !full_label,
};
let gbr = gbr_member(bits, alpha);
let planes = identity_planes(
&mut p444, w as usize, h as usize, bits, alpha, limited, true,
)?;
let gbr_frame = make_frame(src, planes);
if gbr == dst_format {
return Ok(gbr_frame);
}
return convert_with(
&gbr_frame,
FrameInfo::new(gbr, w, h),
dst_format,
opts,
&plain,
);
}
let (f444, bits, alpha, full_label) = identity_carrier(dst_format);
let gbr = gbr_member(bits, alpha);
let mut g = if src_info.format == gbr {
src.clone()
} else {
convert_with(src, src_info, gbr, opts, &plain)?
};
let limited = match range {
ColorRange::Limited => true,
ColorRange::Full => false,
_ => !full_label,
};
let planes = identity_planes(&mut g, w as usize, h as usize, bits, alpha, limited, false)?;
let yuv = make_frame(src, planes);
if f444 == dst_format {
return Ok(yuv);
}
convert_with(&yuv, FrameInfo::new(f444, w, h), dst_format, opts, &plain)
}
fn identity_carrier(f: PixelFormat) -> (PixelFormat, u32, bool, bool) {
use PixelFormat as P;
match f {
P::YuvJ420P | P::YuvJ422P | P::YuvJ444P => (P::YuvJ444P, 8, false, true),
_ => match planar_yuv_desc(f) {
Some(d) => {
let member = match (d.bits, d.alpha) {
(8, false) => P::Yuv444P,
(8, true) => P::Yuva444P,
(10, false) => P::Yuv444P10Le,
(10, true) => P::Yuva444P10Le,
(12, false) => P::Yuv444P12Le,
(12, true) => P::Yuva444P12Le,
(_, false) => P::Yuv444P16Le,
(_, true) => P::Yuva444P16Le,
};
(member, d.bits, d.alpha, false)
}
None => (P::Yuv444P, 8, false, false),
},
}
}
fn gbr_member(bits: u32, alpha: bool) -> PixelFormat {
use PixelFormat as P;
match (bits, alpha) {
(8, false) => P::Gbrp8,
(8, true) => P::Gbrap8,
(10, false) => P::Gbrp10Le,
(10, true) => P::Gbrap10Le,
(12, false) => P::Gbrp12Le,
(12, true) => P::Gbrap12Le,
(_, false) => P::Gbrp16Le,
(_, true) => P::Gbrap16Le,
}
}
fn identity_planes(
frame: &mut VideoFrame,
w: usize,
h: usize,
bits: u32,
alpha: bool,
limited: bool,
to_rgb: bool,
) -> Result<Vec<VideoPlane>> {
let sb = if bits > 8 { 2 } else { 1 };
let n = if alpha { 4 } else { 3 };
if frame.planes.len() < n {
return Err(Error::invalid(
"pixfmt: identity-coded frame is missing planes",
));
}
let mut out = Vec::with_capacity(n);
for i in 0..n {
let mut data = tight_plane(&frame.planes[i], w * sb, h)?.into_owned();
if limited && i < 3 {
if to_rgb {
range_limited_to_full(&mut data, bits);
} else {
range_full_to_limited(&mut data, bits);
}
}
out.push(VideoPlane {
stride: w * sb,
data,
});
}
Ok(out)
}
static RANGE_TABLES: [std::sync::OnceLock<Box<[u16]>>; 34] =
[const { std::sync::OnceLock::new() }; 34];
fn range_table(bits: u32, to_full: bool) -> &'static [u16] {
debug_assert!((9..=16).contains(&bits));
RANGE_TABLES[bits as usize * 2 + to_full as usize].get_or_init(|| {
let s = 1i64 << (bits - 8);
let max = (1i64 << bits) - 1;
let den = 219 * s;
(0..=max)
.map(|v| {
if to_full {
let num = (v - 16 * s) * max;
if num <= 0 {
0
} else {
((num + den / 2) / den).min(max) as u16
}
} else {
((219 * s * v + 16 * s * max + max / 2) / max).min(max) as u16
}
})
.collect()
})
}
fn range_limited_to_full(plane: &mut [u8], bits: u32) {
if bits <= 8 {
yuv::limited_to_full_luma(plane);
return;
}
let table = range_table(bits, true);
let mask = ((1u32 << bits) - 1) as u16;
for word in plane.chunks_exact_mut(2) {
let v = u16::from_le_bytes([word[0], word[1]]) & mask;
word.copy_from_slice(&table[v as usize].to_le_bytes());
}
}
fn range_full_to_limited(plane: &mut [u8], bits: u32) {
if bits <= 8 {
yuv::full_to_limited_luma(plane);
return;
}
let table = range_table(bits, false);
let mask = ((1u32 << bits) - 1) as u16;
for word in plane.chunks_exact_mut(2) {
let v = u16::from_le_bytes([word[0], word[1]]) & mask;
word.copy_from_slice(&table[v as usize].to_le_bytes());
}
}
fn widen_bits(v: u32, from: u32, to: u32) -> u32 {
let mut out = v << (to - from);
let mut fill = to - from;
while fill > 0 {
let take = fill.min(from);
out |= (v >> (from - take)) << (fill - take);
fill -= take;
}
out
}
fn normalize_significant_bits(src: &VideoFrame, src_info: FrameInfo) -> Result<Option<VideoFrame>> {
let Some(record) = src.significant_bits() else {
return Ok(None);
};
let fmt = src_info.format;
if fmt == PixelFormat::Pal8 {
return Ok(None);
}
if fmt.is_float() {
return Ok(None);
}
let nominal = crate::format_info::FormatInfo::of(fmt).bit_depth as u32;
let plane_count = src.image_plane_count();
let mut needs_work = false;
for (i, &b) in record.iter().take(plane_count).enumerate() {
let b = b as u32;
if b == 0 || b > nominal {
return Err(Error::invalid(format!(
"pixfmt: significant-bits record byte {i} = {b} out of range 1..={nominal} for {fmt:?}"
)));
}
if b < nominal {
needs_work = true;
}
}
if !needs_work {
return Ok(None);
}
let wide_words = nominal > 8;
let mut planes = Vec::with_capacity(plane_count);
for (i, plane) in src.image_planes().iter().enumerate() {
let b = record.get(i).map(|&b| b as u32).unwrap_or(nominal);
let mut data = plane.data.clone();
if b < nominal {
if wide_words {
let mask = (1u32 << b) - 1;
for word in data.chunks_exact_mut(2) {
let v = u16::from_le_bytes([word[0], word[1]]) as u32 & mask;
let out = widen_bits(v, b, nominal) as u16;
word.copy_from_slice(&out.to_le_bytes());
}
} else {
let mask = (1u32 << b) - 1;
for byte in data.iter_mut() {
let v = *byte as u32 & mask;
*byte = widen_bits(v, b, 8) as u8;
}
}
}
planes.push(VideoPlane {
stride: plane.stride,
data,
});
}
Ok(Some(VideoFrame {
pts: src.pts,
planes,
}))
}
pub fn supports(src: PixelFormat, dst: PixelFormat) -> bool {
src == dst || lookup_any(src, dst).is_some() || lookup_staged(src, dst).is_some()
}
pub fn supports_direct(src: PixelFormat, dst: PixelFormat) -> bool {
src == dst || lookup_any(src, dst).is_some()
}
pub fn staged_pivot(src: PixelFormat, dst: PixelFormat) -> Option<PixelFormat> {
if supports_direct(src, dst) {
return None;
}
lookup_staged(src, dst).map(|(_, pivot, _)| pivot)
}
const YUV_PIVOTS: &[PixelFormat] = &[
PixelFormat::Yuv444P,
PixelFormat::Yuv422P,
PixelFormat::Yuv420P,
];
const YUV_PIVOTS_DEEP: &[PixelFormat] = &[
PixelFormat::Yuva444P16Le,
PixelFormat::Yuva422P16Le,
PixelFormat::Yuva420P16Le,
PixelFormat::Yuv444P16Le,
PixelFormat::Yuv422P16Le,
PixelFormat::Yuv420P16Le,
PixelFormat::Yuv444P,
PixelFormat::Yuv422P,
PixelFormat::Yuv420P,
];
const RGB_PIVOTS: &[PixelFormat] = &[
PixelFormat::Rgba,
PixelFormat::Rgb24,
PixelFormat::Rgb48Le,
PixelFormat::Rgba64Le,
PixelFormat::Gray8,
];
const RGB_PIVOTS_DEEP: &[PixelFormat] = &[
PixelFormat::Rgba64Le,
PixelFormat::Rgb48Le,
PixelFormat::Rgba,
PixelFormat::Rgb24,
PixelFormat::Gray8,
];
const YUV_PIVOTS_DEEP_NO_ALPHA: &[PixelFormat] = &[
PixelFormat::Yuv444P16Le,
PixelFormat::Yuv422P16Le,
PixelFormat::Yuv420P16Le,
PixelFormat::Yuva444P16Le,
PixelFormat::Yuva422P16Le,
PixelFormat::Yuva420P16Le,
PixelFormat::Yuv444P,
PixelFormat::Yuv422P,
PixelFormat::Yuv420P,
];
const RGB_PIVOTS_DEEP_NO_ALPHA: &[PixelFormat] = &[
PixelFormat::Rgb48Le,
PixelFormat::Rgba64Le,
PixelFormat::Rgba,
PixelFormat::Rgb24,
PixelFormat::Gray8,
];
fn is_yuv_carriage(f: PixelFormat) -> bool {
use PixelFormat as P;
matches!(
f,
P::Yuv420P
| P::Yuv422P
| P::Yuv444P
| P::Yuv411P
| P::Yuv420P10Le
| P::Yuv422P10Le
| P::Yuv444P10Le
| P::Yuv420P12Le
| P::Yuv422P12Le
| P::Yuv444P12Le
| P::Yuv420P16Le
| P::Yuv422P16Le
| P::Yuv444P16Le
| P::YuvJ420P
| P::YuvJ422P
| P::YuvJ444P
| P::Nv12
| P::Nv21
| P::Yuva420P
| P::Yuva422P
| P::Yuva444P
| P::Yuva422P10Le
| P::Yuva422P12Le
| P::Yuva422P16Le
| P::Yuva444P10Le
| P::Yuva444P12Le
| P::Yuva444P16Le
| P::Yuva420P10Le
| P::Yuva420P12Le
| P::Yuva420P16Le
| P::Yuyv422
| P::Uyvy422
| P::Yuv440P
| P::Yuv440P10Le
| P::Yuv440P12Le
| P::Yuv440P16Le
)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct PlanarYuv {
wsub: usize,
hsub: usize,
bits: u32,
alpha: bool,
}
impl PlanarYuv {
const fn sample_bytes(&self) -> usize {
if self.bits > 8 {
2
} else {
1
}
}
}
fn planar_yuv_desc(f: PixelFormat) -> Option<PlanarYuv> {
use PixelFormat as P;
let (wsub, hsub, bits, alpha) = match f {
P::Yuv420P => (2, 2, 8, false),
P::Yuv422P => (2, 1, 8, false),
P::Yuv444P => (1, 1, 8, false),
P::Yuv420P10Le => (2, 2, 10, false),
P::Yuv422P10Le => (2, 1, 10, false),
P::Yuv444P10Le => (1, 1, 10, false),
P::Yuv420P12Le => (2, 2, 12, false),
P::Yuv422P12Le => (2, 1, 12, false),
P::Yuv444P12Le => (1, 1, 12, false),
P::Yuv420P16Le => (2, 2, 16, false),
P::Yuv422P16Le => (2, 1, 16, false),
P::Yuv444P16Le => (1, 1, 16, false),
P::Yuva420P => (2, 2, 8, true),
P::Yuva422P => (2, 1, 8, true),
P::Yuva444P => (1, 1, 8, true),
P::Yuva422P10Le => (2, 1, 10, true),
P::Yuva422P12Le => (2, 1, 12, true),
P::Yuva422P16Le => (2, 1, 16, true),
P::Yuva444P10Le => (1, 1, 10, true),
P::Yuva444P12Le => (1, 1, 12, true),
P::Yuva444P16Le => (1, 1, 16, true),
P::Yuva420P10Le => (2, 2, 10, true),
P::Yuva420P12Le => (2, 2, 12, true),
P::Yuva420P16Le => (2, 2, 16, true),
P::Yuv440P => (1, 2, 8, false),
P::Yuv440P10Le => (1, 2, 10, false),
P::Yuv440P12Le => (1, 2, 12, false),
P::Yuv440P16Le => (1, 2, 16, false),
_ => return None,
};
Some(PlanarYuv {
wsub,
hsub,
bits,
alpha,
})
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum FloatLayout {
Gray,
Rgb,
Rgba,
Gbrp,
Gbrap,
}
impl FloatLayout {
const fn has_alpha(self) -> bool {
matches!(self, Self::Rgba | Self::Gbrap)
}
const fn is_planar(self) -> bool {
matches!(self, Self::Gbrp | Self::Gbrap)
}
const fn packed_words(self) -> usize {
match self {
Self::Gray => 1,
Self::Rgb => 3,
Self::Rgba => 4,
Self::Gbrp | Self::Gbrap => 1,
}
}
const fn plane_count(self) -> usize {
match self {
Self::Gbrp => 3,
Self::Gbrap => 4,
_ => 1,
}
}
}
fn float_layout(f: PixelFormat) -> Option<FloatLayout> {
use PixelFormat as P;
Some(match f {
P::GrayF32Le => FloatLayout::Gray,
P::RgbF32Le => FloatLayout::Rgb,
P::RgbaF32Le => FloatLayout::Rgba,
P::GbrpF32Le => FloatLayout::Gbrp,
P::GbrapF32Le => FloatLayout::Gbrap,
_ => return None,
})
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum IntShape {
Gray,
PackedRgb,
PlanarGbr,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct IntLayout {
shape: IntShape,
bits: u32,
alpha: bool,
}
impl IntLayout {
const fn sample_bytes(&self) -> usize {
if self.bits > 8 {
2
} else {
1
}
}
const fn comps(&self) -> usize {
match (self.shape, self.alpha) {
(IntShape::Gray, _) => 1,
(IntShape::PackedRgb, false) => 3,
(IntShape::PackedRgb, true) => 4,
(IntShape::PlanarGbr, _) => 1,
}
}
const fn plane_count(&self) -> usize {
match (self.shape, self.alpha) {
(IntShape::PlanarGbr, false) => 3,
(IntShape::PlanarGbr, true) => 4,
_ => 1,
}
}
}
fn int_layout(f: PixelFormat) -> Option<IntLayout> {
use PixelFormat as P;
let (shape, bits, alpha) = match f {
P::Gray8 => (IntShape::Gray, 8, false),
P::Gray10Le => (IntShape::Gray, 10, false),
P::Gray12Le => (IntShape::Gray, 12, false),
P::Gray16Le => (IntShape::Gray, 16, false),
P::Rgb24 => (IntShape::PackedRgb, 8, false),
P::Rgba => (IntShape::PackedRgb, 8, true),
P::Rgb48Le => (IntShape::PackedRgb, 16, false),
P::Rgba64Le => (IntShape::PackedRgb, 16, true),
P::Gbrp8 => (IntShape::PlanarGbr, 8, false),
P::Gbrap8 => (IntShape::PlanarGbr, 8, true),
P::Gbrp10Le => (IntShape::PlanarGbr, 10, false),
P::Gbrap10Le => (IntShape::PlanarGbr, 10, true),
P::Gbrp12Le => (IntShape::PlanarGbr, 12, false),
P::Gbrap12Le => (IntShape::PlanarGbr, 12, true),
P::Gbrp14Le => (IntShape::PlanarGbr, 14, false),
P::Gbrap14Le => (IntShape::PlanarGbr, 14, true),
P::Gbrp16Le => (IntShape::PlanarGbr, 16, false),
P::Gbrap16Le => (IntShape::PlanarGbr, 16, true),
_ => return None,
};
Some(IntLayout { shape, bits, alpha })
}
fn lookup_computed(src: PixelFormat, dst: PixelFormat) -> Option<ConvertOp> {
use PixelFormat as P;
if let Some(fs) = float_layout(src) {
if let Some(fd) = float_layout(dst) {
return Some(ConvertOp::FloatToFloat { src: fs, dst: fd });
}
if let Some(il) = int_layout(dst) {
return Some(ConvertOp::FloatToInt { src: fs, dst: il });
}
return match planar_yuv_desc(dst) {
Some(d) if d.bits == 16 => Some(ConvertOp::FloatToDeepYuv { src: fs, dst: d }),
_ => None,
};
}
if let Some(fd) = float_layout(dst) {
if let Some(il) = int_layout(src) {
return Some(ConvertOp::IntToFloat { src: il, dst: fd });
}
return match planar_yuv_desc(src) {
Some(s) if s.bits == 16 => Some(ConvertOp::DeepYuvToFloat { src: s, dst: fd }),
_ => None,
};
}
match (planar_yuv_desc(src), planar_yuv_desc(dst)) {
(Some(s), Some(d)) => Some(ConvertOp::PlanarFamily { src: s, dst: d }),
(Some(s), None) => match dst {
P::Rgba => Some(ConvertOp::PlanarFamilyToRgb {
src: s,
alpha: true,
}),
P::Rgb24 => Some(ConvertOp::PlanarFamilyToRgb {
src: s,
alpha: false,
}),
P::Gray8 => Some(ConvertOp::PlanarFamilyToGray { src: s }),
P::Gray10Le | P::Gray12Le | P::Gray16Le => Some(ConvertOp::PlanarFamilyToDeepGray {
src: s,
bits: deep_gray_bits(dst),
}),
P::Rgb48Le => Some(ConvertOp::FamilyToPacked16 {
src: s,
alpha: false,
}),
P::Rgba64Le => Some(ConvertOp::FamilyToPacked16 {
src: s,
alpha: true,
}),
_ => None,
},
(None, Some(d)) => match src {
P::Rgba => Some(ConvertOp::RgbToPlanarFamily {
dst: d,
alpha_in: true,
}),
P::Rgb24 => Some(ConvertOp::RgbToPlanarFamily {
dst: d,
alpha_in: false,
}),
P::Gray8 => Some(ConvertOp::GrayToPlanarFamily { dst: d }),
P::Gray10Le | P::Gray12Le | P::Gray16Le => Some(ConvertOp::DeepGrayToPlanarFamily {
bits: deep_gray_bits(src),
dst: d,
}),
P::Rgb48Le => Some(ConvertOp::Packed16ToFamily {
dst: d,
alpha_in: false,
}),
P::Rgba64Le => Some(ConvertOp::Packed16ToFamily {
dst: d,
alpha_in: true,
}),
_ => None,
},
(None, None) => match (int_layout(src), int_layout(dst)) {
(Some(il), _) if il.shape == IntShape::PlanarGbr => match dst {
P::Gray10Le | P::Gray12Le | P::Gray16Le => Some(ConvertOp::GbrToDeepGray {
src: il,
bits: deep_gray_bits(dst),
}),
_ => None,
},
(_, Some(il)) if il.shape == IntShape::PlanarGbr => match src {
P::Gray10Le | P::Gray12Le | P::Gray16Le => Some(ConvertOp::DeepGrayToGbr {
bits: deep_gray_bits(src),
dst: il,
}),
_ => None,
},
_ => None,
},
}
}
fn lookup_any(src: PixelFormat, dst: PixelFormat) -> Option<ConvertOp> {
lookup(src, dst)
.copied()
.or_else(|| lookup_computed(src, dst))
}
fn lookup_staged(
src: PixelFormat,
dst: PixelFormat,
) -> Option<(ConvertOp, PixelFormat, ConvertOp)> {
let yuv_first = is_yuv_carriage(src) && is_yuv_carriage(dst);
let deep = crate::format_info::FormatInfo::of(src).bit_depth > 8
|| crate::format_info::FormatInfo::of(dst).bit_depth > 8;
let no_alpha = !crate::format_info::FormatInfo::of(src).has_alpha
&& !crate::format_info::FormatInfo::of(dst).has_alpha;
let rgb_pivots = match (deep, no_alpha) {
(true, true) => RGB_PIVOTS_DEEP_NO_ALPHA,
(true, false) => RGB_PIVOTS_DEEP,
(false, _) => RGB_PIVOTS,
};
let yuv_pivots = match (deep, no_alpha) {
(true, true) => YUV_PIVOTS_DEEP_NO_ALPHA,
(true, false) => YUV_PIVOTS_DEEP,
(false, _) => YUV_PIVOTS,
};
let (a, b) = if yuv_first || (deep && (is_yuv_carriage(src) || is_yuv_carriage(dst))) {
(yuv_pivots, rgb_pivots)
} else {
(rgb_pivots, yuv_pivots)
};
for &pivot in a.iter().chain(b.iter()) {
if pivot == src || pivot == dst {
continue;
}
if let (Some(first), Some(second)) = (lookup_any(src, pivot), lookup_any(pivot, dst)) {
return Some((first, pivot, second));
}
}
None
}
#[rustfmt::skip]
const TABLE: &[(PixelFormat, PixelFormat, ConvertOp)] = {
use ConvertOp::*;
use PixelFormat as P;
&[
(P::Rgb24, P::Bgr24, Swizzle3 { src: rgb::RGB_POS, dst: rgb::BGR_POS }),
(P::Bgr24, P::Rgb24, Swizzle3 { src: rgb::BGR_POS, dst: rgb::RGB_POS }),
(P::Rgba, P::Bgra, Swizzle4 { src: rgb::RGBA_POS, dst: rgb::BGRA_POS }),
(P::Bgra, P::Rgba, Swizzle4 { src: rgb::BGRA_POS, dst: rgb::RGBA_POS }),
(P::Rgba, P::Argb, Swizzle4 { src: rgb::RGBA_POS, dst: rgb::ARGB_POS }),
(P::Argb, P::Rgba, Swizzle4 { src: rgb::ARGB_POS, dst: rgb::RGBA_POS }),
(P::Rgba, P::Abgr, Swizzle4 { src: rgb::RGBA_POS, dst: rgb::ABGR_POS }),
(P::Abgr, P::Rgba, Swizzle4 { src: rgb::ABGR_POS, dst: rgb::RGBA_POS }),
(P::Bgra, P::Argb, Swizzle4 { src: rgb::BGRA_POS, dst: rgb::ARGB_POS }),
(P::Argb, P::Bgra, Swizzle4 { src: rgb::ARGB_POS, dst: rgb::BGRA_POS }),
(P::Bgra, P::Abgr, Swizzle4 { src: rgb::BGRA_POS, dst: rgb::ABGR_POS }),
(P::Abgr, P::Bgra, Swizzle4 { src: rgb::ABGR_POS, dst: rgb::BGRA_POS }),
(P::Argb, P::Abgr, Swizzle4 { src: rgb::ARGB_POS, dst: rgb::ABGR_POS }),
(P::Abgr, P::Argb, Swizzle4 { src: rgb::ABGR_POS, dst: rgb::ARGB_POS }),
(P::Rgb24, P::Rgba, Promote3To4 { src: rgb::RGB_POS, dst: rgb::RGBA_POS }),
(P::Rgb24, P::Bgra, Promote3To4 { src: rgb::RGB_POS, dst: rgb::BGRA_POS }),
(P::Rgb24, P::Argb, Promote3To4 { src: rgb::RGB_POS, dst: rgb::ARGB_POS }),
(P::Rgb24, P::Abgr, Promote3To4 { src: rgb::RGB_POS, dst: rgb::ABGR_POS }),
(P::Bgr24, P::Rgba, Promote3To4 { src: rgb::BGR_POS, dst: rgb::RGBA_POS }),
(P::Bgr24, P::Bgra, Promote3To4 { src: rgb::BGR_POS, dst: rgb::BGRA_POS }),
(P::Bgr24, P::Argb, Promote3To4 { src: rgb::BGR_POS, dst: rgb::ARGB_POS }),
(P::Bgr24, P::Abgr, Promote3To4 { src: rgb::BGR_POS, dst: rgb::ABGR_POS }),
(P::Rgba, P::Rgb24, Demote4To3 { src: rgb::RGBA_POS, dst: rgb::RGB_POS }),
(P::Rgba, P::Bgr24, Demote4To3 { src: rgb::RGBA_POS, dst: rgb::BGR_POS }),
(P::Bgra, P::Rgb24, Demote4To3 { src: rgb::BGRA_POS, dst: rgb::RGB_POS }),
(P::Bgra, P::Bgr24, Demote4To3 { src: rgb::BGRA_POS, dst: rgb::BGR_POS }),
(P::Argb, P::Rgb24, Demote4To3 { src: rgb::ARGB_POS, dst: rgb::RGB_POS }),
(P::Argb, P::Bgr24, Demote4To3 { src: rgb::ARGB_POS, dst: rgb::BGR_POS }),
(P::Abgr, P::Rgb24, Demote4To3 { src: rgb::ABGR_POS, dst: rgb::RGB_POS }),
(P::Abgr, P::Bgr24, Demote4To3 { src: rgb::ABGR_POS, dst: rgb::BGR_POS }),
(P::Rgb48Le, P::Rgb24, Rgb48ToRgb24),
(P::Rgb24, P::Rgb48Le, Rgb24ToRgb48),
(P::Rgba64Le, P::Rgba, Rgba64ToRgba),
(P::Rgba, P::Rgba64Le, RgbaToRgba64),
(P::Gray8, P::Rgb24, Gray8ToPacked3),
(P::Gray8, P::Bgr24, Gray8ToPacked3),
(P::Gray8, P::Rgba, Gray8ToPacked4 { alpha_first: false }),
(P::Gray8, P::Bgra, Gray8ToPacked4 { alpha_first: false }),
(P::Gray8, P::Argb, Gray8ToPacked4 { alpha_first: true }),
(P::Gray8, P::Abgr, Gray8ToPacked4 { alpha_first: true }),
(P::Gray16Le, P::Gray8, Gray16ToGray8),
(P::Gray8, P::Gray16Le, Gray8ToGray16),
(P::MonoBlack, P::Gray8, MonoToGray { black_is_zero: true }),
(P::MonoWhite, P::Gray8, MonoToGray { black_is_zero: false }),
(P::Gray8, P::MonoBlack, GrayToMono { black_is_zero: true }),
(P::Gray8, P::MonoWhite, GrayToMono { black_is_zero: false }),
(P::Rgb24, P::Gray8, RgbToGray { alpha_in: false }),
(P::Rgba, P::Gray8, RgbToGray { alpha_in: true }),
(P::Ya8, P::Gray8, Ya8ToGray8),
(P::Gray8, P::Ya8, Gray8ToYa8),
(P::Ya8, P::Rgb24, Ya8ToRgb24),
(P::Ya8, P::Rgba, Ya8ToRgba),
(P::Rgb24, P::Ya8, Rgb24ToYa8),
(P::Rgba, P::Ya8, RgbaToYa8),
(P::Ya16Le, P::Ya8, Ya16ToYa8),
(P::Ya8, P::Ya16Le, Ya8ToYa16),
(P::Ya16Le, P::Gray16Le, Ya16ToGray16),
(P::Gray16Le, P::Ya16Le, Gray16ToYa16),
(P::Ya16Le, P::Gray8, Ya16ToGray8),
(P::Gray8, P::Ya16Le, Gray8ToYa16),
(P::Ya16Le, P::Rgba64Le, Ya16ToRgba64),
(P::Rgba64Le, P::Ya16Le, Rgba64ToYa16),
(P::Ya16Le, P::Rgba, Ya16ToPacked8 { alpha: true }),
(P::Ya16Le, P::Rgb24, Ya16ToPacked8 { alpha: false }),
(P::Rgba, P::Ya16Le, Packed8ToYa16 { alpha_in: true }),
(P::Rgb24, P::Ya16Le, Packed8ToYa16 { alpha_in: false }),
(P::Yuv420P, P::Gray8, YuvLumaToGray { full_range: false }),
(P::Yuv422P, P::Gray8, YuvLumaToGray { full_range: false }),
(P::Yuv444P, P::Gray8, YuvLumaToGray { full_range: false }),
(P::Yuv411P, P::Gray8, YuvLumaToGray { full_range: false }),
(P::Nv12, P::Gray8, YuvLumaToGray { full_range: false }),
(P::Nv21, P::Gray8, YuvLumaToGray { full_range: false }),
(P::Yuva420P, P::Gray8, YuvLumaToGray { full_range: false }),
(P::Yuva422P, P::Gray8, YuvLumaToGray { full_range: false }),
(P::Yuva444P, P::Gray8, YuvLumaToGray { full_range: false }),
(P::YuvJ420P, P::Gray8, YuvLumaToGray { full_range: true }),
(P::YuvJ422P, P::Gray8, YuvLumaToGray { full_range: true }),
(P::YuvJ444P, P::Gray8, YuvLumaToGray { full_range: true }),
(P::Gray8, P::Yuv420P, GrayToYuvPlanar { wsub: 2, hsub: 2, full_range: false }),
(P::Gray8, P::Yuv422P, GrayToYuvPlanar { wsub: 2, hsub: 1, full_range: false }),
(P::Gray8, P::Yuv444P, GrayToYuvPlanar { wsub: 1, hsub: 1, full_range: false }),
(P::Gray8, P::Yuv411P, GrayToYuvPlanar { wsub: 4, hsub: 1, full_range: false }),
(P::Gray8, P::YuvJ420P, GrayToYuvPlanar { wsub: 2, hsub: 2, full_range: true }),
(P::Gray8, P::YuvJ422P, GrayToYuvPlanar { wsub: 2, hsub: 1, full_range: true }),
(P::Gray8, P::YuvJ444P, GrayToYuvPlanar { wsub: 1, hsub: 1, full_range: true }),
(P::Gray8, P::Nv12, GrayToNv),
(P::Gray8, P::Nv21, GrayToNv),
(P::Yuv420P, P::Rgb24, YuvToRgb { wsub: 2, hsub: 2, alpha: false, full_range: false }),
(P::Yuv422P, P::Rgb24, YuvToRgb { wsub: 2, hsub: 1, alpha: false, full_range: false }),
(P::Yuv444P, P::Rgb24, YuvToRgb { wsub: 1, hsub: 1, alpha: false, full_range: false }),
(P::Yuv420P, P::Rgba, YuvToRgb { wsub: 2, hsub: 2, alpha: true, full_range: false }),
(P::Yuv422P, P::Rgba, YuvToRgb { wsub: 2, hsub: 1, alpha: true, full_range: false }),
(P::Yuv444P, P::Rgba, YuvToRgb { wsub: 1, hsub: 1, alpha: true, full_range: false }),
(P::Rgb24, P::Yuv420P, RgbToYuv { wsub: 2, hsub: 2, alpha_in: false, full_range: false }),
(P::Rgb24, P::Yuv422P, RgbToYuv { wsub: 2, hsub: 1, alpha_in: false, full_range: false }),
(P::Rgb24, P::Yuv444P, RgbToYuv { wsub: 1, hsub: 1, alpha_in: false, full_range: false }),
(P::Rgba, P::Yuv420P, RgbToYuv { wsub: 2, hsub: 2, alpha_in: true, full_range: false }),
(P::Rgba, P::Yuv422P, RgbToYuv { wsub: 2, hsub: 1, alpha_in: true, full_range: false }),
(P::Rgba, P::Yuv444P, RgbToYuv { wsub: 1, hsub: 1, alpha_in: true, full_range: false }),
(P::YuvJ420P, P::Rgb24, YuvToRgb { wsub: 2, hsub: 2, alpha: false, full_range: true }),
(P::YuvJ422P, P::Rgb24, YuvToRgb { wsub: 2, hsub: 1, alpha: false, full_range: true }),
(P::YuvJ444P, P::Rgb24, YuvToRgb { wsub: 1, hsub: 1, alpha: false, full_range: true }),
(P::YuvJ420P, P::Rgba, YuvToRgb { wsub: 2, hsub: 2, alpha: true, full_range: true }),
(P::YuvJ422P, P::Rgba, YuvToRgb { wsub: 2, hsub: 1, alpha: true, full_range: true }),
(P::YuvJ444P, P::Rgba, YuvToRgb { wsub: 1, hsub: 1, alpha: true, full_range: true }),
(P::Rgb24, P::YuvJ420P, RgbToYuv { wsub: 2, hsub: 2, alpha_in: false, full_range: true }),
(P::Rgb24, P::YuvJ422P, RgbToYuv { wsub: 2, hsub: 1, alpha_in: false, full_range: true }),
(P::Rgb24, P::YuvJ444P, RgbToYuv { wsub: 1, hsub: 1, alpha_in: false, full_range: true }),
(P::Rgba, P::YuvJ420P, RgbToYuv { wsub: 2, hsub: 2, alpha_in: true, full_range: true }),
(P::Rgba, P::YuvJ422P, RgbToYuv { wsub: 2, hsub: 1, alpha_in: true, full_range: true }),
(P::Rgba, P::YuvJ444P, RgbToYuv { wsub: 1, hsub: 1, alpha_in: true, full_range: true }),
(P::YuvJ420P, P::Yuv420P, RescaleRange { wsub: 2, hsub: 2, to_full: false }),
(P::YuvJ422P, P::Yuv422P, RescaleRange { wsub: 2, hsub: 1, to_full: false }),
(P::YuvJ444P, P::Yuv444P, RescaleRange { wsub: 1, hsub: 1, to_full: false }),
(P::Yuv420P, P::YuvJ420P, RescaleRange { wsub: 2, hsub: 2, to_full: true }),
(P::Yuv422P, P::YuvJ422P, RescaleRange { wsub: 2, hsub: 1, to_full: true }),
(P::Yuv444P, P::YuvJ444P, RescaleRange { wsub: 1, hsub: 1, to_full: true }),
(P::Nv12, P::Yuv420P, NvToYuv420p { is_nv12: true }),
(P::Nv21, P::Yuv420P, NvToYuv420p { is_nv12: false }),
(P::Yuv420P, P::Nv12, Yuv420pToNv { is_nv12: true }),
(P::Yuv420P, P::Nv21, Yuv420pToNv { is_nv12: false }),
(P::Nv12, P::Rgb24, NvToRgb { is_nv12: true, alpha: false }),
(P::Nv21, P::Rgb24, NvToRgb { is_nv12: false, alpha: false }),
(P::Nv12, P::Rgba, NvToRgb { is_nv12: true, alpha: true }),
(P::Nv21, P::Rgba, NvToRgb { is_nv12: false, alpha: true }),
(P::Rgb24, P::Nv12, RgbToNv { is_nv12: true, alpha_in: false }),
(P::Rgb24, P::Nv21, RgbToNv { is_nv12: false, alpha_in: false }),
(P::Rgba, P::Nv12, RgbToNv { is_nv12: true, alpha_in: true }),
(P::Rgba, P::Nv21, RgbToNv { is_nv12: false, alpha_in: true }),
(P::Yuv420P, P::Yuv422P, ChromaResample { src_wsub: 2, src_hsub: 2, dst_wsub: 2, dst_hsub: 1 }),
(P::Yuv420P, P::Yuv444P, ChromaResample { src_wsub: 2, src_hsub: 2, dst_wsub: 1, dst_hsub: 1 }),
(P::Yuv422P, P::Yuv420P, ChromaResample { src_wsub: 2, src_hsub: 1, dst_wsub: 2, dst_hsub: 2 }),
(P::Yuv422P, P::Yuv444P, ChromaResample { src_wsub: 2, src_hsub: 1, dst_wsub: 1, dst_hsub: 1 }),
(P::Yuv444P, P::Yuv420P, ChromaResample { src_wsub: 1, src_hsub: 1, dst_wsub: 2, dst_hsub: 2 }),
(P::Yuv444P, P::Yuv422P, ChromaResample { src_wsub: 1, src_hsub: 1, dst_wsub: 2, dst_hsub: 1 }),
(P::YuvJ420P, P::YuvJ422P, ChromaResample { src_wsub: 2, src_hsub: 2, dst_wsub: 2, dst_hsub: 1 }),
(P::YuvJ420P, P::YuvJ444P, ChromaResample { src_wsub: 2, src_hsub: 2, dst_wsub: 1, dst_hsub: 1 }),
(P::YuvJ422P, P::YuvJ420P, ChromaResample { src_wsub: 2, src_hsub: 1, dst_wsub: 2, dst_hsub: 2 }),
(P::YuvJ422P, P::YuvJ444P, ChromaResample { src_wsub: 2, src_hsub: 1, dst_wsub: 1, dst_hsub: 1 }),
(P::YuvJ444P, P::YuvJ420P, ChromaResample { src_wsub: 1, src_hsub: 1, dst_wsub: 2, dst_hsub: 2 }),
(P::YuvJ444P, P::YuvJ422P, ChromaResample { src_wsub: 1, src_hsub: 1, dst_wsub: 2, dst_hsub: 1 }),
(P::Yuyv422, P::Yuv422P, Packed422ToYuv422p { is_yuyv: true }),
(P::Uyvy422, P::Yuv422P, Packed422ToYuv422p { is_yuyv: false }),
(P::Yuv422P, P::Yuyv422, Yuv422pToPacked422 { is_yuyv: true }),
(P::Yuv422P, P::Uyvy422, Yuv422pToPacked422 { is_yuyv: false }),
(P::Yuyv422, P::Uyvy422, Packed422Swap),
(P::Uyvy422, P::Yuyv422, Packed422Swap),
(P::Yuyv422, P::Rgb24, Packed422ToRgb { is_yuyv: true, alpha: false }),
(P::Uyvy422, P::Rgb24, Packed422ToRgb { is_yuyv: false, alpha: false }),
(P::Yuyv422, P::Rgba, Packed422ToRgb { is_yuyv: true, alpha: true }),
(P::Uyvy422, P::Rgba, Packed422ToRgb { is_yuyv: false, alpha: true }),
(P::Rgb24, P::Yuyv422, RgbToPacked422 { is_yuyv: true, alpha_in: false }),
(P::Rgb24, P::Uyvy422, RgbToPacked422 { is_yuyv: false, alpha_in: false }),
(P::Rgba, P::Yuyv422, RgbToPacked422 { is_yuyv: true, alpha_in: true }),
(P::Rgba, P::Uyvy422, RgbToPacked422 { is_yuyv: false, alpha_in: true }),
(P::Pal8, P::Rgb24, Pal8ToRgb { alpha: false }),
(P::Pal8, P::Rgba, Pal8ToRgb { alpha: true }),
(P::Rgb24, P::Pal8, RgbToPal8 { alpha_in: false }),
(P::Rgba, P::Pal8, RgbToPal8 { alpha_in: true }),
(P::Cmyk, P::Rgb24, CmykToRgb { alpha: false, inverted: false }),
(P::Cmyk, P::Rgba, CmykToRgb { alpha: true, inverted: false }),
(P::Rgb24, P::Cmyk, RgbToCmyk { alpha_in: false, inverted: false }),
(P::Rgba, P::Cmyk, RgbToCmyk { alpha_in: true, inverted: false }),
(P::CmykInverted, P::Rgb24, CmykToRgb { alpha: false, inverted: true }),
(P::CmykInverted, P::Rgba, CmykToRgb { alpha: true, inverted: true }),
(P::Rgb24, P::CmykInverted, RgbToCmyk { alpha_in: false, inverted: true }),
(P::Rgba, P::CmykInverted, RgbToCmyk { alpha_in: true, inverted: true }),
(P::Cmyk, P::CmykInverted, CmykComplement),
(P::CmykInverted, P::Cmyk, CmykComplement),
(P::Yuv411P, P::Yuv444P, ChromaResample { src_wsub: 4, src_hsub: 1, dst_wsub: 1, dst_hsub: 1 }),
(P::Yuv411P, P::Yuv422P, ChromaResample { src_wsub: 4, src_hsub: 1, dst_wsub: 2, dst_hsub: 1 }),
(P::Yuv411P, P::Yuv420P, ChromaResample { src_wsub: 4, src_hsub: 1, dst_wsub: 2, dst_hsub: 2 }),
(P::Yuv444P, P::Yuv411P, ChromaResample { src_wsub: 1, src_hsub: 1, dst_wsub: 4, dst_hsub: 1 }),
(P::Yuv422P, P::Yuv411P, ChromaResample { src_wsub: 2, src_hsub: 1, dst_wsub: 4, dst_hsub: 1 }),
(P::Yuv420P, P::Yuv411P, ChromaResample { src_wsub: 2, src_hsub: 2, dst_wsub: 4, dst_hsub: 1 }),
(P::Yuv411P, P::Yuv440P, ChromaResample { src_wsub: 4, src_hsub: 1, dst_wsub: 1, dst_hsub: 2 }),
(P::Yuv440P, P::Yuv411P, ChromaResample { src_wsub: 1, src_hsub: 2, dst_wsub: 4, dst_hsub: 1 }),
(P::Yuv411P, P::Rgb24, YuvToRgb { wsub: 4, hsub: 1, alpha: false, full_range: false }),
(P::Yuv411P, P::Rgba, YuvToRgb { wsub: 4, hsub: 1, alpha: true, full_range: false }),
(P::Rgb24, P::Yuv411P, RgbToYuv { wsub: 4, hsub: 1, alpha_in: false, full_range: false }),
(P::Rgba, P::Yuv411P, RgbToYuv { wsub: 4, hsub: 1, alpha_in: true, full_range: false }),
(P::Yuv420P, P::Yuva420P, YuvToYuva { wsub: 2, hsub: 2 }),
(P::Yuv422P, P::Yuva422P, YuvToYuva { wsub: 2, hsub: 1 }),
(P::Yuv444P, P::Yuva444P, YuvToYuva { wsub: 1, hsub: 1 }),
(P::Yuva420P, P::Yuv420P, YuvaToYuv { wsub: 2, hsub: 2 }),
(P::Yuva422P, P::Yuv422P, YuvaToYuv { wsub: 2, hsub: 1 }),
(P::Yuva444P, P::Yuv444P, YuvaToYuv { wsub: 1, hsub: 1 }),
(P::Yuva420P, P::Rgb24, YuvaToRgb { wsub: 2, hsub: 2, alpha: false }),
(P::Yuva422P, P::Rgb24, YuvaToRgb { wsub: 2, hsub: 1, alpha: false }),
(P::Yuva444P, P::Rgb24, YuvaToRgb { wsub: 1, hsub: 1, alpha: false }),
(P::Yuva420P, P::Rgba, YuvaToRgb { wsub: 2, hsub: 2, alpha: true }),
(P::Yuva422P, P::Rgba, YuvaToRgb { wsub: 2, hsub: 1, alpha: true }),
(P::Yuva444P, P::Rgba, YuvaToRgb { wsub: 1, hsub: 1, alpha: true }),
(P::Rgb24, P::Yuva420P, RgbToYuva { wsub: 2, hsub: 2, alpha_in: false }),
(P::Rgb24, P::Yuva422P, RgbToYuva { wsub: 2, hsub: 1, alpha_in: false }),
(P::Rgb24, P::Yuva444P, RgbToYuva { wsub: 1, hsub: 1, alpha_in: false }),
(P::Rgba, P::Yuva420P, RgbToYuva { wsub: 2, hsub: 2, alpha_in: true }),
(P::Rgba, P::Yuva422P, RgbToYuva { wsub: 2, hsub: 1, alpha_in: true }),
(P::Rgba, P::Yuva444P, RgbToYuva { wsub: 1, hsub: 1, alpha_in: true }),
(P::Yuva420P, P::Yuva422P, YuvaChromaResample { src_wsub: 2, src_hsub: 2, dst_wsub: 2, dst_hsub: 1 }),
(P::Yuva420P, P::Yuva444P, YuvaChromaResample { src_wsub: 2, src_hsub: 2, dst_wsub: 1, dst_hsub: 1 }),
(P::Yuva422P, P::Yuva420P, YuvaChromaResample { src_wsub: 2, src_hsub: 1, dst_wsub: 2, dst_hsub: 2 }),
(P::Yuva422P, P::Yuva444P, YuvaChromaResample { src_wsub: 2, src_hsub: 1, dst_wsub: 1, dst_hsub: 1 }),
(P::Yuva444P, P::Yuva420P, YuvaChromaResample { src_wsub: 1, src_hsub: 1, dst_wsub: 2, dst_hsub: 2 }),
(P::Yuva444P, P::Yuva422P, YuvaChromaResample { src_wsub: 1, src_hsub: 1, dst_wsub: 2, dst_hsub: 1 }),
(P::Gbrp10Le, P::Rgb48Le, GbrToPackedDeep { bits: 10, alpha_in: false, alpha_out: false }),
(P::Gbrp12Le, P::Rgb48Le, GbrToPackedDeep { bits: 12, alpha_in: false, alpha_out: false }),
(P::Gbrp14Le, P::Rgb48Le, GbrToPackedDeep { bits: 14, alpha_in: false, alpha_out: false }),
(P::Gbrap10Le, P::Rgba64Le, GbrToPackedDeep { bits: 10, alpha_in: true, alpha_out: true }),
(P::Gbrap12Le, P::Rgba64Le, GbrToPackedDeep { bits: 12, alpha_in: true, alpha_out: true }),
(P::Gbrap14Le, P::Rgba64Le, GbrToPackedDeep { bits: 14, alpha_in: true, alpha_out: true }),
(P::Gbrp10Le, P::Rgb24, GbrToPacked8 { bits: 10, alpha: false }),
(P::Gbrp12Le, P::Rgb24, GbrToPacked8 { bits: 12, alpha: false }),
(P::Gbrp14Le, P::Rgb24, GbrToPacked8 { bits: 14, alpha: false }),
(P::Gbrap10Le, P::Rgba, GbrToPacked8 { bits: 10, alpha: true }),
(P::Gbrap12Le, P::Rgba, GbrToPacked8 { bits: 12, alpha: true }),
(P::Gbrap14Le, P::Rgba, GbrToPacked8 { bits: 14, alpha: true }),
(P::Rgb24, P::Gbrp10Le, Packed8ToGbr { bits: 10, alpha: false }),
(P::Rgb24, P::Gbrp12Le, Packed8ToGbr { bits: 12, alpha: false }),
(P::Rgb24, P::Gbrp14Le, Packed8ToGbr { bits: 14, alpha: false }),
(P::Rgba, P::Gbrap10Le, Packed8ToGbr { bits: 10, alpha: true }),
(P::Rgba, P::Gbrap12Le, Packed8ToGbr { bits: 12, alpha: true }),
(P::Rgba, P::Gbrap14Le, Packed8ToGbr { bits: 14, alpha: true }),
(P::Rgb48Le, P::Gbrp10Le, PackedDeepToGbr { bits: 10, alpha_in: false, alpha_out: false }),
(P::Rgb48Le, P::Gbrp12Le, PackedDeepToGbr { bits: 12, alpha_in: false, alpha_out: false }),
(P::Rgb48Le, P::Gbrp14Le, PackedDeepToGbr { bits: 14, alpha_in: false, alpha_out: false }),
(P::Rgba64Le, P::Gbrap10Le, PackedDeepToGbr { bits: 10, alpha_in: true, alpha_out: true }),
(P::Rgba64Le, P::Gbrap12Le, PackedDeepToGbr { bits: 12, alpha_in: true, alpha_out: true }),
(P::Rgba64Le, P::Gbrap14Le, PackedDeepToGbr { bits: 14, alpha_in: true, alpha_out: true }),
(P::Gbrp16Le, P::Rgb48Le, GbrToPackedDeep { bits: 16, alpha_in: false, alpha_out: false }),
(P::Gbrap16Le, P::Rgba64Le, GbrToPackedDeep { bits: 16, alpha_in: true, alpha_out: true }),
(P::Rgb48Le, P::Gbrp16Le, PackedDeepToGbr { bits: 16, alpha_in: false, alpha_out: false }),
(P::Rgba64Le, P::Gbrap16Le, PackedDeepToGbr { bits: 16, alpha_in: true, alpha_out: true }),
(P::Gbrp16Le, P::Rgb24, GbrToPacked8 { bits: 16, alpha: false }),
(P::Gbrap16Le, P::Rgba, GbrToPacked8 { bits: 16, alpha: true }),
(P::Rgb24, P::Gbrp16Le, Packed8ToGbr { bits: 16, alpha: false }),
(P::Rgba, P::Gbrap16Le, Packed8ToGbr { bits: 16, alpha: true }),
(P::Gbrp8, P::Rgb24, Gbr8ToPacked8 { alpha_in: false, alpha_out: false }),
(P::Rgb24, P::Gbrp8, Packed8ToGbr8 { alpha_in: false, alpha_out: false }),
(P::Gbrp8, P::Rgb48Le, Gbr8ToPackedDeep { alpha_in: false, alpha_out: false }),
(P::Rgb48Le, P::Gbrp8, PackedDeepToGbr8 { alpha_in: false, alpha_out: false }),
(P::Gbrp10Le, P::Rgba64Le, GbrToPackedDeep { bits: 10, alpha_in: false, alpha_out: true }),
(P::Gbrp12Le, P::Rgba64Le, GbrToPackedDeep { bits: 12, alpha_in: false, alpha_out: true }),
(P::Gbrp14Le, P::Rgba64Le, GbrToPackedDeep { bits: 14, alpha_in: false, alpha_out: true }),
(P::Gbrp16Le, P::Rgba64Le, GbrToPackedDeep { bits: 16, alpha_in: false, alpha_out: true }),
(P::Gbrap10Le, P::Rgb48Le, GbrToPackedDeep { bits: 10, alpha_in: true, alpha_out: false }),
(P::Gbrap12Le, P::Rgb48Le, GbrToPackedDeep { bits: 12, alpha_in: true, alpha_out: false }),
(P::Gbrap14Le, P::Rgb48Le, GbrToPackedDeep { bits: 14, alpha_in: true, alpha_out: false }),
(P::Gbrap16Le, P::Rgb48Le, GbrToPackedDeep { bits: 16, alpha_in: true, alpha_out: false }),
(P::Rgba64Le, P::Gbrp10Le, PackedDeepToGbr { bits: 10, alpha_in: true, alpha_out: false }),
(P::Rgba64Le, P::Gbrp12Le, PackedDeepToGbr { bits: 12, alpha_in: true, alpha_out: false }),
(P::Rgba64Le, P::Gbrp14Le, PackedDeepToGbr { bits: 14, alpha_in: true, alpha_out: false }),
(P::Rgba64Le, P::Gbrp16Le, PackedDeepToGbr { bits: 16, alpha_in: true, alpha_out: false }),
(P::Rgb48Le, P::Gbrap10Le, PackedDeepToGbr { bits: 10, alpha_in: false, alpha_out: true }),
(P::Rgb48Le, P::Gbrap12Le, PackedDeepToGbr { bits: 12, alpha_in: false, alpha_out: true }),
(P::Rgb48Le, P::Gbrap14Le, PackedDeepToGbr { bits: 14, alpha_in: false, alpha_out: true }),
(P::Rgb48Le, P::Gbrap16Le, PackedDeepToGbr { bits: 16, alpha_in: false, alpha_out: true }),
(P::Gbrp8, P::Rgba64Le, Gbr8ToPackedDeep { alpha_in: false, alpha_out: true }),
(P::Rgba64Le, P::Gbrp8, PackedDeepToGbr8 { alpha_in: true, alpha_out: false }),
(P::Gbrap8, P::Rgba, Gbr8ToPacked8 { alpha_in: true, alpha_out: true }),
(P::Rgba, P::Gbrap8, Packed8ToGbr8 { alpha_in: true, alpha_out: true }),
(P::Gbrap8, P::Rgb24, Gbr8ToPacked8 { alpha_in: true, alpha_out: false }),
(P::Rgb24, P::Gbrap8, Packed8ToGbr8 { alpha_in: false, alpha_out: true }),
(P::Gbrp8, P::Rgba, Gbr8ToPacked8 { alpha_in: false, alpha_out: true }),
(P::Rgba, P::Gbrp8, Packed8ToGbr8 { alpha_in: true, alpha_out: false }),
(P::Gbrap8, P::Rgba64Le, Gbr8ToPackedDeep { alpha_in: true, alpha_out: true }),
(P::Rgba64Le, P::Gbrap8, PackedDeepToGbr8 { alpha_in: true, alpha_out: true }),
(P::Gbrap8, P::Rgb48Le, Gbr8ToPackedDeep { alpha_in: true, alpha_out: false }),
(P::Rgb48Le, P::Gbrap8, PackedDeepToGbr8 { alpha_in: false, alpha_out: true }),
(P::Gbrp8, P::Gbrap8, Gbr8Alpha { add: true }),
(P::Gbrap8, P::Gbrp8, Gbr8Alpha { add: false }),
(P::Gbrp8, P::Gray8, GbrToGray { bits: 8, alpha_in: false }),
(P::Gbrap8, P::Gray8, GbrToGray { bits: 8, alpha_in: true }),
(P::Gbrp10Le, P::Gray8, GbrToGray { bits: 10, alpha_in: false }),
(P::Gbrp12Le, P::Gray8, GbrToGray { bits: 12, alpha_in: false }),
(P::Gbrp14Le, P::Gray8, GbrToGray { bits: 14, alpha_in: false }),
(P::Gbrp16Le, P::Gray8, GbrToGray { bits: 16, alpha_in: false }),
(P::Gbrap10Le, P::Gray8, GbrToGray { bits: 10, alpha_in: true }),
(P::Gbrap12Le, P::Gray8, GbrToGray { bits: 12, alpha_in: true }),
(P::Gbrap14Le, P::Gray8, GbrToGray { bits: 14, alpha_in: true }),
(P::Gbrap16Le, P::Gray8, GbrToGray { bits: 16, alpha_in: true }),
(P::Gray8, P::Gbrp8, GrayToGbr { bits: 8, alpha_out: false }),
(P::Gray8, P::Gbrap8, GrayToGbr { bits: 8, alpha_out: true }),
(P::Gray8, P::Gbrp10Le, GrayToGbr { bits: 10, alpha_out: false }),
(P::Gray8, P::Gbrp12Le, GrayToGbr { bits: 12, alpha_out: false }),
(P::Gray8, P::Gbrp14Le, GrayToGbr { bits: 14, alpha_out: false }),
(P::Gray8, P::Gbrp16Le, GrayToGbr { bits: 16, alpha_out: false }),
(P::Gray8, P::Gbrap10Le, GrayToGbr { bits: 10, alpha_out: true }),
(P::Gray8, P::Gbrap12Le, GrayToGbr { bits: 12, alpha_out: true }),
(P::Gray8, P::Gbrap14Le, GrayToGbr { bits: 14, alpha_out: true }),
(P::Gray8, P::Gbrap16Le, GrayToGbr { bits: 16, alpha_out: true }),
(P::Yuv420P10Le, P::Yuv420P, DepthDownYuv { wsub: 2, hsub: 2, bits: 10 }),
(P::Yuv422P10Le, P::Yuv422P, DepthDownYuv { wsub: 2, hsub: 1, bits: 10 }),
(P::Yuv444P10Le, P::Yuv444P, DepthDownYuv { wsub: 1, hsub: 1, bits: 10 }),
(P::Yuv420P12Le, P::Yuv420P, DepthDownYuv { wsub: 2, hsub: 2, bits: 12 }),
(P::Yuv422P12Le, P::Yuv422P, DepthDownYuv { wsub: 2, hsub: 1, bits: 12 }),
(P::Yuv444P12Le, P::Yuv444P, DepthDownYuv { wsub: 1, hsub: 1, bits: 12 }),
(P::Yuv420P, P::Yuv420P10Le, DepthUpYuv { wsub: 2, hsub: 2, bits: 10 }),
(P::Yuv422P, P::Yuv422P10Le, DepthUpYuv { wsub: 2, hsub: 1, bits: 10 }),
(P::Yuv444P, P::Yuv444P10Le, DepthUpYuv { wsub: 1, hsub: 1, bits: 10 }),
(P::Yuv420P, P::Yuv420P12Le, DepthUpYuv { wsub: 2, hsub: 2, bits: 12 }),
(P::Yuv422P, P::Yuv422P12Le, DepthUpYuv { wsub: 2, hsub: 1, bits: 12 }),
(P::Yuv444P, P::Yuv444P12Le, DepthUpYuv { wsub: 1, hsub: 1, bits: 12 }),
(P::Yuv420P10Le, P::Yuv420P12Le, DepthRescaleYuv { wsub: 2, hsub: 2, src_bits: 10, dst_bits: 12 }),
(P::Yuv422P10Le, P::Yuv422P12Le, DepthRescaleYuv { wsub: 2, hsub: 1, src_bits: 10, dst_bits: 12 }),
(P::Yuv444P10Le, P::Yuv444P12Le, DepthRescaleYuv { wsub: 1, hsub: 1, src_bits: 10, dst_bits: 12 }),
(P::Yuv420P12Le, P::Yuv420P10Le, DepthRescaleYuv { wsub: 2, hsub: 2, src_bits: 12, dst_bits: 10 }),
(P::Yuv422P12Le, P::Yuv422P10Le, DepthRescaleYuv { wsub: 2, hsub: 1, src_bits: 12, dst_bits: 10 }),
(P::Yuv444P12Le, P::Yuv444P10Le, DepthRescaleYuv { wsub: 1, hsub: 1, src_bits: 12, dst_bits: 10 }),
(P::Yuv420P16Le, P::Yuv420P, DepthDownYuv { wsub: 2, hsub: 2, bits: 16 }),
(P::Yuv422P16Le, P::Yuv422P, DepthDownYuv { wsub: 2, hsub: 1, bits: 16 }),
(P::Yuv444P16Le, P::Yuv444P, DepthDownYuv { wsub: 1, hsub: 1, bits: 16 }),
(P::Yuv420P, P::Yuv420P16Le, DepthUpYuv { wsub: 2, hsub: 2, bits: 16 }),
(P::Yuv422P, P::Yuv422P16Le, DepthUpYuv { wsub: 2, hsub: 1, bits: 16 }),
(P::Yuv444P, P::Yuv444P16Le, DepthUpYuv { wsub: 1, hsub: 1, bits: 16 }),
(P::Yuv420P10Le, P::Yuv420P16Le, DepthRescaleYuv { wsub: 2, hsub: 2, src_bits: 10, dst_bits: 16 }),
(P::Yuv422P10Le, P::Yuv422P16Le, DepthRescaleYuv { wsub: 2, hsub: 1, src_bits: 10, dst_bits: 16 }),
(P::Yuv444P10Le, P::Yuv444P16Le, DepthRescaleYuv { wsub: 1, hsub: 1, src_bits: 10, dst_bits: 16 }),
(P::Yuv420P16Le, P::Yuv420P10Le, DepthRescaleYuv { wsub: 2, hsub: 2, src_bits: 16, dst_bits: 10 }),
(P::Yuv422P16Le, P::Yuv422P10Le, DepthRescaleYuv { wsub: 2, hsub: 1, src_bits: 16, dst_bits: 10 }),
(P::Yuv444P16Le, P::Yuv444P10Le, DepthRescaleYuv { wsub: 1, hsub: 1, src_bits: 16, dst_bits: 10 }),
(P::Yuv420P12Le, P::Yuv420P16Le, DepthRescaleYuv { wsub: 2, hsub: 2, src_bits: 12, dst_bits: 16 }),
(P::Yuv422P12Le, P::Yuv422P16Le, DepthRescaleYuv { wsub: 2, hsub: 1, src_bits: 12, dst_bits: 16 }),
(P::Yuv444P12Le, P::Yuv444P16Le, DepthRescaleYuv { wsub: 1, hsub: 1, src_bits: 12, dst_bits: 16 }),
(P::Yuv420P16Le, P::Yuv420P12Le, DepthRescaleYuv { wsub: 2, hsub: 2, src_bits: 16, dst_bits: 12 }),
(P::Yuv422P16Le, P::Yuv422P12Le, DepthRescaleYuv { wsub: 2, hsub: 1, src_bits: 16, dst_bits: 12 }),
(P::Yuv444P16Le, P::Yuv444P12Le, DepthRescaleYuv { wsub: 1, hsub: 1, src_bits: 16, dst_bits: 12 }),
(P::Yuv444P16Le, P::Rgb48Le, DeepYuvToRgb48 { wsub: 1, hsub: 1, alpha: false }),
(P::Yuv422P16Le, P::Rgb48Le, DeepYuvToRgb48 { wsub: 2, hsub: 1, alpha: false }),
(P::Yuv420P16Le, P::Rgb48Le, DeepYuvToRgb48 { wsub: 2, hsub: 2, alpha: false }),
(P::Rgb48Le, P::Yuv444P16Le, Rgb48ToDeepYuv { wsub: 1, hsub: 1, alpha: false }),
(P::Rgb48Le, P::Yuv422P16Le, Rgb48ToDeepYuv { wsub: 2, hsub: 1, alpha: false }),
(P::Rgb48Le, P::Yuv420P16Le, Rgb48ToDeepYuv { wsub: 2, hsub: 2, alpha: false }),
(P::Yuva444P16Le, P::Rgba64Le, DeepYuvToRgb48 { wsub: 1, hsub: 1, alpha: true }),
(P::Yuva422P16Le, P::Rgba64Le, DeepYuvToRgb48 { wsub: 2, hsub: 1, alpha: true }),
(P::Yuva420P16Le, P::Rgba64Le, DeepYuvToRgb48 { wsub: 2, hsub: 2, alpha: true }),
(P::Rgba64Le, P::Yuva444P16Le, Rgb48ToDeepYuv { wsub: 1, hsub: 1, alpha: true }),
(P::Rgba64Le, P::Yuva422P16Le, Rgb48ToDeepYuv { wsub: 2, hsub: 1, alpha: true }),
(P::Rgba64Le, P::Yuva420P16Le, Rgb48ToDeepYuv { wsub: 2, hsub: 2, alpha: true }),
(P::Yuv440P16Le, P::Rgb48Le, DeepYuvToRgb48 { wsub: 1, hsub: 2, alpha: false }),
(P::Rgb48Le, P::Yuv440P16Le, Rgb48ToDeepYuv { wsub: 1, hsub: 2, alpha: false }),
(P::Yuv420P16Le, P::Yuv422P16Le, ChromaResample16 { src_wsub: 2, src_hsub: 2, dst_wsub: 2, dst_hsub: 1 }),
(P::Yuv420P16Le, P::Yuv444P16Le, ChromaResample16 { src_wsub: 2, src_hsub: 2, dst_wsub: 1, dst_hsub: 1 }),
(P::Yuv422P16Le, P::Yuv420P16Le, ChromaResample16 { src_wsub: 2, src_hsub: 1, dst_wsub: 2, dst_hsub: 2 }),
(P::Yuv422P16Le, P::Yuv444P16Le, ChromaResample16 { src_wsub: 2, src_hsub: 1, dst_wsub: 1, dst_hsub: 1 }),
(P::Yuv444P16Le, P::Yuv420P16Le, ChromaResample16 { src_wsub: 1, src_hsub: 1, dst_wsub: 2, dst_hsub: 2 }),
(P::Yuv444P16Le, P::Yuv422P16Le, ChromaResample16 { src_wsub: 1, src_hsub: 1, dst_wsub: 2, dst_hsub: 1 }),
(P::Rgb48Le, P::Rgba64Le, Rgb48ToRgba64),
(P::Rgba64Le, P::Rgb48Le, Rgba64ToRgb48),
(P::Bgr24, P::Gray8, PackedPosToGray { comps: 3, r: 2, g: 1, b: 0 }),
(P::Bgra, P::Gray8, PackedPosToGray { comps: 4, r: 2, g: 1, b: 0 }),
(P::Argb, P::Gray8, PackedPosToGray { comps: 4, r: 1, g: 2, b: 3 }),
(P::Abgr, P::Gray8, PackedPosToGray { comps: 4, r: 3, g: 2, b: 1 }),
(P::Rgb48Le, P::Gray8, DeepPackedToGray { alpha_in: false }),
(P::Rgba64Le, P::Gray8, DeepPackedToGray { alpha_in: true }),
(P::Gray8, P::Rgb48Le, GrayToPackedDeep { alpha_out: false }),
(P::Gray8, P::Rgba64Le, GrayToPackedDeep { alpha_out: true }),
(P::Cmyk, P::Gray8, CmykToGray { inverted: false }),
(P::CmykInverted, P::Gray8, CmykToGray { inverted: true }),
(P::Gray8, P::Cmyk, GrayToCmyk { inverted: false }),
(P::Gray8, P::CmykInverted, GrayToCmyk { inverted: true }),
(P::Pal8, P::Gray8, Pal8ToGray),
(P::Gray8, P::Pal8, GrayToPal8),
(P::Yuyv422, P::Gray8, Packed422ToGray { is_yuyv: true }),
(P::Uyvy422, P::Gray8, Packed422ToGray { is_yuyv: false }),
(P::Gray8, P::Yuyv422, GrayToPacked422 { is_yuyv: true }),
(P::Gray8, P::Uyvy422, GrayToPacked422 { is_yuyv: false }),
(P::Gray10Le, P::Gray8, GrayDepthDown8 { bits: 10 }),
(P::Gray12Le, P::Gray8, GrayDepthDown8 { bits: 12 }),
(P::Gray8, P::Gray10Le, GrayDepthUp8 { bits: 10 }),
(P::Gray8, P::Gray12Le, GrayDepthUp8 { bits: 12 }),
(P::Gray10Le, P::Gray12Le, GrayDepthRescale { src_bits: 10, dst_bits: 12 }),
(P::Gray12Le, P::Gray10Le, GrayDepthRescale { src_bits: 12, dst_bits: 10 }),
(P::Gray10Le, P::Gray16Le, GrayDepthRescale { src_bits: 10, dst_bits: 16 }),
(P::Gray16Le, P::Gray10Le, GrayDepthRescale { src_bits: 16, dst_bits: 10 }),
(P::Gray12Le, P::Gray16Le, GrayDepthRescale { src_bits: 12, dst_bits: 16 }),
(P::Gray16Le, P::Gray12Le, GrayDepthRescale { src_bits: 16, dst_bits: 12 }),
(P::Gray16Le, P::Rgb48Le, DeepGrayToPackedDeep { bits: 16, alpha_out: false }),
(P::Gray16Le, P::Rgba64Le, DeepGrayToPackedDeep { bits: 16, alpha_out: true }),
(P::Gray12Le, P::Rgb48Le, DeepGrayToPackedDeep { bits: 12, alpha_out: false }),
(P::Gray12Le, P::Rgba64Le, DeepGrayToPackedDeep { bits: 12, alpha_out: true }),
(P::Gray10Le, P::Rgb48Le, DeepGrayToPackedDeep { bits: 10, alpha_out: false }),
(P::Gray10Le, P::Rgba64Le, DeepGrayToPackedDeep { bits: 10, alpha_out: true }),
(P::Rgb48Le, P::Gray16Le, PackedDeepToDeepGray { bits: 16, alpha_in: false }),
(P::Rgba64Le, P::Gray16Le, PackedDeepToDeepGray { bits: 16, alpha_in: true }),
(P::Rgb48Le, P::Gray12Le, PackedDeepToDeepGray { bits: 12, alpha_in: false }),
(P::Rgba64Le, P::Gray12Le, PackedDeepToDeepGray { bits: 12, alpha_in: true }),
(P::Rgb48Le, P::Gray10Le, PackedDeepToDeepGray { bits: 10, alpha_in: false }),
(P::Rgba64Le, P::Gray10Le, PackedDeepToDeepGray { bits: 10, alpha_in: true }),
(P::Ya16Le, P::Gray12Le, Ya16ToDeepGray { bits: 12 }),
(P::Ya16Le, P::Gray10Le, Ya16ToDeepGray { bits: 10 }),
(P::Gray12Le, P::Ya16Le, DeepGrayToYa16 { bits: 12 }),
(P::Gray10Le, P::Ya16Le, DeepGrayToYa16 { bits: 10 }),
]
};
fn lookup(src: PixelFormat, dst: PixelFormat) -> Option<&'static ConvertOp> {
TABLE
.iter()
.find(|(s, d, _)| *s == src && *d == dst)
.map(|(_, _, op)| op)
}
#[derive(Clone, Copy)]
enum ConvertOp {
Swizzle3 {
src: rgb::Rgb3,
dst: rgb::Rgb3,
},
Swizzle4 {
src: rgb::Rgba4,
dst: rgb::Rgba4,
},
Promote3To4 {
src: rgb::Rgb3,
dst: rgb::Rgba4,
},
Demote4To3 {
src: rgb::Rgba4,
dst: rgb::Rgb3,
},
Rgb48ToRgb24,
Rgb24ToRgb48,
Rgba64ToRgba,
RgbaToRgba64,
Gray8ToPacked3,
Gray8ToPacked4 {
alpha_first: bool,
},
Gray16ToGray8,
Gray8ToGray16,
MonoToGray {
black_is_zero: bool,
},
GrayToMono {
black_is_zero: bool,
},
RgbToGray {
alpha_in: bool,
},
Ya8ToGray8,
Gray8ToYa8,
Ya8ToRgb24,
Ya8ToRgba,
Rgb24ToYa8,
RgbaToYa8,
Ya16ToYa8,
Ya8ToYa16,
Ya16ToGray16,
Gray16ToYa16,
Ya16ToGray8,
Gray8ToYa16,
Ya16ToRgba64,
Rgba64ToYa16,
Ya16ToPacked8 {
alpha: bool,
},
Packed8ToYa16 {
alpha_in: bool,
},
YuvLumaToGray {
full_range: bool,
},
GrayToYuvPlanar {
wsub: usize,
hsub: usize,
full_range: bool,
},
GrayToNv,
YuvToRgb {
wsub: usize,
hsub: usize,
alpha: bool,
full_range: bool,
},
RgbToYuv {
wsub: usize,
hsub: usize,
alpha_in: bool,
full_range: bool,
},
RescaleRange {
wsub: usize,
hsub: usize,
to_full: bool,
},
ChromaResample {
src_wsub: usize,
src_hsub: usize,
dst_wsub: usize,
dst_hsub: usize,
},
NvToYuv420p {
is_nv12: bool,
},
Yuv420pToNv {
is_nv12: bool,
},
NvToRgb {
is_nv12: bool,
alpha: bool,
},
RgbToNv {
is_nv12: bool,
alpha_in: bool,
},
Packed422ToYuv422p {
is_yuyv: bool,
},
Yuv422pToPacked422 {
is_yuyv: bool,
},
Packed422Swap,
Packed422ToRgb {
is_yuyv: bool,
alpha: bool,
},
RgbToPacked422 {
is_yuyv: bool,
alpha_in: bool,
},
Pal8ToRgb {
alpha: bool,
},
RgbToPal8 {
alpha_in: bool,
},
CmykToRgb {
alpha: bool,
inverted: bool,
},
RgbToCmyk {
alpha_in: bool,
inverted: bool,
},
CmykComplement,
YuvToYuva {
wsub: usize,
hsub: usize,
},
YuvaToYuv {
wsub: usize,
hsub: usize,
},
YuvaToRgb {
wsub: usize,
hsub: usize,
alpha: bool,
},
RgbToYuva {
wsub: usize,
hsub: usize,
alpha_in: bool,
},
YuvaChromaResample {
src_wsub: usize,
src_hsub: usize,
dst_wsub: usize,
dst_hsub: usize,
},
ChromaResample16 {
src_wsub: usize,
src_hsub: usize,
dst_wsub: usize,
dst_hsub: usize,
},
GbrToPackedDeep {
bits: u8,
alpha_in: bool,
alpha_out: bool,
},
PackedDeepToGbr {
bits: u8,
alpha_in: bool,
alpha_out: bool,
},
DepthDownYuv {
wsub: usize,
hsub: usize,
bits: u32,
},
DepthUpYuv {
wsub: usize,
hsub: usize,
bits: u32,
},
GbrToPacked8 {
bits: u32,
alpha: bool,
},
Packed8ToGbr {
bits: u32,
alpha: bool,
},
Gbr8ToPacked8 {
alpha_in: bool,
alpha_out: bool,
},
Packed8ToGbr8 {
alpha_in: bool,
alpha_out: bool,
},
Gbr8ToPackedDeep {
alpha_in: bool,
alpha_out: bool,
},
PackedDeepToGbr8 {
alpha_in: bool,
alpha_out: bool,
},
Gbr8Alpha {
add: bool,
},
GbrToGray {
bits: u32,
alpha_in: bool,
},
GrayToGbr {
bits: u32,
alpha_out: bool,
},
DepthRescaleYuv {
wsub: usize,
hsub: usize,
src_bits: u32,
dst_bits: u32,
},
GrayDepthDown8 {
bits: u32,
},
GrayDepthUp8 {
bits: u32,
},
GrayDepthRescale {
src_bits: u32,
dst_bits: u32,
},
DeepYuvToRgb48 {
wsub: usize,
hsub: usize,
alpha: bool,
},
FamilyToPacked16 {
src: PlanarYuv,
alpha: bool,
},
Rgb48ToDeepYuv {
wsub: usize,
hsub: usize,
alpha: bool,
},
Rgb48ToRgba64,
Rgba64ToRgb48,
PackedPosToGray {
comps: usize,
r: usize,
g: usize,
b: usize,
},
DeepPackedToGray {
alpha_in: bool,
},
GrayToPackedDeep {
alpha_out: bool,
},
DeepGrayToPackedDeep {
bits: u32,
alpha_out: bool,
},
PackedDeepToDeepGray {
bits: u32,
alpha_in: bool,
},
Ya16ToDeepGray {
bits: u32,
},
DeepGrayToYa16 {
bits: u32,
},
CmykToGray {
inverted: bool,
},
GrayToCmyk {
inverted: bool,
},
Pal8ToGray,
GrayToPal8,
Packed422ToGray {
is_yuyv: bool,
},
GrayToPacked422 {
is_yuyv: bool,
},
PlanarFamily {
src: PlanarYuv,
dst: PlanarYuv,
},
PlanarFamilyToRgb {
src: PlanarYuv,
alpha: bool,
},
RgbToPlanarFamily {
dst: PlanarYuv,
alpha_in: bool,
},
PlanarFamilyToGray {
src: PlanarYuv,
},
GrayToPlanarFamily {
dst: PlanarYuv,
},
Packed16ToFamily {
dst: PlanarYuv,
alpha_in: bool,
},
DeepGrayToPlanarFamily {
bits: u32,
dst: PlanarYuv,
},
PlanarFamilyToDeepGray {
src: PlanarYuv,
bits: u32,
},
GbrToDeepGray {
src: IntLayout,
bits: u32,
},
DeepGrayToGbr {
bits: u32,
dst: IntLayout,
},
FloatToFloat {
src: FloatLayout,
dst: FloatLayout,
},
FloatToInt {
src: FloatLayout,
dst: IntLayout,
},
IntToFloat {
src: IntLayout,
dst: FloatLayout,
},
FloatToDeepYuv {
src: FloatLayout,
dst: PlanarYuv,
},
DeepYuvToFloat {
src: PlanarYuv,
dst: FloatLayout,
},
}
impl ConvertOp {
fn apply(
&self,
src: &VideoFrame,
src_info: FrameInfo,
opts: &ConvertOptions,
ctx: &Resolved,
) -> Result<VideoFrame> {
let primaries = ctx.primaries(opts);
let matrix = primaries.with_range(ctx.limited(true));
let ranged = |full_range: bool| primaries.with_range(ctx.limited(!full_range));
let workers = ctx.workers(src_info.height as usize);
match *self {
Self::Swizzle3 { src: sp, dst: dp } => swizzle3(src, src_info, sp, dp),
Self::Swizzle4 { src: sp, dst: dp } => swizzle4(src, src_info, sp, dp),
Self::Promote3To4 { src: sp, dst: dp } => promote3_to_4(src, src_info, sp, dp),
Self::Demote4To3 { src: sp, dst: dp } => demote4_to_3(src, src_info, sp, dp),
Self::Rgb48ToRgb24 => do_rgb48_to_rgb24(src, src_info),
Self::Rgb24ToRgb48 => do_rgb24_to_rgb48(src, src_info),
Self::Rgba64ToRgba => do_rgba64_to_rgba(src, src_info),
Self::RgbaToRgba64 => do_rgba_to_rgba64(src, src_info),
Self::Gray8ToPacked3 => gray_to_packed3(src, src_info),
Self::Gray8ToPacked4 { alpha_first } => gray_to_packed4(src, src_info, alpha_first),
Self::Gray16ToGray8 => do_gray16_to_gray8(src, src_info),
Self::Gray8ToGray16 => do_gray8_to_gray16(src, src_info),
Self::MonoToGray { black_is_zero } => do_mono_to_gray(src, src_info, black_is_zero),
Self::GrayToMono { black_is_zero } => do_gray_to_mono(src, src_info, black_is_zero),
Self::RgbToGray { alpha_in } => {
do_rgb_to_gray(src, src_info, matrix.with_range(false), alpha_in)
}
Self::Ya8ToGray8 => do_ya8_to_gray8(src, src_info),
Self::Gray8ToYa8 => do_gray8_to_ya8(src, src_info),
Self::Ya8ToRgb24 => do_ya8_to_rgb24(src, src_info),
Self::Ya8ToRgba => do_ya8_to_rgba(src, src_info),
Self::Rgb24ToYa8 => do_rgb24_to_ya8(src, src_info),
Self::RgbaToYa8 => do_rgba_to_ya8(src, src_info),
Self::Ya16ToYa8 => packed_map(src, src_info, 4, 2, gray::ya16le_to_ya8),
Self::Ya8ToYa16 => packed_map(src, src_info, 2, 4, gray::ya8_to_ya16le),
Self::Ya16ToGray16 => packed_map(src, src_info, 4, 2, gray::ya16le_to_gray16le),
Self::Gray16ToYa16 => packed_map(src, src_info, 2, 4, gray::gray16le_to_ya16le),
Self::Ya16ToGray8 => packed_map(src, src_info, 4, 1, gray::ya16le_to_gray8),
Self::Gray8ToYa16 => packed_map(src, src_info, 1, 4, gray::gray8_to_ya16le),
Self::Ya16ToRgba64 => packed_map(src, src_info, 4, 8, gray::ya16le_to_rgba64le),
Self::Rgba64ToYa16 => packed_map(src, src_info, 8, 4, gray::rgba64le_to_ya16le),
Self::Ya16ToPacked8 { alpha } => {
if alpha {
packed_map(src, src_info, 4, 4, gray::ya16le_to_rgba)
} else {
packed_map(src, src_info, 4, 3, gray::ya16le_to_rgb24)
}
}
Self::Packed8ToYa16 { alpha_in } => {
if alpha_in {
packed_map(src, src_info, 4, 4, gray::rgba_to_ya16le)
} else {
packed_map(src, src_info, 3, 4, gray::rgb24_to_ya16le)
}
}
Self::YuvLumaToGray { full_range } => {
do_yuv_luma_to_gray(src, src_info, !ctx.limited(!full_range))
}
Self::GrayToYuvPlanar {
wsub,
hsub,
full_range,
} => do_gray_to_yuv_planar(src, src_info, wsub, hsub, !ctx.limited(!full_range)),
Self::GrayToNv => do_gray_to_nv(src, src_info, !ctx.limited(true)),
Self::YuvToRgb {
wsub,
hsub,
alpha,
full_range,
} => do_yuv_to_rgb(
src,
src_info,
ranged(full_range),
wsub,
hsub,
alpha,
workers,
),
Self::RgbToYuv {
wsub,
hsub,
alpha_in,
full_range,
} => do_rgb_to_yuv(
src,
src_info,
ranged(full_range),
wsub,
hsub,
alpha_in,
workers,
),
Self::RescaleRange {
wsub,
hsub,
to_full,
} => {
let src_limited = ctx.limited(to_full);
let rescale = src_limited == to_full;
rescale_range(src, src_info, wsub, hsub, to_full, rescale)
}
Self::ChromaResample {
src_wsub,
src_hsub,
dst_wsub,
dst_hsub,
} => chroma_resample(src, src_info, src_wsub, src_hsub, dst_wsub, dst_hsub),
Self::NvToYuv420p { is_nv12 } => nv_to_yuv420p(src, src_info, is_nv12),
Self::Yuv420pToNv { is_nv12 } => yuv420p_to_nv(src, src_info, is_nv12),
Self::NvToRgb { is_nv12, alpha } => {
nv_to_rgb(src, src_info, matrix, is_nv12, alpha, workers)
}
Self::RgbToNv { is_nv12, alpha_in } => {
rgb_to_nv(src, src_info, matrix, is_nv12, alpha_in, workers)
}
Self::Packed422ToYuv422p { is_yuyv } => packed422_to_yuv422p(src, src_info, is_yuyv),
Self::Yuv422pToPacked422 { is_yuyv } => yuv422p_to_packed422(src, src_info, is_yuyv),
Self::Packed422Swap => packed422_swap(src, src_info),
Self::Packed422ToRgb { is_yuyv, alpha } => {
packed422_to_rgb(src, src_info, matrix, is_yuyv, alpha)
}
Self::RgbToPacked422 { is_yuyv, alpha_in } => {
rgb_to_packed422(src, src_info, matrix, is_yuyv, alpha_in)
}
Self::Pal8ToRgb { alpha } => pal8_to_rgb(src, src_info, opts, alpha),
Self::RgbToPal8 { alpha_in } => rgb_to_pal8(src, src_info, opts, alpha_in),
Self::CmykToRgb { alpha, inverted } => {
let f = match (alpha, inverted) {
(false, false) => cmyk::cmyk_to_rgb24,
(true, false) => cmyk::cmyk_to_rgba,
(false, true) => cmyk::cmyk_inverted_to_rgb24,
(true, true) => cmyk::cmyk_inverted_to_rgba,
};
packed_map(src, src_info, 4, if alpha { 4 } else { 3 }, f)
}
Self::RgbToCmyk { alpha_in, inverted } => {
let f = match (alpha_in, inverted) {
(false, false) => cmyk::rgb24_to_cmyk,
(true, false) => cmyk::rgba_to_cmyk,
(false, true) => cmyk::rgb24_to_cmyk_inverted,
(true, true) => cmyk::rgba_to_cmyk_inverted,
};
packed_map(src, src_info, if alpha_in { 4 } else { 3 }, 4, f)
}
Self::CmykComplement => packed_map(src, src_info, 4, 4, cmyk::cmyk_complement),
Self::YuvToYuva { wsub, hsub } => do_yuv_to_yuva(src, src_info, wsub, hsub),
Self::YuvaToYuv { wsub, hsub } => do_yuva_to_yuv(src, src_info, wsub, hsub),
Self::YuvaToRgb { wsub, hsub, alpha } => {
do_yuva_to_rgb(src, src_info, matrix, wsub, hsub, alpha, workers)
}
Self::RgbToYuva {
wsub,
hsub,
alpha_in,
} => do_rgb_to_yuva(src, src_info, matrix, wsub, hsub, alpha_in, workers),
Self::YuvaChromaResample {
src_wsub,
src_hsub,
dst_wsub,
dst_hsub,
} => yuva_chroma_resample(src, src_info, src_wsub, src_hsub, dst_wsub, dst_hsub),
Self::ChromaResample16 {
src_wsub,
src_hsub,
dst_wsub,
dst_hsub,
} => chroma_resample16(src, src_info, src_wsub, src_hsub, dst_wsub, dst_hsub),
Self::GbrToPackedDeep {
bits,
alpha_in,
alpha_out,
} => do_gbr_to_packed_deep(src, src_info, bits, alpha_in, alpha_out, workers),
Self::PackedDeepToGbr {
bits,
alpha_in,
alpha_out,
} => do_packed_deep_to_gbr(src, src_info, bits, alpha_in, alpha_out, workers),
Self::Gbr8ToPackedDeep {
alpha_in,
alpha_out,
} => do_gbr8_to_packed_deep(src, src_info, alpha_in, alpha_out),
Self::PackedDeepToGbr8 {
alpha_in,
alpha_out,
} => do_packed_deep_to_gbr8(src, src_info, alpha_in, alpha_out),
Self::Gbr8Alpha { add } => do_gbr8_alpha(src, src_info, add),
Self::GbrToGray { bits, alpha_in } => {
do_gbr_to_gray(src, src_info, matrix.with_range(false), bits, alpha_in)
}
Self::GrayToGbr { bits, alpha_out } => do_gray_to_gbr(src, src_info, bits, alpha_out),
Self::DepthDownYuv { wsub, hsub, bits } => {
do_yuv_depth_down(src, src_info, wsub, hsub, bits)
}
Self::DepthUpYuv { wsub, hsub, bits } => {
do_yuv_depth_up(src, src_info, wsub, hsub, bits)
}
Self::GbrToPacked8 { bits, alpha } => do_gbr_to_packed8(src, src_info, bits, alpha),
Self::Packed8ToGbr { bits, alpha } => do_packed8_to_gbr(src, src_info, bits, alpha),
Self::Gbr8ToPacked8 {
alpha_in,
alpha_out,
} => do_gbr8_to_packed8(src, src_info, alpha_in, alpha_out),
Self::Packed8ToGbr8 {
alpha_in,
alpha_out,
} => do_packed8_to_gbr8(src, src_info, alpha_in, alpha_out),
Self::DepthRescaleYuv {
wsub,
hsub,
src_bits,
dst_bits,
} => do_yuv_depth_rescale(src, src_info, wsub, hsub, src_bits, dst_bits),
Self::GrayDepthDown8 { bits } => do_gray_depth_down8(src, src_info, bits),
Self::GrayDepthUp8 { bits } => do_gray_depth_up8(src, src_info, bits),
Self::GrayDepthRescale { src_bits, dst_bits } => {
do_gray_depth_rescale(src, src_info, src_bits, dst_bits)
}
Self::DeepYuvToRgb48 { wsub, hsub, alpha } => {
do_deep_yuv_to_rgb48(src, src_info, matrix, wsub, hsub, alpha, workers)
}
Self::FamilyToPacked16 { src: s, alpha } => {
deep_family_to_packed16(src, src_info, matrix, s, alpha, workers)
}
Self::Rgb48ToDeepYuv { wsub, hsub, alpha } => {
do_rgb48_to_deep_yuv(src, src_info, matrix, wsub, hsub, alpha, workers)
}
Self::Packed16ToFamily { dst: d, alpha_in } => {
packed16_to_family(src, src_info, matrix, d, alpha_in, workers)
}
Self::GbrToDeepGray { src: il, bits } => {
gbr_to_deep_gray(src, src_info, matrix.with_range(false), il, bits, workers)
}
Self::DeepGrayToGbr { bits, dst: il } => {
deep_gray_to_gbr(src, src_info, bits, il, workers)
}
Self::DeepGrayToPlanarFamily { bits, dst: d } => {
deep_gray_to_planar_family(src, src_info, bits, d, ctx.limited(true), workers)
}
Self::PlanarFamilyToDeepGray { src: s, bits } => {
planar_family_to_deep_gray(src, src_info, s, bits, ctx.limited(true), workers)
}
Self::Rgb48ToRgba64 => packed_map(src, src_info, 6, 8, rgb::rgb48_to_rgba64),
Self::Rgba64ToRgb48 => packed_map(src, src_info, 8, 6, rgb::rgba64_to_rgb48),
Self::PackedPosToGray { comps, r, g, b } => {
do_packed_pos_to_gray(src, src_info, matrix.with_range(false), comps, r, g, b)
}
Self::DeepPackedToGray { alpha_in } => {
do_deep_packed_to_gray(src, src_info, matrix.with_range(false), alpha_in)
}
Self::GrayToPackedDeep { alpha_out } => {
if alpha_out {
packed_map(src, src_info, 1, 8, gray::gray8_to_rgba64le)
} else {
packed_map(src, src_info, 1, 6, gray::gray8_to_rgb48le)
}
}
Self::DeepGrayToPackedDeep { bits, alpha_out } => {
do_deep_gray_to_packed_deep(src, src_info, bits, alpha_out, workers)
}
Self::PackedDeepToDeepGray { bits, alpha_in } => do_packed_deep_to_deep_gray(
src,
src_info,
matrix.with_range(false),
bits,
alpha_in,
workers,
),
Self::Ya16ToDeepGray { bits } => do_ya16_to_deep_gray(src, src_info, bits),
Self::DeepGrayToYa16 { bits } => do_deep_gray_to_ya16(src, src_info, bits),
Self::CmykToGray { inverted } => {
let f = if inverted {
cmyk::cmyk_inverted_to_rgb24
} else {
cmyk::cmyk_to_rgb24
};
let rgb = packed_map(src, src_info, 4, 3, f)?;
let rgb_info = FrameInfo::new(PixelFormat::Rgb24, src_info.width, src_info.height);
do_rgb_to_gray(&rgb, rgb_info, matrix.with_range(false), false)
}
Self::GrayToCmyk { inverted } => {
let f = if inverted {
cmyk::gray8_to_cmyk_inverted
} else {
cmyk::gray8_to_cmyk
};
packed_map(src, src_info, 1, 4, f)
}
Self::Pal8ToGray => {
let rgb = pal8_to_rgb(src, src_info, opts, false)?;
let rgb_info = FrameInfo::new(PixelFormat::Rgb24, src_info.width, src_info.height);
do_rgb_to_gray(&rgb, rgb_info, matrix.with_range(false), false)
}
Self::GrayToPal8 => {
let rgb = gray_to_packed3(src, src_info)?;
let rgb_info = FrameInfo::new(PixelFormat::Rgb24, src_info.width, src_info.height);
rgb_to_pal8(&rgb, rgb_info, opts, false)
}
Self::Packed422ToGray { is_yuyv } => do_packed422_to_gray(src, src_info, is_yuyv),
Self::GrayToPacked422 { is_yuyv } => do_gray_to_packed422(src, src_info, is_yuyv),
Self::PlanarFamily { src: s, dst: d } => planar_family(src, src_info, s, d),
Self::PlanarFamilyToRgb { src: s, alpha } => {
planar_family_to_rgb(src, src_info, matrix, s, alpha, workers)
}
Self::RgbToPlanarFamily { dst: d, alpha_in } => {
rgb_to_planar_family(src, src_info, matrix, d, alpha_in, workers)
}
Self::PlanarFamilyToGray { src: s } => {
planar_family_to_gray(src, src_info, s, ctx.limited(true))
}
Self::GrayToPlanarFamily { dst: d } => {
gray_to_planar_family(src, src_info, d, ctx.limited(true))
}
Self::FloatToFloat { src: s, dst: d } => {
float_to_float(src, src_info, matrix.with_range(false), s, d)
}
Self::FloatToInt { src: s, dst: d } => {
float_to_int(src, src_info, matrix.with_range(false), s, d)
}
Self::IntToFloat { src: s, dst: d } => {
int_to_float(src, src_info, matrix.with_range(false), s, d)
}
Self::FloatToDeepYuv { src: s, dst: d } => {
float_to_deep_yuv(src, src_info, matrix, s, d)
}
Self::DeepYuvToFloat { src: s, dst: d } => {
deep_yuv_to_float(src, src_info, matrix, s, d)
}
}
}
}
fn make_frame(src: &VideoFrame, planes: Vec<VideoPlane>) -> VideoFrame {
VideoFrame {
pts: src.pts,
planes,
}
}
fn tight_row(src: &[u8], stride: usize, row: usize, row_bytes: usize) -> &[u8] {
let off = row * stride;
&src[off..off + row_bytes]
}
fn packed_map(
src: &VideoFrame,
src_info: FrameInfo,
src_bpp: usize,
dst_bpp: usize,
f: fn(&[u8], &mut [u8], usize),
) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let in_plane = &src.planes[0];
let mut out = vec![0u8; w * h * dst_bpp];
for row in 0..h {
let sr = tight_row(&in_plane.data, in_plane.stride, row, w * src_bpp);
f(sr, &mut out[row * w * dst_bpp..(row + 1) * w * dst_bpp], w);
}
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * dst_bpp,
data: out,
}],
))
}
fn gather_tight(src: &[u8], stride: usize, w_bytes: usize, h: usize) -> Result<Vec<u8>> {
if h == 0 || w_bytes == 0 {
return Ok(Vec::new());
}
if stride < w_bytes {
return Err(Error::invalid(format!(
"pixfmt: plane stride {stride} shorter than its {w_bytes}-byte rows"
)));
}
let need = stride
.checked_mul(h - 1)
.and_then(|v| v.checked_add(w_bytes))
.ok_or_else(|| Error::invalid("pixfmt: plane geometry overflows usize"))?;
if src.len() < need {
return Err(Error::invalid(format!(
"pixfmt: plane holds {} bytes, geometry needs {need}",
src.len()
)));
}
if stride == w_bytes {
return Ok(src[..need].to_vec());
}
let mut out = Vec::with_capacity(w_bytes * h);
for row in 0..h {
out.extend_from_slice(tight_row(src, stride, row, w_bytes));
}
Ok(out)
}
fn swizzle3(
src: &VideoFrame,
src_info: FrameInfo,
src_pos: rgb::Rgb3,
dst_pos: rgb::Rgb3,
) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let in_plane = &src.planes[0];
let mut out = vec![0u8; w * h * 3];
for row in 0..h {
let sr = tight_row(&in_plane.data, in_plane.stride, row, w * 3);
rgb::swizzle3(
sr,
src_pos,
&mut out[row * w * 3..row * w * 3 + w * 3],
dst_pos,
w,
);
}
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * 3,
data: out,
}],
))
}
fn swizzle4(
src: &VideoFrame,
src_info: FrameInfo,
src_pos: rgb::Rgba4,
dst_pos: rgb::Rgba4,
) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let in_plane = &src.planes[0];
let mut out = vec![0u8; w * h * 4];
for row in 0..h {
let sr = tight_row(&in_plane.data, in_plane.stride, row, w * 4);
rgb::swizzle4(
sr,
src_pos,
&mut out[row * w * 4..row * w * 4 + w * 4],
dst_pos,
w,
);
}
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * 4,
data: out,
}],
))
}
fn promote3_to_4(
src: &VideoFrame,
src_info: FrameInfo,
src_pos: rgb::Rgb3,
dst_pos: rgb::Rgba4,
) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let in_plane = &src.planes[0];
let mut out = vec![0u8; w * h * 4];
for row in 0..h {
let sr = tight_row(&in_plane.data, in_plane.stride, row, w * 3);
rgb::rgb3_to_rgba4(
sr,
src_pos,
&mut out[row * w * 4..row * w * 4 + w * 4],
dst_pos,
w,
);
}
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * 4,
data: out,
}],
))
}
fn demote4_to_3(
src: &VideoFrame,
src_info: FrameInfo,
src_pos: rgb::Rgba4,
dst_pos: rgb::Rgb3,
) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let in_plane = &src.planes[0];
let mut out = vec![0u8; w * h * 3];
for row in 0..h {
let sr = tight_row(&in_plane.data, in_plane.stride, row, w * 4);
rgb::rgba4_to_rgb3(
sr,
src_pos,
&mut out[row * w * 3..row * w * 3 + w * 3],
dst_pos,
w,
);
}
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * 3,
data: out,
}],
))
}
fn do_rgb48_to_rgb24(src: &VideoFrame, src_info: FrameInfo) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let in_plane = &src.planes[0];
let mut out = vec![0u8; w * h * 3];
for row in 0..h {
let sr = tight_row(&in_plane.data, in_plane.stride, row, w * 6);
rgb::rgb48_to_rgb24(sr, &mut out[row * w * 3..row * w * 3 + w * 3], w);
}
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * 3,
data: out,
}],
))
}
fn do_rgb24_to_rgb48(src: &VideoFrame, src_info: FrameInfo) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let in_plane = &src.planes[0];
let mut out = vec![0u8; w * h * 6];
for row in 0..h {
let sr = tight_row(&in_plane.data, in_plane.stride, row, w * 3);
rgb::rgb24_to_rgb48(sr, &mut out[row * w * 6..row * w * 6 + w * 6], w);
}
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * 6,
data: out,
}],
))
}
fn do_rgba64_to_rgba(src: &VideoFrame, src_info: FrameInfo) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let in_plane = &src.planes[0];
let mut out = vec![0u8; w * h * 4];
for row in 0..h {
let sr = tight_row(&in_plane.data, in_plane.stride, row, w * 8);
rgb::rgba64_to_rgba(sr, &mut out[row * w * 4..row * w * 4 + w * 4], w);
}
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * 4,
data: out,
}],
))
}
fn do_rgba_to_rgba64(src: &VideoFrame, src_info: FrameInfo) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let in_plane = &src.planes[0];
let mut out = vec![0u8; w * h * 8];
for row in 0..h {
let sr = tight_row(&in_plane.data, in_plane.stride, row, w * 4);
rgb::rgba_to_rgba64(sr, &mut out[row * w * 8..row * w * 8 + w * 8], w);
}
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * 8,
data: out,
}],
))
}
fn gray_to_packed3(src: &VideoFrame, src_info: FrameInfo) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let in_plane = &src.planes[0];
let mut out = vec![0u8; w * h * 3];
for row in 0..h {
let sr = tight_row(&in_plane.data, in_plane.stride, row, w);
gray::gray8_to_rgb24(sr, &mut out[row * w * 3..row * w * 3 + w * 3], w);
}
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * 3,
data: out,
}],
))
}
fn gray_to_packed4(src: &VideoFrame, src_info: FrameInfo, alpha_first: bool) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let in_plane = &src.planes[0];
let mut out = vec![0u8; w * h * 4];
for row in 0..h {
let sr = tight_row(&in_plane.data, in_plane.stride, row, w);
let dr = &mut out[row * w * 4..row * w * 4 + w * 4];
if alpha_first {
for (i, &g) in sr.iter().enumerate().take(w) {
dr[i * 4] = 255;
dr[i * 4 + 1] = g;
dr[i * 4 + 2] = g;
dr[i * 4 + 3] = g;
}
} else {
gray::gray8_to_rgba(sr, dr, w);
}
}
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * 4,
data: out,
}],
))
}
fn do_gray16_to_gray8(src: &VideoFrame, src_info: FrameInfo) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let in_plane = &src.planes[0];
let mut out = vec![0u8; w * h];
for row in 0..h {
let sr = tight_row(&in_plane.data, in_plane.stride, row, w * 2);
gray::gray16le_to_gray8(sr, &mut out[row * w..row * w + w], w);
}
Ok(make_frame(
src,
vec![VideoPlane {
stride: w,
data: out,
}],
))
}
fn do_gray8_to_gray16(src: &VideoFrame, src_info: FrameInfo) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let in_plane = &src.planes[0];
let mut out = vec![0u8; w * h * 2];
for row in 0..h {
let sr = tight_row(&in_plane.data, in_plane.stride, row, w);
gray::gray8_to_gray16le(sr, &mut out[row * w * 2..row * w * 2 + w * 2], w);
}
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * 2,
data: out,
}],
))
}
fn do_mono_to_gray(
src: &VideoFrame,
src_info: FrameInfo,
black_is_zero: bool,
) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let in_plane = &src.planes[0];
let mut out = vec![0u8; w * h];
let src_stride = in_plane.stride;
let compact = gather_mono_rows(&in_plane.data, src_stride, w.div_ceil(8), h);
gray::mono_to_gray8(&compact, &mut out, w, h, black_is_zero);
Ok(make_frame(
src,
vec![VideoPlane {
stride: w,
data: out,
}],
))
}
fn do_gray_to_mono(
src: &VideoFrame,
src_info: FrameInfo,
black_is_zero: bool,
) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let in_plane = &src.planes[0];
let packed_stride = w.div_ceil(8);
let src_tight = gather_tight(&in_plane.data, in_plane.stride, w, h)?;
let mut out = vec![0u8; packed_stride * h];
gray::gray8_to_mono(&src_tight, &mut out, w, h, black_is_zero);
Ok(make_frame(
src,
vec![VideoPlane {
stride: packed_stride,
data: out,
}],
))
}
fn gather_mono_rows(src: &[u8], stride: usize, packed: usize, h: usize) -> Vec<u8> {
if stride == packed {
return src[..packed * h].to_vec();
}
let mut out = Vec::with_capacity(packed * h);
for row in 0..h {
out.extend_from_slice(&src[row * stride..row * stride + packed]);
}
out
}
fn do_rgb_to_gray(
src: &VideoFrame,
src_info: FrameInfo,
matrix: YuvMatrix,
alpha_in: bool,
) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let in_plane = &src.planes[0];
let rgb24: Vec<u8> = if alpha_in {
let mut out = Vec::with_capacity(w * h * 3);
for row in 0..h {
let sr = tight_row(&in_plane.data, in_plane.stride, row, w * 4);
for i in 0..w {
out.push(sr[i * 4]);
out.push(sr[i * 4 + 1]);
out.push(sr[i * 4 + 2]);
}
}
out
} else {
gather_tight(&in_plane.data, in_plane.stride, w * 3, h)?
};
let mut gray = vec![0u8; w * h];
yuv::rgb24_to_gray8(&rgb24, &mut gray, w * h, matrix);
Ok(make_frame(
src,
vec![VideoPlane {
stride: w,
data: gray,
}],
))
}
fn do_deep_yuv_to_rgb48(
src: &VideoFrame,
src_info: FrameInfo,
matrix: YuvMatrix,
wsub: usize,
hsub: usize,
alpha: bool,
workers: usize,
) -> Result<VideoFrame> {
let s = PlanarYuv {
wsub,
hsub,
bits: 16,
alpha,
};
deep_family_to_packed16(src, src_info, matrix, s, alpha, workers)
}
fn deep_family_to_packed16(
src: &VideoFrame,
src_info: FrameInfo,
matrix: YuvMatrix,
s: PlanarYuv,
alpha_out: bool,
workers: usize,
) -> Result<VideoFrame> {
let need = if s.alpha { 4 } else { 3 };
if src.planes.len() < need {
return Err(Error::invalid(
"pixfmt: deep YUV source needs Y, U, V(, A) planes",
));
}
let w = src_info.width as usize;
let h = src_info.height as usize;
let (cw, ch) = chroma_dims(w, h, s.wsub, s.hsub);
let sb = s.sample_bytes();
let yp = tight_plane(&src.planes[0], w * sb, h)?;
let up = tight_plane(&src.planes[1], cw * sb, ch)?;
let vp = tight_plane(&src.planes[2], cw * sb, ch)?;
let ap = if s.alpha && alpha_out {
Some(tight_plane(&src.planes[3], w * sb, h)?)
} else {
None
};
let out = planar8::decode_deep(
&planar8::PlanarDeep {
y: &yp,
u: &up,
v: &vp,
a: ap.as_deref(),
w,
h,
wsub: s.wsub,
hsub: s.hsub,
bits: s.bits,
},
alpha_out,
matrix,
workers,
);
let bpp = if alpha_out { 8 } else { 6 };
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * bpp,
data: out,
}],
))
}
fn do_rgb48_to_deep_yuv(
src: &VideoFrame,
src_info: FrameInfo,
matrix: YuvMatrix,
wsub: usize,
hsub: usize,
alpha: bool,
workers: usize,
) -> Result<VideoFrame> {
let d = PlanarYuv {
wsub,
hsub,
bits: 16,
alpha,
};
packed16_to_family(src, src_info, matrix, d, alpha, workers)
}
fn packed16_to_family(
src: &VideoFrame,
src_info: FrameInfo,
matrix: YuvMatrix,
d: PlanarYuv,
alpha_in: bool,
workers: usize,
) -> Result<VideoFrame> {
if src.planes.is_empty() {
return Err(Error::invalid("pixfmt: packed RGB source needs a plane"));
}
let w = src_info.width as usize;
let h = src_info.height as usize;
let comps = if alpha_in { 4 } else { 3 };
let packed = tight_plane(&src.planes[0], w * comps * 2, h)?;
let out = planar8::encode_deep(
&planar8::PackedDeep {
data: &packed,
comps,
w,
h,
},
d.wsub,
d.hsub,
d.alpha,
matrix,
workers,
);
let (cw, ch) = (out.cw, out.ch);
let sb = d.sample_bytes();
let narrow = |plane: Vec<u8>, count: usize| -> Vec<u8> {
if d.bits >= 16 {
plane
} else {
plane_to_depth(&plane, count, 16, d.bits)
}
};
let mut planes = vec![
VideoPlane {
stride: w * sb,
data: narrow(out.y, w * h),
},
VideoPlane {
stride: cw * sb,
data: narrow(out.u, cw * ch),
},
VideoPlane {
stride: cw * sb,
data: narrow(out.v, cw * ch),
},
];
if let Some(a) = out.a {
planes.push(VideoPlane {
stride: w * sb,
data: narrow(a, w * h),
});
}
Ok(make_frame(src, planes))
}
fn do_packed_pos_to_gray(
src: &VideoFrame,
src_info: FrameInfo,
matrix: YuvMatrix,
comps: usize,
r: usize,
g: usize,
b: usize,
) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let in_plane = &src.planes[0];
let mut rgb24 = Vec::with_capacity(w * h * 3);
for row in 0..h {
let sr = tight_row(&in_plane.data, in_plane.stride, row, w * comps);
for i in 0..w {
rgb24.push(sr[i * comps + r]);
rgb24.push(sr[i * comps + g]);
rgb24.push(sr[i * comps + b]);
}
}
let mut gray = vec![0u8; w * h];
yuv::rgb24_to_gray8(&rgb24, &mut gray, w * h, matrix);
Ok(make_frame(
src,
vec![VideoPlane {
stride: w,
data: gray,
}],
))
}
fn do_deep_packed_to_gray(
src: &VideoFrame,
src_info: FrameInfo,
matrix: YuvMatrix,
alpha_in: bool,
) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let comps = if alpha_in { 4 } else { 3 };
let in_plane = &src.planes[0];
let mut rgb24 = Vec::with_capacity(w * h * 3);
for row in 0..h {
let sr = tight_row(&in_plane.data, in_plane.stride, row, w * comps * 2);
for i in 0..w {
rgb24.push(sr[i * comps * 2 + 1]);
rgb24.push(sr[i * comps * 2 + 3]);
rgb24.push(sr[i * comps * 2 + 5]);
}
}
let mut gray = vec![0u8; w * h];
yuv::rgb24_to_gray8(&rgb24, &mut gray, w * h, matrix);
Ok(make_frame(
src,
vec![VideoPlane {
stride: w,
data: gray,
}],
))
}
fn do_packed422_to_gray(
src: &VideoFrame,
src_info: FrameInfo,
is_yuyv: bool,
) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
if w % 2 != 0 {
return Err(Error::invalid("pixfmt: packed 4:2:2 requires even width"));
}
let in_plane = &src.planes[0];
let packed = gather_tight(&in_plane.data, in_plane.stride, w * 2, h)?;
let luma_off = if is_yuyv { 0 } else { 1 };
let mut yp: Vec<u8> = (0..w * h).map(|i| packed[i * 2 + luma_off]).collect();
yuv::limited_to_full_luma(&mut yp);
Ok(make_frame(
src,
vec![VideoPlane {
stride: w,
data: yp,
}],
))
}
fn do_gray_to_packed422(
src: &VideoFrame,
src_info: FrameInfo,
is_yuyv: bool,
) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
if w % 2 != 0 {
return Err(Error::invalid("pixfmt: packed 4:2:2 requires even width"));
}
let in_plane = &src.planes[0];
let mut yp = gather_tight(&in_plane.data, in_plane.stride, w, h)?;
yuv::full_to_limited_luma(&mut yp);
let mut out = vec![128u8; w * h * 2];
let luma_off = if is_yuyv { 0 } else { 1 };
for (i, &y) in yp.iter().enumerate() {
out[i * 2 + luma_off] = y;
}
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * 2,
data: out,
}],
))
}
fn do_deep_gray_to_packed_deep(
src: &VideoFrame,
src_info: FrameInfo,
bits: u32,
alpha_out: bool,
workers: usize,
) -> Result<VideoFrame> {
if src.planes.is_empty() {
return Err(Error::invalid("pixfmt: grey source needs a plane"));
}
let w = src_info.width as usize;
let h = src_info.height as usize;
let tight = tight_plane(&src.planes[0], w * 2, h)?;
let bpp = if alpha_out { 8 } else { 6 };
let kernel = if alpha_out {
gray::gray16le_to_rgba64le
} else {
gray::gray16le_to_rgb48le
};
let out = planar8::map_packed_banded(&tight, w * 2, w * bpp, h, workers, |s, d| {
let n = d.len() / bpp;
if bits >= 16 {
kernel(s, d, n);
} else {
let mut wide = vec![0u8; n * 2];
yuv::depth_rescale_le16_plane(s, &mut wide, n, bits, 16);
kernel(&wide, d, n);
}
});
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * bpp,
data: out,
}],
))
}
fn do_packed_deep_to_deep_gray(
src: &VideoFrame,
src_info: FrameInfo,
matrix: YuvMatrix,
bits: u32,
alpha_in: bool,
workers: usize,
) -> Result<VideoFrame> {
if src.planes.is_empty() {
return Err(Error::invalid("pixfmt: packed RGB source needs a plane"));
}
let w = src_info.width as usize;
let h = src_info.height as usize;
let bpp = if alpha_in { 8 } else { 6 };
let tight = tight_plane(&src.planes[0], w * bpp, h)?;
let kernel = if alpha_in {
yuv::rgba64le_to_gray16le
} else {
yuv::rgb48le_to_gray16le
};
let out = planar8::map_packed_banded(&tight, w * bpp, w * 2, h, workers, |s, d| {
let n = d.len() / 2;
if bits >= 16 {
kernel(s, d, n, matrix);
} else {
let mut luma = vec![0u8; n * 2];
kernel(s, &mut luma, n, matrix);
yuv::depth_rescale_le16_plane(&luma, d, n, 16, bits);
}
});
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * 2,
data: out,
}],
))
}
fn do_ya16_to_deep_gray(src: &VideoFrame, src_info: FrameInfo, bits: u32) -> Result<VideoFrame> {
let g16 = packed_map(src, src_info, 4, 2, gray::ya16le_to_gray16le)?;
let g16_info = FrameInfo::new(PixelFormat::Gray16Le, src_info.width, src_info.height);
do_gray_depth_rescale(&g16, g16_info, 16, bits)
}
fn do_deep_gray_to_ya16(src: &VideoFrame, src_info: FrameInfo, bits: u32) -> Result<VideoFrame> {
let g16 = do_gray_depth_rescale(src, src_info, bits, 16)?;
let g16_info = FrameInfo::new(PixelFormat::Gray16Le, src_info.width, src_info.height);
packed_map(&g16, g16_info, 2, 4, gray::gray16le_to_ya16le)
}
fn deep_gray_bits(f: PixelFormat) -> u32 {
match f {
PixelFormat::Gray10Le => 10,
PixelFormat::Gray12Le => 12,
_ => 16,
}
}
#[inline]
fn luma_move_code(v: u32, src_bits: u32, dst_bits: u32, limited: bool, to_yuv: bool) -> u32 {
let work = src_bits.max(dst_bits);
let v = v & ((1u32 << src_bits) - 1);
let wide = if work == src_bits {
v
} else {
widen_bits(v, src_bits, work)
};
let moved = if limited {
range_table(work, !to_yuv)[wide as usize] as u32
} else {
wide
};
moved >> (work - dst_bits)
}
#[allow(clippy::too_many_arguments)]
fn luma_depth_range_banded(
src: &[u8],
w: usize,
h: usize,
src_bits: u32,
dst_bits: u32,
limited: bool,
to_yuv: bool,
workers: usize,
) -> Vec<u8> {
debug_assert!(src_bits.max(dst_bits) > 8);
let ssb = if src_bits > 8 { 2 } else { 1 };
let dsb = if dst_bits > 8 { 2 } else { 1 };
let codes = 1usize << src_bits;
let mask = (codes - 1) as u32;
let table: Vec<u16> = if w * h >= codes {
(0..codes as u32)
.map(|v| luma_move_code(v, src_bits, dst_bits, limited, to_yuv) as u16)
.collect()
} else {
Vec::new()
};
let map = |v: u32| -> u16 {
if table.is_empty() {
luma_move_code(v, src_bits, dst_bits, limited, to_yuv) as u16
} else {
table[(v & mask) as usize]
}
};
planar8::map_packed_banded(src, w * ssb, w * dsb, h, workers, |s, d| {
let n = d.len() / dsb;
match (ssb, dsb) {
(1, 2) => {
for (o, &v) in d.chunks_exact_mut(2).zip(s[..n].iter()) {
o.copy_from_slice(&map(v as u32).to_le_bytes());
}
}
(2, 1) => {
for (o, i) in d.iter_mut().zip(s[..n * 2].chunks_exact(2)) {
*o = map(u16::from_le_bytes([i[0], i[1]]) as u32) as u8;
}
}
_ => {
for (o, i) in d.chunks_exact_mut(2).zip(s[..n * 2].chunks_exact(2)) {
let v = u16::from_le_bytes([i[0], i[1]]) as u32;
o.copy_from_slice(&map(v).to_le_bytes());
}
}
}
})
}
fn gbr_to_deep_gray(
src: &VideoFrame,
src_info: FrameInfo,
matrix: YuvMatrix,
il: IntLayout,
bits: u32,
workers: usize,
) -> Result<VideoFrame> {
if src.planes.len() < 3 {
return Err(Error::invalid(
"pixfmt: GBR(A) source needs G, B, R(, A) planes",
));
}
let w = src_info.width as usize;
let h = src_info.height as usize;
let sb = il.sample_bytes();
let g = tight_plane(&src.planes[0], w * sb, h)?;
let b = tight_plane(&src.planes[1], w * sb, h)?;
let r = tight_plane(&src.planes[2], w * sb, h)?;
let p = matrix.encode_params16();
let src_bits = il.bits;
let out = planar8::map_packed_banded(&r, w * sb, w * 2, h, workers, |rb, d| {
let n = d.len() / 2;
let off = rb.as_ptr() as usize - r.as_ptr() as usize;
let gb = &g[off..off + n * sb];
let bb = &b[off..off + n * sb];
let mut r16 = vec![0u16; n];
let mut g16 = vec![0u16; n];
let mut b16 = vec![0u16; n];
planar8::widen_row16(rb, &mut r16, src_bits);
planar8::widen_row16(gb, &mut g16, src_bits);
planar8::widen_row16(bb, &mut b16, src_bits);
let shift = 16 - bits;
for (i, o) in d.chunks_exact_mut(2).enumerate() {
let y = yuv::rgb16_to_luma16_fp(r16[i], g16[i], b16[i], &p) >> shift;
o.copy_from_slice(&y.to_le_bytes());
}
});
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * 2,
data: out,
}],
))
}
fn deep_gray_to_gbr(
src: &VideoFrame,
src_info: FrameInfo,
bits: u32,
il: IntLayout,
workers: usize,
) -> Result<VideoFrame> {
if src.planes.is_empty() {
return Err(Error::invalid("pixfmt: grey source needs a plane"));
}
let w = src_info.width as usize;
let h = src_info.height as usize;
let tight = tight_plane(&src.planes[0], w * 2, h)?;
let sb = il.sample_bytes();
let plane = if il.bits == bits && bits == 16 {
tight.into_owned()
} else {
planar8::map_packed_banded(&tight, w * 2, w * sb, h, workers, |s, d| {
let n = d.len() / sb;
if il.bits > 8 {
yuv::depth_rescale_le16_plane(s, d, n, bits, il.bits);
} else {
yuv::depth_down_le16_plane(s, d, n, bits);
}
})
};
let mut planes = vec![
VideoPlane {
stride: w * sb,
data: plane.clone(),
},
VideoPlane {
stride: w * sb,
data: plane.clone(),
},
VideoPlane {
stride: w * sb,
data: plane,
},
];
if il.alpha {
planes.push(VideoPlane {
stride: w * sb,
data: opaque_plane(w * h, il.bits),
});
}
Ok(make_frame(src, planes))
}
fn deep_gray_to_planar_family(
src: &VideoFrame,
src_info: FrameInfo,
bits: u32,
d: PlanarYuv,
limited: bool,
workers: usize,
) -> Result<VideoFrame> {
if src.planes.is_empty() {
return Err(Error::invalid("pixfmt: grey source needs a plane"));
}
let w = src_info.width as usize;
let h = src_info.height as usize;
let (cw, ch) = chroma_dims(w, h, d.wsub, d.hsub);
let sb = d.sample_bytes();
let tight = tight_plane(&src.planes[0], w * 2, h)?;
let luma = luma_depth_range_banded(&tight, w, h, bits, d.bits, limited, true, workers);
let mut planes = vec![
VideoPlane {
stride: w * sb,
data: luma,
},
VideoPlane {
stride: cw * sb,
data: neutral_chroma_plane(cw * ch, d.bits),
},
VideoPlane {
stride: cw * sb,
data: neutral_chroma_plane(cw * ch, d.bits),
},
];
if d.alpha {
planes.push(VideoPlane {
stride: w * sb,
data: opaque_plane(w * h, d.bits),
});
}
Ok(make_frame(src, planes))
}
fn planar_family_to_deep_gray(
src: &VideoFrame,
src_info: FrameInfo,
s: PlanarYuv,
bits: u32,
limited: bool,
workers: usize,
) -> Result<VideoFrame> {
if src.planes.is_empty() {
return Err(Error::invalid("pixfmt: YUV source needs a luma plane"));
}
let w = src_info.width as usize;
let h = src_info.height as usize;
let tight = tight_plane(&src.planes[0], w * s.sample_bytes(), h)?;
let out = luma_depth_range_banded(&tight, w, h, s.bits, bits, limited, false, workers);
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * 2,
data: out,
}],
))
}
fn do_ya8_to_gray8(src: &VideoFrame, src_info: FrameInfo) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let in_plane = &src.planes[0];
let mut out = vec![0u8; w * h];
for row in 0..h {
let sr = tight_row(&in_plane.data, in_plane.stride, row, w * 2);
gray::ya8_to_gray8(sr, &mut out[row * w..row * w + w], w);
}
Ok(make_frame(
src,
vec![VideoPlane {
stride: w,
data: out,
}],
))
}
fn do_gray8_to_ya8(src: &VideoFrame, src_info: FrameInfo) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let in_plane = &src.planes[0];
let mut out = vec![0u8; w * h * 2];
for row in 0..h {
let sr = tight_row(&in_plane.data, in_plane.stride, row, w);
gray::gray8_to_ya8(sr, &mut out[row * w * 2..row * w * 2 + w * 2], w);
}
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * 2,
data: out,
}],
))
}
fn do_ya8_to_rgb24(src: &VideoFrame, src_info: FrameInfo) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let in_plane = &src.planes[0];
let mut out = vec![0u8; w * h * 3];
for row in 0..h {
let sr = tight_row(&in_plane.data, in_plane.stride, row, w * 2);
gray::ya8_to_rgb24(sr, &mut out[row * w * 3..row * w * 3 + w * 3], w);
}
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * 3,
data: out,
}],
))
}
fn do_ya8_to_rgba(src: &VideoFrame, src_info: FrameInfo) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let in_plane = &src.planes[0];
let mut out = vec![0u8; w * h * 4];
for row in 0..h {
let sr = tight_row(&in_plane.data, in_plane.stride, row, w * 2);
gray::ya8_to_rgba(sr, &mut out[row * w * 4..row * w * 4 + w * 4], w);
}
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * 4,
data: out,
}],
))
}
fn do_rgb24_to_ya8(src: &VideoFrame, src_info: FrameInfo) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let in_plane = &src.planes[0];
let mut out = vec![0u8; w * h * 2];
for row in 0..h {
let sr = tight_row(&in_plane.data, in_plane.stride, row, w * 3);
gray::rgb24_to_ya8(sr, &mut out[row * w * 2..row * w * 2 + w * 2], w);
}
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * 2,
data: out,
}],
))
}
fn do_rgba_to_ya8(src: &VideoFrame, src_info: FrameInfo) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let in_plane = &src.planes[0];
let mut out = vec![0u8; w * h * 2];
for row in 0..h {
let sr = tight_row(&in_plane.data, in_plane.stride, row, w * 4);
gray::rgba_to_ya8(sr, &mut out[row * w * 2..row * w * 2 + w * 2], w);
}
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * 2,
data: out,
}],
))
}
fn do_yuv_luma_to_gray(
src: &VideoFrame,
src_info: FrameInfo,
full_range: bool,
) -> Result<VideoFrame> {
if src.planes.is_empty() {
return Err(Error::invalid("pixfmt: YUV source needs a luma plane"));
}
let w = src_info.width as usize;
let h = src_info.height as usize;
let mut yp = gather_tight(&src.planes[0].data, src.planes[0].stride, w, h)?;
if !full_range {
yuv::limited_to_full_luma(&mut yp);
}
Ok(make_frame(
src,
vec![VideoPlane {
stride: w,
data: yp,
}],
))
}
fn do_gray_to_yuv_planar(
src: &VideoFrame,
src_info: FrameInfo,
wsub: usize,
hsub: usize,
full_range: bool,
) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let (cw, ch) = chroma_grid(w, h, wsub, hsub)?;
let mut yp = gather_tight(&src.planes[0].data, src.planes[0].stride, w, h)?;
if !full_range {
yuv::full_to_limited_luma(&mut yp);
}
Ok(make_frame(
src,
vec![
VideoPlane {
stride: w,
data: yp,
},
VideoPlane {
stride: cw,
data: vec![128u8; cw * ch],
},
VideoPlane {
stride: cw,
data: vec![128u8; cw * ch],
},
],
))
}
fn do_gray_to_nv(src: &VideoFrame, src_info: FrameInfo, full_range: bool) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let (cw, ch) = chroma_dims(w, h, 2, 2);
let mut yp = gather_tight(&src.planes[0].data, src.planes[0].stride, w, h)?;
if !full_range {
yuv::full_to_limited_luma(&mut yp);
}
Ok(make_frame(
src,
vec![
VideoPlane {
stride: w,
data: yp,
},
VideoPlane {
stride: cw * 2,
data: vec![128u8; cw * ch * 2],
},
],
))
}
fn do_yuv_to_rgb(
src: &VideoFrame,
src_info: FrameInfo,
matrix: YuvMatrix,
wsub: usize,
hsub: usize,
alpha: bool,
workers: usize,
) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let (yp, up, vp, _) = planar8_source(src, w, h, wsub, hsub, false)?;
let bpp = if alpha { 4 } else { 3 };
let out = planar8::decode(
&Planar8 {
y: &yp,
u: &up,
v: &vp,
a: None,
w,
h,
wsub,
hsub,
},
bpp,
matrix,
workers,
);
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * bpp,
data: out,
}],
))
}
fn do_rgb_to_yuv(
src: &VideoFrame,
src_info: FrameInfo,
matrix: YuvMatrix,
wsub: usize,
hsub: usize,
alpha_in: bool,
workers: usize,
) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let (cw, _ch) = chroma_grid(w, h, wsub, hsub)?;
let packed = packed8_source(src, w, h, if alpha_in { 4 } else { 3 })?;
let (out_wsub, out_hsub) = if wsub == 4 { (1, 1) } else { (wsub, hsub) };
let enc = planar8::encode(&packed, out_wsub, out_hsub, matrix, false, workers);
let (up, vp) = if wsub == 4 {
let mut u = vec![0u8; cw * h];
let mut v = vec![0u8; cw * h];
yuv::chroma_444_to_411(&enc.u, &mut u, w, h);
yuv::chroma_444_to_411(&enc.v, &mut v, w, h);
(u, v)
} else {
(enc.u, enc.v)
};
Ok(make_frame(
src,
vec![
VideoPlane {
stride: w,
data: enc.y,
},
VideoPlane {
stride: cw,
data: up,
},
VideoPlane {
stride: cw,
data: vp,
},
],
))
}
fn chroma_grid(w: usize, h: usize, wsub: usize, hsub: usize) -> Result<(usize, usize)> {
if wsub == 4 && w % 4 != 0 {
return Err(Error::invalid(
"pixfmt: 4:1:1 YUV requires width divisible by 4",
));
}
Ok(chroma_dims(w, h, wsub, hsub))
}
#[allow(clippy::type_complexity)]
fn planar8_source<'a>(
src: &'a VideoFrame,
w: usize,
h: usize,
wsub: usize,
hsub: usize,
alpha: bool,
) -> Result<(
std::borrow::Cow<'a, [u8]>,
std::borrow::Cow<'a, [u8]>,
std::borrow::Cow<'a, [u8]>,
Option<std::borrow::Cow<'a, [u8]>>,
)> {
let need = if alpha { 4 } else { 3 };
if src.planes.len() < need {
return Err(Error::invalid(format!(
"pixfmt: planar YUV source needs {need} planes"
)));
}
let (cw, ch) = chroma_grid(w, h, wsub, hsub)?;
let yp = tight_plane(&src.planes[0], w, h)?;
let up = tight_plane(&src.planes[1], cw, ch)?;
let vp = tight_plane(&src.planes[2], cw, ch)?;
let ap = if alpha {
Some(tight_plane(&src.planes[3], w, h)?)
} else {
None
};
Ok((yp, up, vp, ap))
}
fn packed8_source(src: &VideoFrame, w: usize, h: usize, bpp: usize) -> Result<Packed8<'_>> {
let Some(plane) = src.planes.first() else {
return Err(Error::invalid("pixfmt: packed RGB source needs a plane"));
};
let _ = tight_plane(plane, w * bpp, h)?;
Ok(Packed8 {
data: &plane.data,
stride: plane.stride,
bpp,
w,
h,
})
}
fn rescale_range(
src: &VideoFrame,
src_info: FrameInfo,
wsub: usize,
hsub: usize,
to_full: bool,
rescale: bool,
) -> Result<VideoFrame> {
if src.planes.len() < 3 {
return Err(Error::invalid("pixfmt: YuvJ source needs 3 planes"));
}
let w = src_info.width as usize;
let h = src_info.height as usize;
let (cw, ch) = chroma_grid(w, h, wsub, hsub)?;
let mut yp = gather_tight(&src.planes[0].data, src.planes[0].stride, w, h)?;
let mut up = gather_tight(&src.planes[1].data, src.planes[1].stride, cw, ch)?;
let mut vp = gather_tight(&src.planes[2].data, src.planes[2].stride, cw, ch)?;
if !rescale {
} else if to_full {
yuv::limited_to_full_luma(&mut yp);
yuv::limited_to_full_chroma(&mut up);
yuv::limited_to_full_chroma(&mut vp);
} else {
yuv::full_to_limited_luma(&mut yp);
yuv::full_to_limited_chroma(&mut up);
yuv::full_to_limited_chroma(&mut vp);
}
Ok(make_frame(
src,
vec![
VideoPlane {
stride: w,
data: yp,
},
VideoPlane {
stride: cw,
data: up,
},
VideoPlane {
stride: cw,
data: vp,
},
],
))
}
fn chroma_resample(
src: &VideoFrame,
src_info: FrameInfo,
src_wsub: usize,
src_hsub: usize,
dst_wsub: usize,
dst_hsub: usize,
) -> Result<VideoFrame> {
if src.planes.len() < 3 {
return Err(Error::invalid(
"pixfmt: planar YUV source needs 3 planes (Y, U, V)",
));
}
let w = src_info.width as usize;
let h = src_info.height as usize;
let (src_cw, src_ch) = chroma_grid(w, h, src_wsub, src_hsub)?;
let (dst_cw, _) = chroma_grid(w, h, dst_wsub, dst_hsub)?;
let yp = gather_tight(&src.planes[0].data, src.planes[0].stride, w, h)?;
let u_src = gather_tight(&src.planes[1].data, src.planes[1].stride, src_cw, src_ch)?;
let v_src = gather_tight(&src.planes[2].data, src.planes[2].stride, src_cw, src_ch)?;
let (u_dst, v_dst) =
resample_chroma_pair(&u_src, &v_src, w, h, src_wsub, src_hsub, dst_wsub, dst_hsub)?;
Ok(make_frame(
src,
vec![
VideoPlane {
stride: w,
data: yp,
},
VideoPlane {
stride: dst_cw,
data: u_dst,
},
VideoPlane {
stride: dst_cw,
data: v_dst,
},
],
))
}
#[allow(clippy::too_many_arguments)]
fn resample_chroma_pair(
u_src: &[u8],
v_src: &[u8],
w: usize,
h: usize,
src_wsub: usize,
src_hsub: usize,
dst_wsub: usize,
dst_hsub: usize,
) -> Result<(Vec<u8>, Vec<u8>)> {
let (dst_cw, dst_ch) = chroma_dims(w, h, dst_wsub, dst_hsub);
let mut u_dst = vec![0u8; dst_cw * dst_ch];
let mut v_dst = vec![0u8; dst_cw * dst_ch];
if (src_wsub, src_hsub) == (1, 2) || (dst_wsub, dst_hsub) == (1, 2) {
let (u444, v444) = if (src_wsub, src_hsub) == (1, 2) {
let mut u = vec![0u8; w * h];
let mut v = vec![0u8; w * h];
yuv::chroma_440_to_444(u_src, &mut u, w, h);
yuv::chroma_440_to_444(v_src, &mut v, w, h);
(u, v)
} else if (src_wsub, src_hsub) == (1, 1) {
(u_src[..w * h].to_vec(), v_src[..w * h].to_vec())
} else {
resample_chroma_pair(u_src, v_src, w, h, src_wsub, src_hsub, 1, 1)?
};
if (dst_wsub, dst_hsub) == (1, 2) {
yuv::chroma_444_to_440(&u444, &mut u_dst, w, h);
yuv::chroma_444_to_440(&v444, &mut v_dst, w, h);
return Ok((u_dst, v_dst));
}
if (dst_wsub, dst_hsub) == (1, 1) {
return Ok((u444, v444));
}
return resample_chroma_pair(&u444, &v444, w, h, 1, 1, dst_wsub, dst_hsub);
}
match (src_wsub, src_hsub, dst_wsub, dst_hsub) {
(1, 1, 2, 1) => {
yuv::chroma_444_to_422(u_src, &mut u_dst, w, h);
yuv::chroma_444_to_422(v_src, &mut v_dst, w, h);
}
(2, 1, 1, 1) => {
yuv::chroma_422_to_444(u_src, &mut u_dst, w, h);
yuv::chroma_422_to_444(v_src, &mut v_dst, w, h);
}
(1, 1, 2, 2) => {
yuv::chroma_444_to_420(u_src, &mut u_dst, w, h);
yuv::chroma_444_to_420(v_src, &mut v_dst, w, h);
}
(2, 2, 1, 1) => {
yuv::chroma_420_to_444(u_src, &mut u_dst, w, h);
yuv::chroma_420_to_444(v_src, &mut v_dst, w, h);
}
(2, 1, 2, 2) => {
yuv::chroma_422_to_420(u_src, &mut u_dst, w, h);
yuv::chroma_422_to_420(v_src, &mut v_dst, w, h);
}
(2, 2, 2, 1) => {
yuv::chroma_420_to_422(u_src, &mut u_dst, w, h);
yuv::chroma_420_to_422(v_src, &mut v_dst, w, h);
}
(1, 1, 4, 1) => {
yuv::chroma_444_to_411(u_src, &mut u_dst, w, h);
yuv::chroma_444_to_411(v_src, &mut v_dst, w, h);
}
(4, 1, 1, 1) => {
yuv::chroma_411_to_444(u_src, &mut u_dst, w, h);
yuv::chroma_411_to_444(v_src, &mut v_dst, w, h);
}
(2, 1, 4, 1) => {
yuv::chroma_422_to_411(u_src, &mut u_dst, w, h);
yuv::chroma_422_to_411(v_src, &mut v_dst, w, h);
}
(4, 1, 2, 1) => {
yuv::chroma_411_to_422(u_src, &mut u_dst, w, h);
yuv::chroma_411_to_422(v_src, &mut v_dst, w, h);
}
(2, 2, 4, 1) => {
yuv::chroma_420_to_411(u_src, &mut u_dst, w, h);
yuv::chroma_420_to_411(v_src, &mut v_dst, w, h);
}
(4, 1, 2, 2) => {
yuv::chroma_411_to_420(u_src, &mut u_dst, w, h);
yuv::chroma_411_to_420(v_src, &mut v_dst, w, h);
}
_ => {
return Err(Error::unsupported(
"pixfmt: unregistered planar YUV chroma resample pair",
))
}
}
Ok((u_dst, v_dst))
}
fn yuva_chroma_resample(
src: &VideoFrame,
src_info: FrameInfo,
src_wsub: usize,
src_hsub: usize,
dst_wsub: usize,
dst_hsub: usize,
) -> Result<VideoFrame> {
if src.planes.len() < 4 {
return Err(Error::invalid(
"pixfmt: Yuva source needs 4 planes (Y, U, V, A)",
));
}
let w = src_info.width as usize;
let h = src_info.height as usize;
let (src_cw, src_ch) = chroma_grid(w, h, src_wsub, src_hsub)?;
let (dst_cw, _) = chroma_grid(w, h, dst_wsub, dst_hsub)?;
let yp = gather_tight(&src.planes[0].data, src.planes[0].stride, w, h)?;
let u_src = gather_tight(&src.planes[1].data, src.planes[1].stride, src_cw, src_ch)?;
let v_src = gather_tight(&src.planes[2].data, src.planes[2].stride, src_cw, src_ch)?;
let ap = gather_tight(&src.planes[3].data, src.planes[3].stride, w, h)?;
let (u_dst, v_dst) =
resample_chroma_pair(&u_src, &v_src, w, h, src_wsub, src_hsub, dst_wsub, dst_hsub)?;
Ok(make_frame(
src,
vec![
VideoPlane {
stride: w,
data: yp,
},
VideoPlane {
stride: dst_cw,
data: u_dst,
},
VideoPlane {
stride: dst_cw,
data: v_dst,
},
VideoPlane {
stride: w,
data: ap,
},
],
))
}
fn chroma_resample16(
src: &VideoFrame,
src_info: FrameInfo,
src_wsub: usize,
src_hsub: usize,
dst_wsub: usize,
dst_hsub: usize,
) -> Result<VideoFrame> {
if src.planes.len() < 3 {
return Err(Error::invalid(
"pixfmt: planar YUV source needs 3 planes (Y, U, V)",
));
}
let w = src_info.width as usize;
let h = src_info.height as usize;
let (src_cw, src_ch) = chroma_dims(w, h, src_wsub, src_hsub);
let (dst_cw, _) = chroma_dims(w, h, dst_wsub, dst_hsub);
let yp = gather_tight(&src.planes[0].data, src.planes[0].stride, w * 2, h)?;
let u_src = gather_tight(
&src.planes[1].data,
src.planes[1].stride,
src_cw * 2,
src_ch,
)?;
let v_src = gather_tight(
&src.planes[2].data,
src.planes[2].stride,
src_cw * 2,
src_ch,
)?;
let (u_dst, v_dst) =
resample_chroma16_pair(&u_src, &v_src, w, h, src_wsub, src_hsub, dst_wsub, dst_hsub)?;
Ok(make_frame(
src,
vec![
VideoPlane {
stride: w * 2,
data: yp,
},
VideoPlane {
stride: dst_cw * 2,
data: u_dst,
},
VideoPlane {
stride: dst_cw * 2,
data: v_dst,
},
],
))
}
#[allow(clippy::too_many_arguments)]
fn resample_chroma16_pair(
u_src: &[u8],
v_src: &[u8],
w: usize,
h: usize,
src_wsub: usize,
src_hsub: usize,
dst_wsub: usize,
dst_hsub: usize,
) -> Result<(Vec<u8>, Vec<u8>)> {
let (dst_cw, dst_ch) = chroma_dims(w, h, dst_wsub, dst_hsub);
let mut u_dst = vec![0u8; dst_cw * dst_ch * 2];
let mut v_dst = vec![0u8; dst_cw * dst_ch * 2];
if (src_wsub, src_hsub) == (1, 2) || (dst_wsub, dst_hsub) == (1, 2) {
let (u444, v444) = if (src_wsub, src_hsub) == (1, 2) {
let mut u = vec![0u8; w * h * 2];
let mut v = vec![0u8; w * h * 2];
yuv::chroma16le_440_to_444(u_src, &mut u, w, h);
yuv::chroma16le_440_to_444(v_src, &mut v, w, h);
(u, v)
} else if (src_wsub, src_hsub) == (1, 1) {
(u_src[..w * h * 2].to_vec(), v_src[..w * h * 2].to_vec())
} else {
resample_chroma16_pair(u_src, v_src, w, h, src_wsub, src_hsub, 1, 1)?
};
if (dst_wsub, dst_hsub) == (1, 2) {
yuv::chroma16le_444_to_440(&u444, &mut u_dst, w, h);
yuv::chroma16le_444_to_440(&v444, &mut v_dst, w, h);
return Ok((u_dst, v_dst));
}
if (dst_wsub, dst_hsub) == (1, 1) {
return Ok((u444, v444));
}
return resample_chroma16_pair(&u444, &v444, w, h, 1, 1, dst_wsub, dst_hsub);
}
match (src_wsub, src_hsub, dst_wsub, dst_hsub) {
(1, 1, 2, 1) => {
yuv::chroma16le_444_to_422(u_src, &mut u_dst, w, h);
yuv::chroma16le_444_to_422(v_src, &mut v_dst, w, h);
}
(2, 1, 1, 1) => {
yuv::chroma16le_422_to_444(u_src, &mut u_dst, w, h);
yuv::chroma16le_422_to_444(v_src, &mut v_dst, w, h);
}
(1, 1, 2, 2) => {
yuv::chroma16le_444_to_420(u_src, &mut u_dst, w, h);
yuv::chroma16le_444_to_420(v_src, &mut v_dst, w, h);
}
(2, 2, 1, 1) => {
yuv::chroma16le_420_to_444(u_src, &mut u_dst, w, h);
yuv::chroma16le_420_to_444(v_src, &mut v_dst, w, h);
}
(2, 1, 2, 2) => {
yuv::chroma16le_422_to_420(u_src, &mut u_dst, w, h);
yuv::chroma16le_422_to_420(v_src, &mut v_dst, w, h);
}
(2, 2, 2, 1) => {
yuv::chroma16le_420_to_422(u_src, &mut u_dst, w, h);
yuv::chroma16le_420_to_422(v_src, &mut v_dst, w, h);
}
_ => {
return Err(Error::unsupported(
"pixfmt: unregistered 16-bit planar YUV chroma resample pair",
))
}
}
Ok((u_dst, v_dst))
}
fn plane_to_depth(src: &[u8], count: usize, src_bits: u32, dst_bits: u32) -> Vec<u8> {
match (src_bits > 8, dst_bits > 8) {
(false, false) => src[..count].to_vec(),
(false, true) => {
let mut out = vec![0u8; count * 2];
yuv::depth_up_8_to_le16_plane(src, &mut out, count, dst_bits);
out
}
(true, false) => {
let mut out = vec![0u8; count];
yuv::depth_down_le16_plane(src, &mut out, count, src_bits);
out
}
(true, true) => {
let mut out = vec![0u8; count * 2];
yuv::depth_rescale_le16_plane(src, &mut out, count, src_bits, dst_bits);
out
}
}
}
#[allow(clippy::too_many_arguments)]
fn resample_pair_at_depth(
u_src: &[u8],
v_src: &[u8],
w: usize,
h: usize,
src_wsub: usize,
src_hsub: usize,
dst_wsub: usize,
dst_hsub: usize,
bits: u32,
) -> Result<(Vec<u8>, Vec<u8>)> {
if bits > 8 {
resample_chroma16_pair(u_src, v_src, w, h, src_wsub, src_hsub, dst_wsub, dst_hsub)
} else {
resample_chroma_pair(u_src, v_src, w, h, src_wsub, src_hsub, dst_wsub, dst_hsub)
}
}
fn opaque_plane(count: usize, bits: u32) -> Vec<u8> {
if bits > 8 {
let full = ((1u32 << bits) - 1) as u16;
let [lo, hi] = full.to_le_bytes();
let mut out = vec![0u8; count * 2];
for word in out.chunks_exact_mut(2) {
word[0] = lo;
word[1] = hi;
}
out
} else {
vec![0xFF; count]
}
}
fn neutral_chroma_plane(count: usize, bits: u32) -> Vec<u8> {
if bits > 8 {
let mid = (1u32 << (bits - 1)) as u16;
let [lo, hi] = mid.to_le_bytes();
let mut out = vec![0u8; count * 2];
for word in out.chunks_exact_mut(2) {
word[0] = lo;
word[1] = hi;
}
out
} else {
vec![128; count]
}
}
fn planar_family(
src: &VideoFrame,
src_info: FrameInfo,
s: PlanarYuv,
d: PlanarYuv,
) -> Result<VideoFrame> {
let need = if s.alpha { 4 } else { 3 };
if src.planes.len() < need {
return Err(Error::invalid(
"pixfmt: planar YUV(A) source is missing planes",
));
}
let w = src_info.width as usize;
let h = src_info.height as usize;
let (scw, sch) = chroma_dims(w, h, s.wsub, s.hsub);
let (dcw, dch) = chroma_dims(w, h, d.wsub, d.hsub);
let sb_src = s.sample_bytes();
let sb_dst = d.sample_bytes();
let y_src = gather_tight(&src.planes[0].data, src.planes[0].stride, w * sb_src, h)?;
let u_src = gather_tight(&src.planes[1].data, src.planes[1].stride, scw * sb_src, sch)?;
let v_src = gather_tight(&src.planes[2].data, src.planes[2].stride, scw * sb_src, sch)?;
let yp = plane_to_depth(&y_src, w * h, s.bits, d.bits);
let (up, vp) = if (s.wsub, s.hsub) == (d.wsub, d.hsub) {
(
plane_to_depth(&u_src, scw * sch, s.bits, d.bits),
plane_to_depth(&v_src, scw * sch, s.bits, d.bits),
)
} else if d.bits >= s.bits {
let u_mid = plane_to_depth(&u_src, scw * sch, s.bits, d.bits);
let v_mid = plane_to_depth(&v_src, scw * sch, s.bits, d.bits);
resample_pair_at_depth(&u_mid, &v_mid, w, h, s.wsub, s.hsub, d.wsub, d.hsub, d.bits)?
} else {
let (u_mid, v_mid) =
resample_pair_at_depth(&u_src, &v_src, w, h, s.wsub, s.hsub, d.wsub, d.hsub, s.bits)?;
(
plane_to_depth(&u_mid, dcw * dch, s.bits, d.bits),
plane_to_depth(&v_mid, dcw * dch, s.bits, d.bits),
)
};
let mut planes = vec![
VideoPlane {
stride: w * sb_dst,
data: yp,
},
VideoPlane {
stride: dcw * sb_dst,
data: up,
},
VideoPlane {
stride: dcw * sb_dst,
data: vp,
},
];
if d.alpha {
let ap = if s.alpha {
let a_src = gather_tight(&src.planes[3].data, src.planes[3].stride, w * sb_src, h)?;
plane_to_depth(&a_src, w * h, s.bits, d.bits)
} else {
opaque_plane(w * h, d.bits)
};
planes.push(VideoPlane {
stride: w * sb_dst,
data: ap,
});
}
Ok(make_frame(src, planes))
}
fn planar_family_to_rgb(
src: &VideoFrame,
src_info: FrameInfo,
matrix: YuvMatrix,
s: PlanarYuv,
alpha: bool,
workers: usize,
) -> Result<VideoFrame> {
let need = if s.alpha { 4 } else { 3 };
if src.planes.len() < need {
return Err(Error::invalid(
"pixfmt: planar YUV(A) source is missing planes",
));
}
let w = src_info.width as usize;
let h = src_info.height as usize;
let (cw, ch) = chroma_dims(w, h, s.wsub, s.hsub);
let sb = s.sample_bytes();
let y_src = tight_plane(&src.planes[0], w * sb, h)?;
let u_src = tight_plane(&src.planes[1], cw * sb, ch)?;
let v_src = tight_plane(&src.planes[2], cw * sb, ch)?;
let yp = plane_to_depth_cow(y_src, w * h, s.bits, workers);
let up = plane_to_depth_cow(u_src, cw * ch, s.bits, workers);
let vp = plane_to_depth_cow(v_src, cw * ch, s.bits, workers);
let ap = if alpha && s.alpha {
let a_src = tight_plane(&src.planes[3], w * sb, h)?;
Some(plane_to_depth_cow(a_src, w * h, s.bits, workers))
} else {
None
};
let bpp = if alpha { 4 } else { 3 };
let out = planar8::decode(
&Planar8 {
y: &yp,
u: &up,
v: &vp,
a: ap.as_deref(),
w,
h,
wsub: s.wsub,
hsub: s.hsub,
},
bpp,
matrix,
workers,
);
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * bpp,
data: out,
}],
))
}
fn plane_to_depth_cow(
src: std::borrow::Cow<'_, [u8]>,
count: usize,
src_bits: u32,
workers: usize,
) -> std::borrow::Cow<'_, [u8]> {
if src_bits > 8 {
std::borrow::Cow::Owned(planar8::narrow_to_8(&src, count, src_bits, workers))
} else {
src
}
}
fn rgb_to_planar_family(
src: &VideoFrame,
src_info: FrameInfo,
matrix: YuvMatrix,
d: PlanarYuv,
alpha_in: bool,
workers: usize,
) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let sb = d.sample_bytes();
let packed = packed8_source(src, w, h, if alpha_in { 4 } else { 3 })?;
let (enc_wsub, enc_hsub) = if (d.wsub, d.hsub) == (1, 2) {
(1, 1)
} else {
(d.wsub, d.hsub)
};
let enc = planar8::encode(&packed, enc_wsub, enc_hsub, matrix, d.alpha, workers);
let (cw, ch) = chroma_dims(w, h, d.wsub, d.hsub);
let (up8, vp8) = if (d.wsub, d.hsub) == (1, 2) {
let mut u = vec![0u8; cw * ch];
let mut v = vec![0u8; cw * ch];
yuv::chroma_444_to_440(&enc.u, &mut u, w, h);
yuv::chroma_444_to_440(&enc.v, &mut v, w, h);
(u, v)
} else {
(enc.u, enc.v)
};
let mut planes = vec![
VideoPlane {
stride: w * sb,
data: plane_to_depth(&enc.y, w * h, 8, d.bits),
},
VideoPlane {
stride: cw * sb,
data: plane_to_depth(&up8, cw * ch, 8, d.bits),
},
VideoPlane {
stride: cw * sb,
data: plane_to_depth(&vp8, cw * ch, 8, d.bits),
},
];
if d.alpha {
let ap = match enc.a {
Some(alpha8) => plane_to_depth(&alpha8, w * h, 8, d.bits),
None => opaque_plane(w * h, d.bits),
};
planes.push(VideoPlane {
stride: w * sb,
data: ap,
});
}
Ok(make_frame(src, planes))
}
fn planar_family_to_gray(
src: &VideoFrame,
src_info: FrameInfo,
s: PlanarYuv,
limited: bool,
) -> Result<VideoFrame> {
if src.planes.is_empty() {
return Err(Error::invalid("pixfmt: YUV source needs a luma plane"));
}
let w = src_info.width as usize;
let h = src_info.height as usize;
let sb = s.sample_bytes();
let y_src = gather_tight(&src.planes[0].data, src.planes[0].stride, w * sb, h)?;
let mut yp = plane_to_depth(&y_src, w * h, s.bits, 8);
if limited {
yuv::limited_to_full_luma(&mut yp);
}
Ok(make_frame(
src,
vec![VideoPlane {
stride: w,
data: yp,
}],
))
}
fn gray_to_planar_family(
src: &VideoFrame,
src_info: FrameInfo,
d: PlanarYuv,
limited: bool,
) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let (cw, ch) = chroma_dims(w, h, d.wsub, d.hsub);
let sb = d.sample_bytes();
let mut luma8 = gather_tight(&src.planes[0].data, src.planes[0].stride, w, h)?;
if limited {
yuv::full_to_limited_luma(&mut luma8);
}
let mut planes = vec![
VideoPlane {
stride: w * sb,
data: plane_to_depth(&luma8, w * h, 8, d.bits),
},
VideoPlane {
stride: cw * sb,
data: neutral_chroma_plane(cw * ch, d.bits),
},
VideoPlane {
stride: cw * sb,
data: neutral_chroma_plane(cw * ch, d.bits),
},
];
if d.alpha {
planes.push(VideoPlane {
stride: w * sb,
data: opaque_plane(w * h, d.bits),
});
}
Ok(make_frame(src, planes))
}
fn do_yuv_depth_down(
src: &VideoFrame,
src_info: FrameInfo,
wsub: usize,
hsub: usize,
bits: u32,
) -> Result<VideoFrame> {
if src.planes.len() < 3 {
return Err(Error::invalid(
"pixfmt: high-bit YUV source needs 3 planes (Y, U, V)",
));
}
let w = src_info.width as usize;
let h = src_info.height as usize;
let (cw, ch) = chroma_dims(w, h, wsub, hsub);
let y_src = gather_tight(&src.planes[0].data, src.planes[0].stride, w * 2, h)?;
let u_src = gather_tight(&src.planes[1].data, src.planes[1].stride, cw * 2, ch)?;
let v_src = gather_tight(&src.planes[2].data, src.planes[2].stride, cw * 2, ch)?;
let mut yp = vec![0u8; w * h];
let mut up = vec![0u8; cw * ch];
let mut vp = vec![0u8; cw * ch];
yuv::depth_down_le16_plane(&y_src, &mut yp, w * h, bits);
yuv::depth_down_le16_plane(&u_src, &mut up, cw * ch, bits);
yuv::depth_down_le16_plane(&v_src, &mut vp, cw * ch, bits);
Ok(make_frame(
src,
vec![
VideoPlane {
stride: w,
data: yp,
},
VideoPlane {
stride: cw,
data: up,
},
VideoPlane {
stride: cw,
data: vp,
},
],
))
}
fn do_yuv_depth_up(
src: &VideoFrame,
src_info: FrameInfo,
wsub: usize,
hsub: usize,
bits: u32,
) -> Result<VideoFrame> {
if src.planes.len() < 3 {
return Err(Error::invalid(
"pixfmt: planar YUV source needs 3 planes (Y, U, V)",
));
}
let w = src_info.width as usize;
let h = src_info.height as usize;
let (cw, ch) = chroma_dims(w, h, wsub, hsub);
let y_src = gather_tight(&src.planes[0].data, src.planes[0].stride, w, h)?;
let u_src = gather_tight(&src.planes[1].data, src.planes[1].stride, cw, ch)?;
let v_src = gather_tight(&src.planes[2].data, src.planes[2].stride, cw, ch)?;
let mut yp = vec![0u8; w * h * 2];
let mut up = vec![0u8; cw * ch * 2];
let mut vp = vec![0u8; cw * ch * 2];
yuv::depth_up_8_to_le16_plane(&y_src, &mut yp, w * h, bits);
yuv::depth_up_8_to_le16_plane(&u_src, &mut up, cw * ch, bits);
yuv::depth_up_8_to_le16_plane(&v_src, &mut vp, cw * ch, bits);
Ok(make_frame(
src,
vec![
VideoPlane {
stride: w * 2,
data: yp,
},
VideoPlane {
stride: cw * 2,
data: up,
},
VideoPlane {
stride: cw * 2,
data: vp,
},
],
))
}
fn do_yuv_depth_rescale(
src: &VideoFrame,
src_info: FrameInfo,
wsub: usize,
hsub: usize,
src_bits: u32,
dst_bits: u32,
) -> Result<VideoFrame> {
if src.planes.len() < 3 {
return Err(Error::invalid(
"pixfmt: high-bit YUV source needs 3 planes (Y, U, V)",
));
}
let w = src_info.width as usize;
let h = src_info.height as usize;
let (cw, ch) = chroma_dims(w, h, wsub, hsub);
let y_src = gather_tight(&src.planes[0].data, src.planes[0].stride, w * 2, h)?;
let u_src = gather_tight(&src.planes[1].data, src.planes[1].stride, cw * 2, ch)?;
let v_src = gather_tight(&src.planes[2].data, src.planes[2].stride, cw * 2, ch)?;
let mut yp = vec![0u8; w * h * 2];
let mut up = vec![0u8; cw * ch * 2];
let mut vp = vec![0u8; cw * ch * 2];
yuv::depth_rescale_le16_plane(&y_src, &mut yp, w * h, src_bits, dst_bits);
yuv::depth_rescale_le16_plane(&u_src, &mut up, cw * ch, src_bits, dst_bits);
yuv::depth_rescale_le16_plane(&v_src, &mut vp, cw * ch, src_bits, dst_bits);
Ok(make_frame(
src,
vec![
VideoPlane {
stride: w * 2,
data: yp,
},
VideoPlane {
stride: cw * 2,
data: up,
},
VideoPlane {
stride: cw * 2,
data: vp,
},
],
))
}
fn do_gray_depth_down8(src: &VideoFrame, src_info: FrameInfo, bits: u32) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let in_plane = &src.planes[0];
let tight = gather_tight(&in_plane.data, in_plane.stride, w * 2, h)?;
let mut out = vec![0u8; w * h];
yuv::depth_down_le16_plane(&tight, &mut out, w * h, bits);
Ok(make_frame(
src,
vec![VideoPlane {
stride: w,
data: out,
}],
))
}
fn do_gray_depth_up8(src: &VideoFrame, src_info: FrameInfo, bits: u32) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let in_plane = &src.planes[0];
let tight = gather_tight(&in_plane.data, in_plane.stride, w, h)?;
let mut out = vec![0u8; w * h * 2];
yuv::depth_up_8_to_le16_plane(&tight, &mut out, w * h, bits);
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * 2,
data: out,
}],
))
}
fn do_gray_depth_rescale(
src: &VideoFrame,
src_info: FrameInfo,
src_bits: u32,
dst_bits: u32,
) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let in_plane = &src.planes[0];
let tight = gather_tight(&in_plane.data, in_plane.stride, w * 2, h)?;
let mut out = vec![0u8; w * h * 2];
yuv::depth_rescale_le16_plane(&tight, &mut out, w * h, src_bits, dst_bits);
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * 2,
data: out,
}],
))
}
fn nv_to_yuv420p(src: &VideoFrame, src_info: FrameInfo, is_nv12: bool) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let (yp, up, vp) = nv_source(src, w, h, is_nv12)?;
let cw = w.div_ceil(2);
Ok(make_frame(
src,
vec![
VideoPlane {
stride: w,
data: yp.into_owned(),
},
VideoPlane {
stride: cw,
data: up,
},
VideoPlane {
stride: cw,
data: vp,
},
],
))
}
fn yuv420p_to_nv(src: &VideoFrame, src_info: FrameInfo, is_nv12: bool) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let (yp, up, vp, _) = planar8_source(src, w, h, 2, 2, false)?;
let (cw, ch) = chroma_dims(w, h, 2, 2);
let mut uv = vec![0u8; cw * ch * 2];
if is_nv12 {
yuv::nv12_uv_merge(&up, &vp, &mut uv, cw, ch);
} else {
yuv::nv21_vu_merge(&up, &vp, &mut uv, cw, ch);
}
Ok(make_frame(
src,
vec![
VideoPlane {
stride: w,
data: yp.into_owned(),
},
VideoPlane {
stride: cw * 2,
data: uv,
},
],
))
}
fn nv_to_rgb(
src: &VideoFrame,
src_info: FrameInfo,
matrix: YuvMatrix,
is_nv12: bool,
alpha: bool,
workers: usize,
) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let (yp, up, vp) = nv_source(src, w, h, is_nv12)?;
let bpp = if alpha { 4 } else { 3 };
let out = planar8::decode(
&Planar8 {
y: &yp,
u: &up,
v: &vp,
a: None,
w,
h,
wsub: 2,
hsub: 2,
},
bpp,
matrix,
workers,
);
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * bpp,
data: out,
}],
))
}
#[allow(clippy::type_complexity)]
fn nv_source(
src: &VideoFrame,
w: usize,
h: usize,
is_nv12: bool,
) -> Result<(std::borrow::Cow<'_, [u8]>, Vec<u8>, Vec<u8>)> {
if src.planes.len() < 2 {
return Err(Error::invalid("pixfmt: NV source needs 2 planes"));
}
let (cw, ch) = chroma_dims(w, h, 2, 2);
let yp = tight_plane(&src.planes[0], w, h)?;
let uv = tight_plane(&src.planes[1], cw * 2, ch)?;
let mut up = vec![0u8; cw * ch];
let mut vp = vec![0u8; cw * ch];
if is_nv12 {
yuv::nv12_uv_split(&uv, &mut up, &mut vp, cw, ch);
} else {
yuv::nv21_vu_split(&uv, &mut up, &mut vp, cw, ch);
}
Ok((yp, up, vp))
}
fn rgb_to_nv(
src: &VideoFrame,
src_info: FrameInfo,
matrix: YuvMatrix,
is_nv12: bool,
alpha_in: bool,
workers: usize,
) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let packed = packed8_source(src, w, h, if alpha_in { 4 } else { 3 })?;
let enc = planar8::encode(&packed, 2, 2, matrix, false, workers);
let (cw, ch) = (enc.cw, enc.ch);
let mut uv = vec![0u8; cw * ch * 2];
if is_nv12 {
yuv::nv12_uv_merge(&enc.u, &enc.v, &mut uv, cw, ch);
} else {
yuv::nv21_vu_merge(&enc.u, &enc.v, &mut uv, cw, ch);
}
Ok(make_frame(
src,
vec![
VideoPlane {
stride: w,
data: enc.y,
},
VideoPlane {
stride: cw * 2,
data: uv,
},
],
))
}
fn packed422_to_yuv422p(
src: &VideoFrame,
src_info: FrameInfo,
is_yuyv: bool,
) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
if w % 2 != 0 {
return Err(Error::invalid("pixfmt: packed 4:2:2 requires even width"));
}
let cw = w / 2;
let in_plane = &src.planes[0];
let packed = gather_tight(&in_plane.data, in_plane.stride, w * 2, h)?;
let mut yp = vec![0u8; w * h];
let mut up = vec![0u8; cw * h];
let mut vp = vec![0u8; cw * h];
if is_yuyv {
yuv::yuyv422_to_yuv422p(&packed, &mut yp, &mut up, &mut vp, w, h);
} else {
yuv::uyvy422_to_yuv422p(&packed, &mut yp, &mut up, &mut vp, w, h);
}
Ok(make_frame(
src,
vec![
VideoPlane {
stride: w,
data: yp,
},
VideoPlane {
stride: cw,
data: up,
},
VideoPlane {
stride: cw,
data: vp,
},
],
))
}
fn yuv422p_to_packed422(
src: &VideoFrame,
src_info: FrameInfo,
is_yuyv: bool,
) -> Result<VideoFrame> {
if src.planes.len() < 3 {
return Err(Error::invalid("pixfmt: Yuv422P source needs 3 planes"));
}
let w = src_info.width as usize;
let h = src_info.height as usize;
if w % 2 != 0 {
return Err(Error::invalid("pixfmt: packed 4:2:2 requires even width"));
}
let cw = w / 2;
let yp = gather_tight(&src.planes[0].data, src.planes[0].stride, w, h)?;
let up = gather_tight(&src.planes[1].data, src.planes[1].stride, cw, h)?;
let vp = gather_tight(&src.planes[2].data, src.planes[2].stride, cw, h)?;
let mut packed = vec![0u8; w * h * 2];
if is_yuyv {
yuv::yuv422p_to_yuyv422(&yp, &up, &vp, &mut packed, w, h);
} else {
yuv::yuv422p_to_uyvy422(&yp, &up, &vp, &mut packed, w, h);
}
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * 2,
data: packed,
}],
))
}
fn packed422_swap(src: &VideoFrame, src_info: FrameInfo) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
if w % 2 != 0 {
return Err(Error::invalid("pixfmt: packed 4:2:2 requires even width"));
}
let in_plane = &src.planes[0];
let mut packed = gather_tight(&in_plane.data, in_plane.stride, w * 2, h)?;
yuv::yuyv_uyvy_swap(&mut packed);
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * 2,
data: packed,
}],
))
}
fn packed422_to_rgb(
src: &VideoFrame,
src_info: FrameInfo,
matrix: YuvMatrix,
is_yuyv: bool,
alpha: bool,
) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
if w % 2 != 0 {
return Err(Error::invalid("pixfmt: packed 4:2:2 requires even width"));
}
let cw = w / 2;
let in_plane = &src.planes[0];
let packed = gather_tight(&in_plane.data, in_plane.stride, w * 2, h)?;
let mut yp = vec![0u8; w * h];
let mut up = vec![0u8; cw * h];
let mut vp = vec![0u8; cw * h];
if is_yuyv {
yuv::yuyv422_to_yuv422p(&packed, &mut yp, &mut up, &mut vp, w, h);
} else {
yuv::uyvy422_to_yuv422p(&packed, &mut yp, &mut up, &mut vp, w, h);
}
let mut rgb_buf = vec![0u8; w * h * 3];
yuv::yuv422_to_rgb24(&yp, &up, &vp, &mut rgb_buf, w, h, matrix);
if !alpha {
return Ok(make_frame(
src,
vec![VideoPlane {
stride: w * 3,
data: rgb_buf,
}],
));
}
let mut rgba = vec![0u8; w * h * 4];
for i in 0..w * h {
rgba[i * 4] = rgb_buf[i * 3];
rgba[i * 4 + 1] = rgb_buf[i * 3 + 1];
rgba[i * 4 + 2] = rgb_buf[i * 3 + 2];
rgba[i * 4 + 3] = 255;
}
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * 4,
data: rgba,
}],
))
}
fn rgb_to_packed422(
src: &VideoFrame,
src_info: FrameInfo,
matrix: YuvMatrix,
is_yuyv: bool,
alpha_in: bool,
) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
if w % 2 != 0 {
return Err(Error::invalid("pixfmt: packed 4:2:2 requires even width"));
}
let cw = w / 2;
let in_plane = &src.planes[0];
let rgb24: Vec<u8> = if alpha_in {
let mut out = Vec::with_capacity(w * h * 3);
for row in 0..h {
let row_bytes = w * 4;
let sr = tight_row(&in_plane.data, in_plane.stride, row, row_bytes);
for i in 0..w {
out.push(sr[i * 4]);
out.push(sr[i * 4 + 1]);
out.push(sr[i * 4 + 2]);
}
}
out
} else {
gather_tight(&in_plane.data, in_plane.stride, w * 3, h)?
};
let mut yp = vec![0u8; w * h];
let mut up = vec![0u8; cw * h];
let mut vp = vec![0u8; cw * h];
yuv::rgb24_to_yuv422(&rgb24, &mut yp, &mut up, &mut vp, w, h, matrix);
let mut packed = vec![0u8; w * h * 2];
if is_yuyv {
yuv::yuv422p_to_yuyv422(&yp, &up, &vp, &mut packed, w, h);
} else {
yuv::yuv422p_to_uyvy422(&yp, &up, &vp, &mut packed, w, h);
}
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * 2,
data: packed,
}],
))
}
fn pal8_to_rgb(
src: &VideoFrame,
src_info: FrameInfo,
opts: &ConvertOptions,
alpha: bool,
) -> Result<VideoFrame> {
let side_channel = src.palette().map(|raw| {
let alpha = src.palette_alpha();
Palette {
colors: raw
.chunks_exact(3)
.enumerate()
.map(|(i, c)| {
let a = alpha.and_then(|a| a.get(i).copied()).unwrap_or(255);
[c[0], c[1], c[2], a]
})
.collect(),
}
});
let palette =
match (&side_channel, &opts.palette) {
(Some(p), _) => p,
(None, Some(p)) => p,
(None, None) => return Err(Error::invalid(
"pixfmt: Pal8 → RGB requires a frame-attached palette or ConvertOptions.palette",
)),
};
let w = src_info.width as usize;
let h = src_info.height as usize;
let in_plane = &src.planes[0];
if alpha {
let mut out = vec![0u8; w * h * 4];
for row in 0..h {
let sr = tight_row(&in_plane.data, in_plane.stride, row, w);
pal8::expand_row_to_rgba(sr, &mut out[row * w * 4..row * w * 4 + w * 4], palette, w);
}
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * 4,
data: out,
}],
))
} else {
let mut out = vec![0u8; w * h * 3];
for row in 0..h {
let sr = tight_row(&in_plane.data, in_plane.stride, row, w);
pal8::expand_row_to_rgb24(sr, &mut out[row * w * 3..row * w * 3 + w * 3], palette, w);
}
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * 3,
data: out,
}],
))
}
}
fn rgb_to_pal8(
src: &VideoFrame,
src_info: FrameInfo,
opts: &ConvertOptions,
alpha_in: bool,
) -> Result<VideoFrame> {
let palette = opts
.palette
.as_ref()
.ok_or_else(|| Error::invalid("pixfmt: RGB → Pal8 requires ConvertOptions.palette"))?;
let w = src_info.width as usize;
let h = src_info.height as usize;
let in_plane = &src.planes[0];
let mut out = vec![0u8; w * h];
if alpha_in {
let tight = gather_tight(&in_plane.data, in_plane.stride, w * 4, h)?;
pal8::quantise_rgba_to_pal8(&tight, &mut out, w, h, palette, opts.dither);
} else {
let tight = gather_tight(&in_plane.data, in_plane.stride, w * 3, h)?;
pal8::quantise_rgb24_to_pal8(&tight, &mut out, w, h, palette, opts.dither);
}
let side_channel: Vec<u8> = palette
.colors
.iter()
.flat_map(|c| [c[0], c[1], c[2]])
.collect();
let mut alpha: Vec<u8> = palette.colors.iter().map(|c| c[3]).collect();
while alpha.last() == Some(&255) {
alpha.pop();
}
let frame = make_frame(
src,
vec![VideoPlane {
stride: w,
data: out,
}],
)
.with_palette(side_channel);
Ok(if alpha.is_empty() {
frame
} else {
frame.with_palette_alpha(alpha)
})
}
fn do_yuv_to_yuva(
src: &VideoFrame,
src_info: FrameInfo,
wsub: usize,
hsub: usize,
) -> Result<VideoFrame> {
if src.planes.len() < 3 {
return Err(Error::invalid(
"pixfmt: planar YUV source needs 3 planes (Y, U, V)",
));
}
let w = src_info.width as usize;
let h = src_info.height as usize;
let (cw, ch) = chroma_dims(w, h, wsub, hsub);
let yp = gather_tight(&src.planes[0].data, src.planes[0].stride, w, h)?;
let up = gather_tight(&src.planes[1].data, src.planes[1].stride, cw, ch)?;
let vp = gather_tight(&src.planes[2].data, src.planes[2].stride, cw, ch)?;
let ap = vec![0xFFu8; w * h];
Ok(make_frame(
src,
vec![
VideoPlane {
stride: w,
data: yp,
},
VideoPlane {
stride: cw,
data: up,
},
VideoPlane {
stride: cw,
data: vp,
},
VideoPlane {
stride: w,
data: ap,
},
],
))
}
fn do_yuva_to_yuv(
src: &VideoFrame,
src_info: FrameInfo,
wsub: usize,
hsub: usize,
) -> Result<VideoFrame> {
if src.planes.len() < 4 {
return Err(Error::invalid(
"pixfmt: Yuva source needs 4 planes (Y, U, V, A)",
));
}
let w = src_info.width as usize;
let h = src_info.height as usize;
let (cw, ch) = chroma_dims(w, h, wsub, hsub);
let yp = gather_tight(&src.planes[0].data, src.planes[0].stride, w, h)?;
let up = gather_tight(&src.planes[1].data, src.planes[1].stride, cw, ch)?;
let vp = gather_tight(&src.planes[2].data, src.planes[2].stride, cw, ch)?;
Ok(make_frame(
src,
vec![
VideoPlane {
stride: w,
data: yp,
},
VideoPlane {
stride: cw,
data: up,
},
VideoPlane {
stride: cw,
data: vp,
},
],
))
}
fn do_yuva_to_rgb(
src: &VideoFrame,
src_info: FrameInfo,
matrix: YuvMatrix,
wsub: usize,
hsub: usize,
alpha: bool,
workers: usize,
) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let (yp, up, vp, ap) = planar8_source(src, w, h, wsub, hsub, true)?;
let bpp = if alpha { 4 } else { 3 };
let out = planar8::decode(
&Planar8 {
y: &yp,
u: &up,
v: &vp,
a: ap.as_deref(),
w,
h,
wsub,
hsub,
},
bpp,
matrix,
workers,
);
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * bpp,
data: out,
}],
))
}
fn do_rgb_to_yuva(
src: &VideoFrame,
src_info: FrameInfo,
matrix: YuvMatrix,
wsub: usize,
hsub: usize,
alpha_in: bool,
workers: usize,
) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let packed = packed8_source(src, w, h, if alpha_in { 4 } else { 3 })?;
let enc = planar8::encode(&packed, wsub, hsub, matrix, true, workers);
let ap = enc.a.unwrap_or_else(|| vec![0xFFu8; w * h]);
Ok(make_frame(
src,
vec![
VideoPlane {
stride: w,
data: enc.y,
},
VideoPlane {
stride: enc.cw,
data: enc.u,
},
VideoPlane {
stride: enc.cw,
data: enc.v,
},
VideoPlane {
stride: w,
data: ap,
},
],
))
}
#[inline]
fn rd16le(buf: &[u8], off: usize) -> u16 {
(buf[off] as u16) | ((buf[off + 1] as u16) << 8)
}
#[inline]
fn wr16le(buf: &mut [u8], off: usize, v: u16) {
buf[off] = (v & 0xFF) as u8;
buf[off + 1] = (v >> 8) as u8;
}
fn do_gbr_to_packed_deep(
src: &VideoFrame,
src_info: FrameInfo,
bits: u8,
alpha_in: bool,
alpha_out: bool,
workers: usize,
) -> Result<VideoFrame> {
let need = if alpha_in { 4 } else { 3 };
if src.planes.len() < need {
return Err(Error::invalid(
"pixfmt: GBR(A) source needs G, B, R(, A) planes",
));
}
let w = src_info.width as usize;
let h = src_info.height as usize;
let bits = bits as u32;
let shift = 16 - bits;
let mask = ((1u32 << bits) - 1) as u16;
let widen = |v: u16| -> u16 {
let v = v & mask;
if shift == 0 {
v
} else {
(v << shift) | (v >> (bits - shift))
}
};
let g = tight_plane(&src.planes[0], w * 2, h)?;
let b = tight_plane(&src.planes[1], w * 2, h)?;
let r = tight_plane(&src.planes[2], w * 2, h)?;
let a = if alpha_in && alpha_out {
Some(tight_plane(&src.planes[3], w * 2, h)?)
} else {
None
};
let comps = if alpha_out { 4 } else { 3 };
let out = planar8::map_packed_banded(&r, w * 2, w * comps * 2, h, workers, |rb, d| {
let n = d.len() / (comps * 2);
let off = rb.as_ptr() as usize - r.as_ptr() as usize;
let gb = &g[off..off + n * 2];
let bb = &b[off..off + n * 2];
let ab = a.as_deref().map(|a| &a[off..off + n * 2]);
for (i, px) in d.chunks_exact_mut(comps * 2).enumerate() {
wr16le(px, 0, widen(rd16le(rb, i * 2)));
wr16le(px, 2, widen(rd16le(gb, i * 2)));
wr16le(px, 4, widen(rd16le(bb, i * 2)));
if alpha_out {
let av = match ab {
Some(a) => widen(rd16le(a, i * 2)),
None => 0xFFFF,
};
wr16le(px, 6, av);
}
}
});
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * comps * 2,
data: out,
}],
))
}
fn do_gbr_to_packed8(
src: &VideoFrame,
src_info: FrameInfo,
bits: u32,
alpha: bool,
) -> Result<VideoFrame> {
let need = if alpha { 4 } else { 3 };
if src.planes.len() < need {
return Err(Error::invalid(
"pixfmt: GBR(A) source needs G, B, R(, A) planes",
));
}
let w = src_info.width as usize;
let h = src_info.height as usize;
let shift = bits - 8;
let mask: u16 = ((1u32 << bits) - 1) as u16;
let g = gather_tight(&src.planes[0].data, src.planes[0].stride, w * 2, h)?;
let b = gather_tight(&src.planes[1].data, src.planes[1].stride, w * 2, h)?;
let r = gather_tight(&src.planes[2].data, src.planes[2].stride, w * 2, h)?;
let a = if alpha {
Some(gather_tight(
&src.planes[3].data,
src.planes[3].stride,
w * 2,
h,
)?)
} else {
None
};
let comps = if alpha { 4 } else { 3 };
let mut out = vec![0u8; w * h * comps];
for i in 0..w * h {
let base = i * comps;
out[base] = ((rd16le(&r, i * 2) & mask) >> shift) as u8;
out[base + 1] = ((rd16le(&g, i * 2) & mask) >> shift) as u8;
out[base + 2] = ((rd16le(&b, i * 2) & mask) >> shift) as u8;
if let Some(a) = &a {
out[base + 3] = ((rd16le(a, i * 2) & mask) >> shift) as u8;
}
}
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * comps,
data: out,
}],
))
}
fn do_packed8_to_gbr(
src: &VideoFrame,
src_info: FrameInfo,
bits: u32,
alpha: bool,
) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let shift = bits - 8;
let comps = if alpha { 4 } else { 3 };
let in_plane = &src.planes[0];
let packed = gather_tight(&in_plane.data, in_plane.stride, w * comps, h)?;
let widen = |v: u8| -> u16 {
let v = v as u32;
(((v << shift) | (v >> (8 - shift))) & ((1u32 << bits) - 1)) as u16
};
let mut g = vec![0u8; w * h * 2];
let mut b = vec![0u8; w * h * 2];
let mut r = vec![0u8; w * h * 2];
let mut a = if alpha {
vec![0u8; w * h * 2]
} else {
Vec::new()
};
for i in 0..w * h {
let base = i * comps;
wr16le(&mut r, i * 2, widen(packed[base]));
wr16le(&mut g, i * 2, widen(packed[base + 1]));
wr16le(&mut b, i * 2, widen(packed[base + 2]));
if alpha {
wr16le(&mut a, i * 2, widen(packed[base + 3]));
}
}
let mut planes = vec![
VideoPlane {
stride: w * 2,
data: g,
},
VideoPlane {
stride: w * 2,
data: b,
},
VideoPlane {
stride: w * 2,
data: r,
},
];
if alpha {
planes.push(VideoPlane {
stride: w * 2,
data: a,
});
}
Ok(make_frame(src, planes))
}
fn do_gbr8_to_packed8(
src: &VideoFrame,
src_info: FrameInfo,
alpha_in: bool,
alpha_out: bool,
) -> Result<VideoFrame> {
let need = if alpha_in { 4 } else { 3 };
if src.planes.len() < need {
return Err(Error::invalid(
"pixfmt: GBR(A) source needs G, B, R(, A) planes",
));
}
let w = src_info.width as usize;
let h = src_info.height as usize;
let g = gather_tight(&src.planes[0].data, src.planes[0].stride, w, h)?;
let b = gather_tight(&src.planes[1].data, src.planes[1].stride, w, h)?;
let r = gather_tight(&src.planes[2].data, src.planes[2].stride, w, h)?;
let a = if alpha_in && alpha_out {
Some(gather_tight(
&src.planes[3].data,
src.planes[3].stride,
w,
h,
)?)
} else {
None
};
let comps = if alpha_out { 4 } else { 3 };
let mut out = vec![0u8; w * h * comps];
for i in 0..w * h {
let base = i * comps;
out[base] = r[i];
out[base + 1] = g[i];
out[base + 2] = b[i];
if alpha_out {
out[base + 3] = a.as_ref().map_or(255, |a| a[i]);
}
}
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * comps,
data: out,
}],
))
}
fn do_packed8_to_gbr8(
src: &VideoFrame,
src_info: FrameInfo,
alpha_in: bool,
alpha_out: bool,
) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let comps = if alpha_in { 4 } else { 3 };
let in_plane = &src.planes[0];
let packed = gather_tight(&in_plane.data, in_plane.stride, w * comps, h)?;
let mut g = vec![0u8; w * h];
let mut b = vec![0u8; w * h];
let mut r = vec![0u8; w * h];
let mut a = if alpha_out {
opaque_plane(w * h, 8)
} else {
Vec::new()
};
for i in 0..w * h {
let base = i * comps;
r[i] = packed[base];
g[i] = packed[base + 1];
b[i] = packed[base + 2];
if alpha_in && alpha_out {
a[i] = packed[base + 3];
}
}
let mut planes = vec![
VideoPlane { stride: w, data: g },
VideoPlane { stride: w, data: b },
VideoPlane { stride: w, data: r },
];
if alpha_out {
planes.push(VideoPlane { stride: w, data: a });
}
Ok(make_frame(src, planes))
}
fn do_gbr8_alpha(src: &VideoFrame, src_info: FrameInfo, add: bool) -> Result<VideoFrame> {
let need = if add { 3 } else { 4 };
if src.planes.len() < need {
return Err(Error::invalid(
"pixfmt: GBR(A) source needs G, B, R(, A) planes",
));
}
let w = src_info.width as usize;
let h = src_info.height as usize;
let mut planes: Vec<VideoPlane> = src.planes[..3]
.iter()
.map(|p| {
Ok(VideoPlane {
stride: w,
data: gather_tight(&p.data, p.stride, w, h)?,
})
})
.collect::<Result<_>>()?;
if add {
planes.push(VideoPlane {
stride: w,
data: opaque_plane(w * h, 8),
});
}
Ok(make_frame(src, planes))
}
fn do_gbr8_to_packed_deep(
src: &VideoFrame,
src_info: FrameInfo,
alpha_in: bool,
alpha_out: bool,
) -> Result<VideoFrame> {
let need = if alpha_in { 4 } else { 3 };
if src.planes.len() < need {
return Err(Error::invalid(
"pixfmt: GBR(A) source needs G, B, R(, A) planes",
));
}
let w = src_info.width as usize;
let h = src_info.height as usize;
let g = gather_tight(&src.planes[0].data, src.planes[0].stride, w, h)?;
let b = gather_tight(&src.planes[1].data, src.planes[1].stride, w, h)?;
let r = gather_tight(&src.planes[2].data, src.planes[2].stride, w, h)?;
let a = if alpha_in && alpha_out {
Some(gather_tight(
&src.planes[3].data,
src.planes[3].stride,
w,
h,
)?)
} else {
None
};
let comps = if alpha_out { 4 } else { 3 };
let mut out = vec![0u8; w * h * comps * 2];
for i in 0..w * h {
let base = i * comps * 2;
wr16le(&mut out, base, r[i] as u16 * 257);
wr16le(&mut out, base + 2, g[i] as u16 * 257);
wr16le(&mut out, base + 4, b[i] as u16 * 257);
if alpha_out {
let av = a.as_ref().map_or(0xFFFF, |a| a[i] as u16 * 257);
wr16le(&mut out, base + 6, av);
}
}
Ok(make_frame(
src,
vec![VideoPlane {
stride: w * comps * 2,
data: out,
}],
))
}
fn do_packed_deep_to_gbr8(
src: &VideoFrame,
src_info: FrameInfo,
alpha_in: bool,
alpha_out: bool,
) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let comps = if alpha_in { 4 } else { 3 };
let in_plane = &src.planes[0];
let packed = gather_tight(&in_plane.data, in_plane.stride, w * comps * 2, h)?;
let mut g = vec![0u8; w * h];
let mut b = vec![0u8; w * h];
let mut r = vec![0u8; w * h];
let mut a = if alpha_out {
opaque_plane(w * h, 8)
} else {
Vec::new()
};
for i in 0..w * h {
let base = i * comps * 2;
r[i] = (rd16le(&packed, base) >> 8) as u8;
g[i] = (rd16le(&packed, base + 2) >> 8) as u8;
b[i] = (rd16le(&packed, base + 4) >> 8) as u8;
if alpha_in && alpha_out {
a[i] = (rd16le(&packed, base + 6) >> 8) as u8;
}
}
let mut planes = vec![
VideoPlane { stride: w, data: g },
VideoPlane { stride: w, data: b },
VideoPlane { stride: w, data: r },
];
if alpha_out {
planes.push(VideoPlane { stride: w, data: a });
}
Ok(make_frame(src, planes))
}
fn do_gbr_to_gray(
src: &VideoFrame,
src_info: FrameInfo,
matrix: YuvMatrix,
bits: u32,
alpha_in: bool,
) -> Result<VideoFrame> {
let need = if alpha_in { 4 } else { 3 };
if src.planes.len() < need {
return Err(Error::invalid(
"pixfmt: GBR(A) source needs G, B, R(, A) planes",
));
}
let w = src_info.width as usize;
let h = src_info.height as usize;
let sb = if bits > 8 { 2 } else { 1 };
let g = gather_tight(&src.planes[0].data, src.planes[0].stride, w * sb, h)?;
let b = gather_tight(&src.planes[1].data, src.planes[1].stride, w * sb, h)?;
let r = gather_tight(&src.planes[2].data, src.planes[2].stride, w * sb, h)?;
let mut rgb24 = vec![0u8; w * h * 3];
if bits > 8 {
let mask: u16 = (((1u32 << bits) - 1) & 0xFFFF) as u16;
let shift = bits - 8;
for i in 0..w * h {
rgb24[i * 3] = ((rd16le(&r, i * 2) & mask) >> shift) as u8;
rgb24[i * 3 + 1] = ((rd16le(&g, i * 2) & mask) >> shift) as u8;
rgb24[i * 3 + 2] = ((rd16le(&b, i * 2) & mask) >> shift) as u8;
}
} else {
for i in 0..w * h {
rgb24[i * 3] = r[i];
rgb24[i * 3 + 1] = g[i];
rgb24[i * 3 + 2] = b[i];
}
}
let mut gray = vec![0u8; w * h];
yuv::rgb24_to_gray8(&rgb24, &mut gray, w * h, matrix);
Ok(make_frame(
src,
vec![VideoPlane {
stride: w,
data: gray,
}],
))
}
fn do_gray_to_gbr(
src: &VideoFrame,
src_info: FrameInfo,
bits: u32,
alpha_out: bool,
) -> Result<VideoFrame> {
let w = src_info.width as usize;
let h = src_info.height as usize;
let gray = gather_tight(&src.planes[0].data, src.planes[0].stride, w, h)?;
let plane = if bits > 8 {
let shift = bits - 8;
let mask = (1u32 << bits) - 1;
let mut out = vec![0u8; w * h * 2];
for (i, &v) in gray.iter().enumerate() {
let v = v as u32;
wr16le(
&mut out,
i * 2,
(((v << shift) | (v >> (8 - shift))) & mask) as u16,
);
}
out
} else {
gray
};
let sb = if bits > 8 { 2 } else { 1 };
let mut planes = vec![
VideoPlane {
stride: w * sb,
data: plane.clone(),
},
VideoPlane {
stride: w * sb,
data: plane.clone(),
},
VideoPlane {
stride: w * sb,
data: plane,
},
];
if alpha_out {
planes.push(VideoPlane {
stride: w * sb,
data: opaque_plane(w * h, bits),
});
}
Ok(make_frame(src, planes))
}
type GbrBand<'a> = (
usize,
&'a mut [u8],
&'a mut [u8],
&'a mut [u8],
Option<&'a mut [u8]>,
);
fn do_packed_deep_to_gbr(
src: &VideoFrame,
src_info: FrameInfo,
bits: u8,
alpha_in: bool,
alpha_out: bool,
workers: usize,
) -> Result<VideoFrame> {
if src.planes.is_empty() {
return Err(Error::invalid("pixfmt: packed RGB source needs a plane"));
}
let w = src_info.width as usize;
let h = src_info.height as usize;
let shift = 16 - bits as u32;
let comps = if alpha_in { 4 } else { 3 };
let packed = tight_plane(&src.planes[0], w * comps * 2, h)?;
let mut g = vec![0u8; w * h * 2];
let mut b = vec![0u8; w * h * 2];
let mut r = vec![0u8; w * h * 2];
let mut a = alpha_out.then(|| vec![0u8; w * h * 2]);
let opaque = ((1u32 << bits) - 1) as u16;
if w > 0 && h > 0 {
let bands = planar8::row_bands(h, 1, workers);
let mut jobs: Vec<GbrBand<'_>> = Vec::with_capacity(bands.len());
let (mut gr, mut br, mut rr): (&mut [u8], &mut [u8], &mut [u8]) = (&mut g, &mut b, &mut r);
let mut ar: Option<&mut [u8]> = a.as_deref_mut();
for &(r0, r1) in &bands {
let n = (r1 - r0) * w * 2;
let (gb, gt) = gr.split_at_mut(n);
gr = gt;
let (bb, bt) = br.split_at_mut(n);
br = bt;
let (rb, rt) = rr.split_at_mut(n);
rr = rt;
let ab = ar.take().map(|rest| {
let (band, tail) = rest.split_at_mut(n);
ar = Some(tail);
band
});
jobs.push((r0, gb, bb, rb, ab));
}
let row_bytes = w * comps * 2;
planar8::run_bands(jobs, |(r0, gb, bb, rb, ab): GbrBand<'_>| {
let n = gb.len() / 2;
let band = &packed[r0 * row_bytes..r0 * row_bytes + n * comps * 2];
let mut ab = ab;
for (i, px) in band.chunks_exact(comps * 2).enumerate() {
wr16le(rb, i * 2, rd16le(px, 0) >> shift);
wr16le(gb, i * 2, rd16le(px, 2) >> shift);
wr16le(bb, i * 2, rd16le(px, 4) >> shift);
if let Some(a) = ab.as_deref_mut() {
let av = if alpha_in {
rd16le(px, 6) >> shift
} else {
opaque
};
wr16le(a, i * 2, av);
}
}
});
}
let mut planes = vec![
VideoPlane {
stride: w * 2,
data: g,
},
VideoPlane {
stride: w * 2,
data: b,
},
VideoPlane {
stride: w * 2,
data: r,
},
];
if let Some(a) = a {
planes.push(VideoPlane {
stride: w * 2,
data: a,
});
}
Ok(make_frame(src, planes))
}
struct FloatPixels {
w: usize,
h: usize,
rgba: Vec<f32>,
}
fn gather_float(src: &VideoFrame, src_info: FrameInfo, s: FloatLayout) -> Result<FloatPixels> {
let w = src_info.width as usize;
let h = src_info.height as usize;
if src.planes.len() < s.plane_count() {
return Err(Error::invalid("pixfmt: float source is missing planes"));
}
let n = w * h;
let mut rgba = vec![1.0f32; n * 4];
if s.is_planar() {
let g = gather_tight(&src.planes[0].data, src.planes[0].stride, w * 4, h)?;
let b = gather_tight(&src.planes[1].data, src.planes[1].stride, w * 4, h)?;
let r = gather_tight(&src.planes[2].data, src.planes[2].stride, w * 4, h)?;
let a = if s.has_alpha() {
Some(gather_tight(
&src.planes[3].data,
src.planes[3].stride,
w * 4,
h,
)?)
} else {
None
};
for i in 0..n {
rgba[i * 4] = float::read_f32le(&r, i);
rgba[i * 4 + 1] = float::read_f32le(&g, i);
rgba[i * 4 + 2] = float::read_f32le(&b, i);
if let Some(a) = &a {
rgba[i * 4 + 3] = float::read_f32le(a, i);
}
}
} else {
let words = s.packed_words();
let packed = gather_tight(&src.planes[0].data, src.planes[0].stride, w * words * 4, h)?;
for i in 0..n {
match s {
FloatLayout::Gray => {
let v = float::read_f32le(&packed, i);
rgba[i * 4] = v;
rgba[i * 4 + 1] = v;
rgba[i * 4 + 2] = v;
}
_ => {
for c in 0..words {
rgba[i * 4 + c] = float::read_f32le(&packed, i * words + c);
}
}
}
}
}
Ok(FloatPixels { w, h, rgba })
}
fn gather_int(src: &VideoFrame, src_info: FrameInfo, s: IntLayout) -> Result<FloatPixels> {
let w = src_info.width as usize;
let h = src_info.height as usize;
if src.planes.len() < s.plane_count() {
return Err(Error::invalid("pixfmt: integer source is missing planes"));
}
let n = w * h;
let sb = s.sample_bytes();
let bits = s.bits;
let mask = ((1u32 << bits) - 1) as u16;
let read = |buf: &[u8], i: usize| -> f32 {
let code = if sb == 2 {
(u16::from_le_bytes([buf[i * 2], buf[i * 2 + 1]]) & mask) as u32
} else {
buf[i] as u32
};
float::unorm_to_f32(code, bits)
};
let mut rgba = vec![1.0f32; n * 4];
match s.shape {
IntShape::PlanarGbr => {
let g = gather_tight(&src.planes[0].data, src.planes[0].stride, w * sb, h)?;
let b = gather_tight(&src.planes[1].data, src.planes[1].stride, w * sb, h)?;
let r = gather_tight(&src.planes[2].data, src.planes[2].stride, w * sb, h)?;
let a = if s.alpha {
Some(gather_tight(
&src.planes[3].data,
src.planes[3].stride,
w * sb,
h,
)?)
} else {
None
};
for i in 0..n {
rgba[i * 4] = read(&r, i);
rgba[i * 4 + 1] = read(&g, i);
rgba[i * 4 + 2] = read(&b, i);
if let Some(a) = &a {
rgba[i * 4 + 3] = read(a, i);
}
}
}
IntShape::Gray => {
let packed = gather_tight(&src.planes[0].data, src.planes[0].stride, w * sb, h)?;
for i in 0..n {
let v = read(&packed, i);
rgba[i * 4] = v;
rgba[i * 4 + 1] = v;
rgba[i * 4 + 2] = v;
}
}
IntShape::PackedRgb => {
let comps = s.comps();
let packed =
gather_tight(&src.planes[0].data, src.planes[0].stride, w * comps * sb, h)?;
for i in 0..n {
for c in 0..comps {
rgba[i * 4 + c] = read(&packed, i * comps + c);
}
}
}
}
Ok(FloatPixels { w, h, rgba })
}
fn emit_float(src: &VideoFrame, px: &FloatPixels, d: FloatLayout, matrix: YuvMatrix) -> VideoFrame {
let (w, h) = (px.w, px.h);
let n = w * h;
let planes = if d.is_planar() {
let mut g = vec![0u8; n * 4];
let mut b = vec![0u8; n * 4];
let mut r = vec![0u8; n * 4];
let mut a = if d.has_alpha() {
Some(vec![0u8; n * 4])
} else {
None
};
for i in 0..n {
let p = &px.rgba[i * 4..i * 4 + 4];
float::write_f32le(&mut r, i, p[0]);
float::write_f32le(&mut g, i, p[1]);
float::write_f32le(&mut b, i, p[2]);
if let Some(a) = a.as_mut() {
float::write_f32le(a, i, p[3]);
}
}
let mut planes = vec![
VideoPlane {
stride: w * 4,
data: g,
},
VideoPlane {
stride: w * 4,
data: b,
},
VideoPlane {
stride: w * 4,
data: r,
},
];
if let Some(a) = a {
planes.push(VideoPlane {
stride: w * 4,
data: a,
});
}
planes
} else {
let words = d.packed_words();
let mut out = vec![0u8; n * words * 4];
for i in 0..n {
let p = &px.rgba[i * 4..i * 4 + 4];
match d {
FloatLayout::Gray => {
let y = float::luminance_linear(p[0], p[1], p[2], matrix);
float::write_f32le(&mut out, i, y);
}
_ => {
for (c, &v) in p.iter().enumerate().take(words) {
float::write_f32le(&mut out, i * words + c, v);
}
}
}
}
vec![VideoPlane {
stride: w * words * 4,
data: out,
}]
};
make_frame(src, planes)
}
fn emit_int(src: &VideoFrame, px: &FloatPixels, d: IntLayout, matrix: YuvMatrix) -> VideoFrame {
let (w, h) = (px.w, px.h);
let n = w * h;
let sb = d.sample_bytes();
let bits = d.bits;
let put = |buf: &mut [u8], i: usize, v: f32| {
let code = float::f32_to_unorm(v, bits);
if sb == 2 {
buf[i * 2..i * 2 + 2].copy_from_slice(&(code as u16).to_le_bytes());
} else {
buf[i] = code as u8;
}
};
let planes = match d.shape {
IntShape::PlanarGbr => {
let mut g = vec![0u8; n * sb];
let mut b = vec![0u8; n * sb];
let mut r = vec![0u8; n * sb];
let mut a = if d.alpha {
Some(vec![0u8; n * sb])
} else {
None
};
for i in 0..n {
let p = &px.rgba[i * 4..i * 4 + 4];
put(&mut r, i, p[0]);
put(&mut g, i, p[1]);
put(&mut b, i, p[2]);
if let Some(a) = a.as_mut() {
put(a, i, p[3]);
}
}
let mut planes = vec![
VideoPlane {
stride: w * sb,
data: g,
},
VideoPlane {
stride: w * sb,
data: b,
},
VideoPlane {
stride: w * sb,
data: r,
},
];
if let Some(a) = a {
planes.push(VideoPlane {
stride: w * sb,
data: a,
});
}
planes
}
IntShape::Gray => {
let mut out = vec![0u8; n * sb];
for i in 0..n {
let p = &px.rgba[i * 4..i * 4 + 4];
put(
&mut out,
i,
float::luminance_linear(p[0], p[1], p[2], matrix),
);
}
vec![VideoPlane {
stride: w * sb,
data: out,
}]
}
IntShape::PackedRgb => {
let comps = d.comps();
let mut out = vec![0u8; n * comps * sb];
for i in 0..n {
let p = &px.rgba[i * 4..i * 4 + 4];
for (c, &v) in p.iter().enumerate().take(comps) {
put(&mut out, i * comps + c, v);
}
}
vec![VideoPlane {
stride: w * comps * sb,
data: out,
}]
}
};
make_frame(src, planes)
}
fn float_to_float(
src: &VideoFrame,
src_info: FrameInfo,
matrix: YuvMatrix,
s: FloatLayout,
d: FloatLayout,
) -> Result<VideoFrame> {
let px = gather_float(src, src_info, s)?;
Ok(emit_float(src, &px, d, matrix))
}
fn float_to_int(
src: &VideoFrame,
src_info: FrameInfo,
matrix: YuvMatrix,
s: FloatLayout,
d: IntLayout,
) -> Result<VideoFrame> {
let px = gather_float(src, src_info, s)?;
Ok(emit_int(src, &px, d, matrix))
}
fn int_to_float(
src: &VideoFrame,
src_info: FrameInfo,
matrix: YuvMatrix,
s: IntLayout,
d: FloatLayout,
) -> Result<VideoFrame> {
let px = gather_int(src, src_info, s)?;
Ok(emit_float(src, &px, d, matrix))
}
fn float_to_deep_yuv(
src: &VideoFrame,
src_info: FrameInfo,
matrix: YuvMatrix,
s: FloatLayout,
d: PlanarYuv,
) -> Result<VideoFrame> {
let px = gather_float(src, src_info, s)?;
let packed = emit_int(
src,
&px,
IntLayout {
shape: IntShape::PackedRgb,
bits: 16,
alpha: d.alpha,
},
matrix,
);
let mid = if d.alpha {
PixelFormat::Rgba64Le
} else {
PixelFormat::Rgb48Le
};
let mid_info = FrameInfo::new(mid, src_info.width, src_info.height);
do_rgb48_to_deep_yuv(&packed, mid_info, matrix, d.wsub, d.hsub, d.alpha, 1)
}
fn deep_yuv_to_float(
src: &VideoFrame,
src_info: FrameInfo,
matrix: YuvMatrix,
s: PlanarYuv,
d: FloatLayout,
) -> Result<VideoFrame> {
let packed = do_deep_yuv_to_rgb48(src, src_info, matrix, s.wsub, s.hsub, s.alpha, 1)?;
let mid = if s.alpha {
PixelFormat::Rgba64Le
} else {
PixelFormat::Rgb48Le
};
let mid_info = FrameInfo::new(mid, src_info.width, src_info.height);
let px = gather_int(
&packed,
mid_info,
IntLayout {
shape: IntShape::PackedRgb,
bits: 16,
alpha: s.alpha,
},
)?;
Ok(emit_float(src, &px, d, matrix.with_range(false)))
}