use crate::error::{Error, Result};
use crate::image::ColorRange;
use rgb::prelude::*;
use rgb::{Rgb, Rgba};
use whereat::at;
use zenpixels::{PixelBuffer, PixelDescriptor};
#[inline]
fn limited_to_full_8(y: u8) -> u8 {
let y = y as i32;
((y - 16).max(0) * 255 / 219).min(255) as u8
}
#[inline]
fn limited_to_full_16(y: u16, bit_depth: u8) -> u16 {
let max_val = (1u32 << bit_depth) - 1;
let y_min = 16u32 << (bit_depth - 8);
let y_range = 219u32 << (bit_depth - 8);
let y32 = y as u32;
((y32.saturating_sub(y_min)) * max_val / y_range).min(max_val) as u16
}
#[inline]
fn scale_to_u16(v: u16, bit_depth: u8) -> u16 {
let shift = 16 - bit_depth;
if shift == 0 {
return v;
}
(v << shift) | (v >> (bit_depth - shift))
}
pub fn downscale_to_8bit(image: PixelBuffer) -> PixelBuffer {
let desc = image.descriptor();
let w = image.width();
let h = image.height();
if desc.layout_compatible(PixelDescriptor::RGB16) {
let src = image.try_as_imgref::<Rgb<u16>>().unwrap();
let out: Vec<Rgb<u8>> = src
.pixels()
.map(|px| Rgb {
r: (px.r >> 8) as u8,
g: (px.g >> 8) as u8,
b: (px.b >> 8) as u8,
})
.collect();
PixelBuffer::from_pixels(out, w, h)
.expect("allocation should succeed for same dimensions")
.into()
} else if desc.layout_compatible(PixelDescriptor::RGBA16) {
let src = image.try_as_imgref::<Rgba<u16>>().unwrap();
let out: Vec<Rgba<u8>> = src
.pixels()
.map(|px| Rgba {
r: (px.r >> 8) as u8,
g: (px.g >> 8) as u8,
b: (px.b >> 8) as u8,
a: (px.a >> 8) as u8,
})
.collect();
PixelBuffer::from_pixels(out, w, h)
.expect("allocation should succeed for same dimensions")
.into()
} else {
image
}
}
pub fn scale_pixels_to_u16(image: &mut PixelBuffer, bit_depth: u8) {
if bit_depth >= 16 {
return;
}
let desc = image.descriptor();
if desc.layout_compatible(PixelDescriptor::RGB16) {
let mut img = image.try_as_imgref_mut::<Rgb<u16>>().unwrap();
for px in img.buf_mut().iter_mut() {
*px = Rgb {
r: scale_to_u16(px.r, bit_depth),
g: scale_to_u16(px.g, bit_depth),
b: scale_to_u16(px.b, bit_depth),
};
}
} else if desc.layout_compatible(PixelDescriptor::RGBA16) {
let mut img = image.try_as_imgref_mut::<Rgba<u16>>().unwrap();
for px in img.buf_mut().iter_mut() {
*px = Rgba {
r: scale_to_u16(px.r, bit_depth),
g: scale_to_u16(px.g, bit_depth),
b: scale_to_u16(px.b, bit_depth),
a: scale_to_u16(px.a, bit_depth),
};
}
}
}
#[cfg(feature = "encode")]
#[inline]
pub fn scale_from_u16(v: u16, bit_depth: u8) -> u16 {
let shift = 16 - bit_depth;
if shift == 0 {
return v;
}
v >> shift
}
pub fn add_alpha8<'a>(
buf: &mut PixelBuffer,
alpha_rows: impl Iterator<Item = &'a [u8]>,
width: usize,
height: usize,
alpha_range: ColorRange,
premultiplied: bool,
) -> Result<()> {
let mut img = buf.try_as_imgref_mut::<Rgba<u8>>().ok_or_else(|| {
at!(Error::Unsupported(
"cannot add 8-bit alpha to this image type",
))
})?;
if img.width() != width || img.height() != height {
return Err(at!(Error::Unsupported("alpha size mismatch")));
}
for (alpha_row, img_row) in alpha_rows.zip(img.rows_mut()) {
if alpha_row.len() < img_row.len() {
return Err(at!(Error::Unsupported("alpha width mismatch")));
}
for (&y, px) in alpha_row.iter().zip(img_row.iter_mut()) {
px.a = match alpha_range {
ColorRange::Full => y,
ColorRange::Limited => limited_to_full_8(y),
};
}
if premultiplied {
unpremultiply8(img_row);
}
}
Ok(())
}
pub fn add_alpha16<'a>(
buf: &mut PixelBuffer,
alpha_rows: impl Iterator<Item = &'a [u16]>,
width: usize,
height: usize,
alpha_range: ColorRange,
bit_depth: u8,
premultiplied: bool,
) -> Result<()> {
let mut img = buf.try_as_imgref_mut::<Rgba<u16>>().ok_or_else(|| {
at!(Error::Unsupported(
"cannot add 16-bit alpha to this image type",
))
})?;
if img.width() != width || img.height() != height {
return Err(at!(Error::Unsupported("alpha size mismatch")));
}
for (alpha_row, img_row) in alpha_rows.zip(img.rows_mut()) {
if alpha_row.len() < img_row.len() {
return Err(at!(Error::Unsupported("alpha width mismatch")));
}
for (&y, px) in alpha_row.iter().zip(img_row.iter_mut()) {
let a = match alpha_range {
ColorRange::Full => y,
ColorRange::Limited => limited_to_full_16(y, bit_depth),
};
px.a = scale_to_u16(a, bit_depth);
}
if premultiplied {
unpremultiply16(img_row);
}
}
Ok(())
}
#[inline(never)]
pub fn unpremultiply8(img_row: &mut [Rgba<u8>]) {
for px in img_row.iter_mut() {
if px.a != 255 && px.a != 0 {
*px.rgb_mut() = px
.rgb()
.map(|c| ((c as u16 * 255 + px.a as u16 / 2) / px.a as u16).min(255) as u8);
}
}
}
#[inline(never)]
pub fn unpremultiply16(img_row: &mut [Rgba<u16>]) {
for px in img_row.iter_mut() {
if px.a != 0xFFFF && px.a != 0 {
*px.rgb_mut() = px
.rgb()
.map(|c| (c as u32 * 0xFFFF / px.a as u32).min(0xFFFF) as u16);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn limited_to_full_8_no_overflow() {
assert_eq!(limited_to_full_8(16), 0);
assert_eq!(limited_to_full_8(235), 255);
assert_eq!(limited_to_full_8(145), 150);
assert_eq!(limited_to_full_8(200), 214);
assert_eq!(limited_to_full_8(0), 0);
assert_eq!(limited_to_full_8(15), 0);
assert_eq!(limited_to_full_8(255), 255);
}
#[test]
fn limited_to_full_8_all_values_in_range() {
for y in 0..=255u8 {
let result = limited_to_full_8(y);
let _ = result;
}
}
#[test]
fn limited_to_full_16_endpoints() {
assert_eq!(limited_to_full_16(64, 10), 0); assert_eq!(limited_to_full_16(940, 10), 1023); assert_eq!(limited_to_full_16(256, 12), 0); assert_eq!(limited_to_full_16(3760, 12), 4095); }
#[test]
fn scale_to_u16_endpoints() {
assert_eq!(scale_to_u16(0, 10), 0);
assert_eq!(scale_to_u16(1023, 10), 65535);
assert_eq!(scale_to_u16(0, 12), 0);
assert_eq!(scale_to_u16(4095, 12), 65535);
assert_eq!(scale_to_u16(12345, 16), 12345);
}
}