#[cfg(not(feature = "std"))]
use alloc::{vec, vec::Vec};
use zenpixels::{AlphaMode, ChannelType, PixelDescriptor, TransferFunction};
pub trait Element: Copy + Default + Send + Sync + 'static {
fn alloc_output(len: usize) -> Vec<Self>;
}
impl Element for u8 {
#[inline]
fn alloc_output(len: usize) -> Vec<Self> {
crate::proven::alloc_output::<u8>(len)
}
}
impl Element for u16 {
#[inline]
fn alloc_output(len: usize) -> Vec<Self> {
vec![0u16; len]
}
}
impl Element for f32 {
#[inline]
fn alloc_output(len: usize) -> Vec<Self> {
vec![0.0f32; len]
}
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq)]
pub enum LobeRatio {
#[default]
Natural,
Exact(f32),
SharpenPercent(f32),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SourceRegion {
pub x: u32,
pub y: u32,
pub width: u32,
pub height: u32,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Padding {
pub top: u32,
pub bottom: u32,
pub left: u32,
pub right: u32,
pub color: [f32; 4],
}
impl Default for Padding {
fn default() -> Self {
Self {
top: 0,
bottom: 0,
left: 0,
right: 0,
color: [0.0; 4],
}
}
}
impl Padding {
pub fn uniform(px: u32) -> Self {
Self {
top: px,
bottom: px,
left: px,
right: px,
color: [0.0; 4],
}
}
pub fn with_color(mut self, color: [f32; 4]) -> Self {
self.color = color;
self
}
pub fn is_empty(&self) -> bool {
self.top == 0 && self.bottom == 0 && self.left == 0 && self.right == 0
}
}
#[non_exhaustive]
#[derive(Clone, Debug)]
pub struct ResizeConfig {
pub filter: crate::filter::Filter,
pub up_filter: Option<crate::filter::Filter>,
pub in_width: u32,
pub in_height: u32,
pub out_width: u32,
pub out_height: u32,
pub input: PixelDescriptor,
pub output: PixelDescriptor,
pub post_sharpen: f32,
pub post_blur_sigma: f32,
pub kernel_width_scale: Option<f64>,
pub lobe_ratio: LobeRatio,
pub linear: bool,
pub in_stride: usize,
pub out_stride: usize,
pub source_region: Option<SourceRegion>,
pub padding: Option<Padding>,
}
impl ResizeConfig {
pub fn builder(
in_width: u32,
in_height: u32,
out_width: u32,
out_height: u32,
) -> ResizeConfigBuilder {
ResizeConfigBuilder::new(in_width, in_height, out_width, out_height)
}
pub fn validate(&self) -> Result<(), &'static str> {
use zenpixels::{ChannelLayout, SignalRange};
if self.in_width == 0 || self.in_height == 0 {
return Err("input dimensions must be positive");
}
if self.out_width == 0 || self.out_height == 0 {
return Err("output dimensions must be positive");
}
if self.input.channels() != self.output.channels() {
return Err("input and output must have the same number of channels");
}
if self.input.channel_type() == ChannelType::F16
|| self.output.channel_type() == ChannelType::F16
{
return Err("F16 channel type is not supported; convert to F32 first");
}
let reject_layout = |l: ChannelLayout| {
matches!(
l,
ChannelLayout::GrayAlpha | ChannelLayout::Oklab | ChannelLayout::OklabA
)
};
if reject_layout(self.input.layout()) || reject_layout(self.output.layout()) {
return Err("unsupported channel layout; convert to a supported layout first");
}
if self.input.transfer == TransferFunction::Unknown
|| self.output.transfer == TransferFunction::Unknown
{
return Err("unknown transfer function; specify Srgb, Linear, Bt709, Pq, or Hlg");
}
if self.input.signal_range == SignalRange::Narrow
|| self.output.signal_range == SignalRange::Narrow
{
return Err("narrow signal range is not supported; expand to full range first");
}
if let Some(ref r) = self.source_region {
if r.width == 0 || r.height == 0 {
return Err("source region dimensions must be positive");
}
if r.x
.checked_add(r.width)
.is_none_or(|end| end > self.in_width)
{
return Err("source region exceeds input width");
}
if r.y
.checked_add(r.height)
.is_none_or(|end| end > self.in_height)
{
return Err("source region exceeds input height");
}
}
if let Some(ref p) = self.padding {
if p.left
.checked_add(self.out_width)
.and_then(|v| v.checked_add(p.right))
.is_none()
{
return Err("padded output width overflows u32");
}
if p.top
.checked_add(self.out_height)
.and_then(|v| v.checked_add(p.bottom))
.is_none()
{
return Err("padded output height overflows u32");
}
}
Ok(())
}
pub fn effective_in_stride(&self) -> usize {
if self.in_stride == 0 {
self.in_width as usize * self.input.channels()
} else {
self.in_stride
}
}
pub fn effective_out_stride(&self) -> usize {
if self.out_stride == 0 {
self.out_width as usize * self.output.channels()
} else {
self.out_stride
}
}
pub fn input_row_len(&self) -> usize {
self.in_width as usize * self.input.channels()
}
pub fn output_row_len(&self) -> usize {
self.out_width as usize * self.output.channels()
}
pub fn resize_in_width(&self) -> u32 {
self.source_region
.as_ref()
.map_or(self.in_width, |r| r.width)
}
pub fn resize_in_height(&self) -> u32 {
self.source_region
.as_ref()
.map_or(self.in_height, |r| r.height)
}
pub fn total_output_width(&self) -> u32 {
let base = self.out_width;
self.padding
.as_ref()
.map_or(base, |p| p.left + base + p.right)
}
pub fn total_output_height(&self) -> u32 {
let base = self.out_height;
self.padding
.as_ref()
.map_or(base, |p| p.top + base + p.bottom)
}
pub fn total_output_row_len(&self) -> usize {
self.total_output_width() as usize * self.output.channels()
}
pub fn needs_linearization(&self) -> bool {
self.linear
&& self.input.channel_type().is_integer()
&& self.input.transfer != TransferFunction::Linear
&& self.input.alpha != Some(AlphaMode::Premultiplied)
}
pub fn effective_input_transfer(&self) -> TransferFunction {
if self.input.alpha == Some(AlphaMode::Premultiplied) {
return TransferFunction::Linear;
}
if self.input.transfer == TransferFunction::Linear {
return TransferFunction::Linear;
}
if !self.linear
&& self.input.channel_type() == ChannelType::U8
&& self.input.transfer == TransferFunction::Srgb
{
return TransferFunction::Linear;
}
self.input.transfer
}
pub fn effective_output_transfer(&self) -> TransferFunction {
if self.output.alpha == Some(AlphaMode::Premultiplied) {
return TransferFunction::Linear;
}
if self.output.transfer == TransferFunction::Linear {
return TransferFunction::Linear;
}
if !self.linear
&& self.output.channel_type() == ChannelType::U8
&& self.output.transfer == TransferFunction::Srgb
{
return TransferFunction::Linear;
}
self.output.transfer
}
#[inline]
pub fn channels(&self) -> usize {
self.input.channels()
}
#[inline]
pub fn needs_premultiply(&self) -> bool {
self.input.alpha == Some(AlphaMode::Straight)
}
#[inline]
pub fn input_channel_type(&self) -> ChannelType {
self.input.channel_type()
}
#[inline]
pub fn output_channel_type(&self) -> ChannelType {
self.output.channel_type()
}
}
pub struct ResizeConfigBuilder {
in_width: u32,
in_height: u32,
out_width: u32,
out_height: u32,
filter: crate::filter::Filter,
up_filter: Option<crate::filter::Filter>,
input: PixelDescriptor,
output: Option<PixelDescriptor>,
post_sharpen: f32,
post_blur_sigma: f32,
kernel_width_scale: Option<f64>,
lobe_ratio: LobeRatio,
linear: bool,
in_stride: usize,
out_stride: usize,
source_region: Option<SourceRegion>,
padding: Option<Padding>,
}
impl ResizeConfigBuilder {
fn new(in_width: u32, in_height: u32, out_width: u32, out_height: u32) -> Self {
Self {
in_width,
in_height,
out_width,
out_height,
filter: crate::filter::Filter::default(),
up_filter: None,
input: PixelDescriptor::RGBA8_SRGB,
output: None,
post_sharpen: 0.0,
post_blur_sigma: 0.0,
kernel_width_scale: None,
lobe_ratio: LobeRatio::Natural,
linear: true,
in_stride: 0,
out_stride: 0,
source_region: None,
padding: None,
}
}
pub fn filter(mut self, filter: crate::filter::Filter) -> Self {
self.filter = filter;
self
}
pub fn up_filter(mut self, filter: crate::filter::Filter) -> Self {
self.up_filter = Some(filter);
self
}
pub fn format(mut self, desc: PixelDescriptor) -> Self {
self.input = desc;
self.output = Some(desc);
self
}
pub fn input(mut self, desc: PixelDescriptor) -> Self {
self.input = desc;
self
}
pub fn output(mut self, desc: PixelDescriptor) -> Self {
self.output = Some(desc);
self
}
pub fn post_sharpen(mut self, amount: f32) -> Self {
self.post_sharpen = amount;
self
}
#[doc(hidden)]
pub fn sharpen(mut self, amount: f32) -> Self {
self.post_sharpen = amount;
self
}
pub fn post_blur(mut self, sigma: f32) -> Self {
self.post_blur_sigma = sigma;
self
}
pub fn kernel_width_scale(mut self, factor: f64) -> Self {
self.kernel_width_scale = Some(factor);
self
}
pub fn lobe_ratio(mut self, ratio: LobeRatio) -> Self {
self.lobe_ratio = ratio;
self
}
pub fn resize_sharpen(mut self, pct: f32) -> Self {
self.lobe_ratio = LobeRatio::SharpenPercent(pct);
self
}
#[doc(hidden)]
pub fn sharpen_percent(self, pct: f32) -> Self {
self.resize_sharpen(pct)
}
pub fn linear(mut self) -> Self {
self.linear = true;
self
}
pub fn srgb(mut self) -> Self {
self.linear = false;
self
}
pub fn in_stride(mut self, stride: usize) -> Self {
self.in_stride = stride;
self
}
pub fn out_stride(mut self, stride: usize) -> Self {
self.out_stride = stride;
self
}
pub fn crop(mut self, x: u32, y: u32, width: u32, height: u32) -> Self {
self.source_region = Some(SourceRegion {
x,
y,
width,
height,
});
self
}
pub fn source_region(mut self, region: SourceRegion) -> Self {
self.source_region = Some(region);
self
}
pub fn padding(mut self, top: u32, right: u32, bottom: u32, left: u32) -> Self {
let color = self.padding.as_ref().map_or([0.0; 4], |p| p.color);
self.padding = Some(Padding {
top,
bottom,
left,
right,
color,
});
self
}
pub fn padding_uniform(self, px: u32) -> Self {
self.padding(px, px, px, px)
}
pub fn padding_color(mut self, color: [f32; 4]) -> Self {
if let Some(ref mut p) = self.padding {
p.color = color;
} else {
self.padding = Some(Padding {
top: 0,
bottom: 0,
left: 0,
right: 0,
color,
});
}
self
}
pub fn with_padding(mut self, padding: Padding) -> Self {
self.padding = Some(padding);
self
}
pub fn build(self) -> ResizeConfig {
let output = self.output.unwrap_or(self.input);
ResizeConfig {
filter: self.filter,
up_filter: self.up_filter,
in_width: self.in_width,
in_height: self.in_height,
out_width: self.out_width,
out_height: self.out_height,
input: self.input,
output,
post_sharpen: self.post_sharpen,
post_blur_sigma: self.post_blur_sigma,
kernel_width_scale: self.kernel_width_scale,
lobe_ratio: self.lobe_ratio,
linear: self.linear,
in_stride: self.in_stride,
out_stride: self.out_stride,
source_region: self.source_region,
padding: self.padding,
}
}
}