use crate::attr::Attributes;
use crate::blur::{liq_blur, liq_max3, liq_min3};
use crate::error::*;
use crate::pal::{f_pixel, PalF, PalIndexRemap, MAX_COLORS, MIN_OPAQUE_A, RGBA};
use crate::remap::DitherMapMode;
use crate::rows::{DynamicRows, PixelsSource};
use crate::seacow::RowBitmap;
use crate::seacow::SeaCow;
use crate::PushInCapacity;
use crate::LIQ_HIGH_MEMORY_LIMIT;
use rgb::ComponentMap;
use std::mem::MaybeUninit;
#[derive(Clone)]
pub struct Image<'pixels> {
pub(crate) px: DynamicRows<'pixels, 'pixels>,
pub(crate) importance_map: Option<Box<[u8]>>,
pub(crate) edges: Option<Box<[u8]>>,
pub(crate) dither_map: Option<Box<[u8]>>,
pub(crate) background: Option<Box<Image<'pixels>>>,
pub(crate) fixed_colors: Vec<RGBA>,
}
impl<'pixels> Image<'pixels> {
#[inline(always)]
pub fn new<VecRGBA>(attr: &Attributes, pixels: VecRGBA, width: usize, height: usize, gamma: f64) -> Result<Self, Error> where VecRGBA: Into<Box<[RGBA]>> {
Self::new_stride(attr, pixels, width, height, width, gamma)
}
#[inline(always)]
pub fn new_borrowed(attr: &Attributes, pixels: &'pixels [RGBA], width: usize, height: usize, gamma: f64) -> Result<Self, Error> {
Self::new_stride_borrowed(attr, pixels, width, height, width, gamma)
}
pub unsafe fn new_fn<F: 'pixels + Fn(&mut [MaybeUninit<RGBA>], usize) + Send + Sync>(attr: &Attributes, convert_row_fn: F, width: usize, height: usize, gamma: f64) -> Result<Self, Error> {
let width = width.try_into().map_err(|_| ValueOutOfRange)?;
let height = height.try_into().map_err(|_| ValueOutOfRange)?;
Image::new_internal(attr, PixelsSource::Callback(Box::new(convert_row_fn)), width, height, gamma)
}
pub(crate) fn free_histogram_inputs(&mut self) {
self.px.free_histogram_inputs();
}
pub(crate) fn new_internal(
attr: &Attributes,
pixels: PixelsSource<'pixels, 'pixels>,
width: u32,
height: u32,
gamma: f64,
) -> Result<Self, Error> {
if !Self::check_image_size(width, height) {
return Err(ValueOutOfRange);
}
if !(0. ..=1.).contains(&gamma) {
attr.verbose_print(" error: gamma must be >= 0 and <= 1 (try 1/gamma instead)");
return Err(ValueOutOfRange);
}
let img = Image {
px: DynamicRows::new(
width,
height,
pixels,
if gamma > 0. { gamma } else { 0.45455 },
),
importance_map: None,
edges: None,
dither_map: None,
background: None,
fixed_colors: Vec::new(),
};
let low_memory_hint = !attr.use_contrast_maps && attr.use_dither_map == DitherMapMode::None;
let limit = if low_memory_hint { LIQ_HIGH_MEMORY_LIMIT / 8 } else { LIQ_HIGH_MEMORY_LIMIT } / std::mem::size_of::<f_pixel>();
if (img.width()) * (img.height()) > limit {
attr.verbose_print(" conserving memory"); }
Ok(img)
}
fn check_image_size(width: u32, height: u32) -> bool {
if width == 0 || height == 0 {
return false;
}
if width.max(height) as usize > i32::MAX as usize ||
width as usize > isize::MAX as usize / std::mem::size_of::<f_pixel>() / height as usize {
return false;
}
true
}
pub(crate) fn update_dither_map(&mut self, remapped_image: &RowBitmap<'_, PalIndexRemap>, palette: &PalF, uses_background: bool) -> Result<(), Error> {
if self.edges.is_none() {
self.contrast_maps()?;
}
let mut edges = match self.edges.take() {
Some(e) => e,
None => return Ok(()),
};
let colors = palette.as_slice();
let width = self.width();
let mut prev_row: Option<&[_]> = None;
let mut rows = remapped_image.rows().zip(edges.chunks_exact_mut(width)).peekable();
while let Some((this_row, edges)) = rows.next() {
let mut lastpixel = this_row[0];
let mut lastcol = 0;
for (col, px) in this_row.iter().copied().enumerate().skip(1) {
if uses_background && (colors[px as usize]).a < MIN_OPAQUE_A {
continue;
}
if px != lastpixel || col == width - 1 {
let mut neighbor_count = 10 * (col - lastcol);
let mut i = lastcol;
while i < col {
if let Some(prev_row) = prev_row {
let pixelabove = prev_row[i];
if pixelabove == lastpixel { neighbor_count += 15; };
}
if let Some((next_row, _)) = rows.peek() {
let pixelbelow = next_row[i];
if pixelbelow == lastpixel { neighbor_count += 15; };
}
i += 1;
}
while lastcol <= col {
edges[lastcol] = (f32::from(u16::from(edges[lastcol]) + 128)
* (255. / (255 + 128) as f32)
* (1. - 20. / (20 + neighbor_count) as f32))
as u8;
lastcol += 1;
}
lastpixel = px;
}
}
prev_row = Some(this_row);
}
self.dither_map = Some(edges);
Ok(())
}
pub fn set_importance_map(&mut self, map: impl Into<Box<[u8]>>) -> Result<(), Error> {
let map = map.into();
if map.len() != self.width() * self.height() {
return Err(BufferTooSmall);
}
self.importance_map = Some(map);
Ok(())
}
pub fn set_background(&mut self, background: Self) -> Result<(), Error> {
if background.background.is_some() {
return Err(Unsupported);
}
if self.px.width != background.px.width || self.px.height != background.px.height {
return Err(BufferTooSmall);
}
self.background = Some(Box::new(background));
Ok(())
}
pub fn add_fixed_color(&mut self, color: RGBA) -> Result<(), Error> {
if self.fixed_colors.len() >= MAX_COLORS { return Err(Unsupported); }
self.fixed_colors.try_reserve(1)?;
self.fixed_colors.push_in_cap(color);
Ok(())
}
#[must_use]
#[inline(always)]
pub fn width(&self) -> usize {
self.px.width as _
}
#[must_use]
#[inline(always)]
pub fn height(&self) -> usize {
self.px.height as _
}
#[inline(always)]
pub(crate) fn gamma(&self) -> Option<f64> {
if self.px.gamma > 0. { Some(self.px.gamma) } else { None }
}
pub(crate) fn contrast_maps(&mut self) -> Result<(), Error> {
let width = self.width();
let height = self.height();
if width < 4 || height < 4 || (3 * width * height) > LIQ_HIGH_MEMORY_LIMIT {
return Ok(()); }
let noise = if let Some(n) = self.importance_map.as_deref_mut() { n } else {
let vec = try_zero_vec(width * height)?;
self.importance_map.get_or_insert_with(move || vec.into_boxed_slice())
};
let edges = if let Some(e) = self.edges.as_mut() { e } else {
let vec = try_zero_vec(width * height)?;
self.edges.get_or_insert_with(move || vec.into_boxed_slice())
};
let mut rows_iter = self.px.all_rows_f()?.chunks_exact(width);
let mut next_row = rows_iter.next().ok_or(Error::InvalidPointer)?;
let mut curr_row = next_row;
let mut prev_row;
for (noise_row, edges_row) in noise[..width * height].chunks_exact_mut(width).zip(edges[..width * height].chunks_exact_mut(width)) {
prev_row = curr_row;
curr_row = next_row;
next_row = rows_iter.next().unwrap_or(next_row);
let mut prev;
let mut curr = curr_row[0].0;
let mut next = curr;
for i in 0..width {
prev = curr;
curr = next;
next = curr_row[(i + 1).min(width - 1)].0;
let horiz = (prev + next - curr * 2.).map(f32::abs); let prevl = prev_row[i].0;
let nextl = next_row[i].0;
let vert = (prevl + nextl - curr * 2.).map(f32::abs);
let horiz = horiz.a.max(horiz.r).max(horiz.g.max(horiz.b));
let vert = vert.a.max(vert.r).max(vert.g.max(vert.b));
let edge = horiz.max(vert);
let mut z = (horiz - vert).abs().mul_add(-0.5, edge);
z = 1. - z.max(horiz.min(vert));
z *= z;
z *= z;
noise_row[i] = z.mul_add(176., 80.) as u8;
edges_row[i] = ((1. - edge) * 256.) as u8;
}
}
let mut tmp = try_zero_vec(width * height)?;
liq_max3(noise, &mut tmp, width, height);
liq_max3(&tmp, noise, width, height);
liq_blur(noise, &mut tmp, width, height, 3);
liq_max3(noise, &mut tmp, width, height);
liq_min3(&tmp, noise, width, height);
liq_min3(noise, &mut tmp, width, height);
liq_min3(&tmp, noise, width, height);
liq_min3(edges, &mut tmp, width, height);
liq_max3(&tmp, edges, width, height);
for (edges, noise) in edges.iter_mut().zip(noise) {
*edges = (*noise).min(*edges);
}
Ok(())
}
#[inline(always)]
pub fn new_stride_borrowed(attr: &Attributes, pixels: &'pixels [RGBA], width: usize, height: usize, stride: usize, gamma: f64) -> Result<Self, Error> {
Self::new_stride_internal(attr, SeaCow::borrowed(pixels), width, height, stride, gamma)
}
#[inline]
pub fn new_stride<VecRGBA>(attr: &Attributes, pixels: VecRGBA, width: usize, height: usize, stride: usize, gamma: f64) -> Result<Image<'static>, Error> where VecRGBA: Into<Box<[RGBA]>> {
Self::new_stride_internal(attr, SeaCow::boxed(pixels.into()), width, height, stride, gamma)
}
fn new_stride_internal<'a>(attr: &Attributes, pixels: SeaCow<'a, RGBA>, width: usize, height: usize, stride: usize, gamma: f64) -> Result<Image<'a>, Error> {
let width = width.try_into().map_err(|_| ValueOutOfRange)?;
let height = height.try_into().map_err(|_| ValueOutOfRange)?;
let stride = stride.try_into().map_err(|_| ValueOutOfRange)?;
let pixels_len = pixels.as_slice().len();
let pixels_rows = match PixelsSource::for_pixels(pixels, width, height, stride) {
Ok(p) => p,
Err(e) => {
attr.verbose_print(format!("Buffer length is {} bytes, which is not enough for {}×{}×4 RGBA bytes", pixels_len*4, stride, height));
return Err(e)
},
};
Image::new_internal(attr, pixels_rows, width, height, gamma)
}
}
fn try_zero_vec(len: usize) -> Result<Vec<u8>, Error> {
let mut vec = Vec::new();
vec.try_reserve_exact(len)?;
vec.resize(len, 0);
Ok(vec)
}