use crate::error::Error;
use crate::hist::Histogram;
use crate::image::Image;
use crate::pal::{PalLen, MAX_COLORS, RGBA};
use crate::quant::{mse_to_quality, quality_to_mse, QuantizationResult};
use crate::remap::DitherMapMode;
use std::sync::Arc;
#[derive(Clone)]
pub struct Attributes {
pub(crate) max_colors: PalLen,
target_mse: f64,
max_mse: Option<f64>,
kmeans_iteration_limit: f64,
kmeans_iterations: u16,
feedback_loop_trials: u16,
pub(crate) max_histogram_entries: u32,
min_posterization_output: u8,
min_posterization_input: u8,
pub(crate) last_index_transparent: bool,
pub(crate) use_contrast_maps: bool,
pub(crate) single_threaded_dithering: bool,
pub(crate) use_dither_map: DitherMapMode,
speed: u8,
pub(crate) progress_stage1: u8,
pub(crate) progress_stage2: u8,
pub(crate) progress_stage3: u8,
progress_callback: Option<Arc<dyn Fn(f32) -> ControlFlow + Send + Sync>>,
log_callback: Option<Arc<dyn Fn(&Attributes, &str) + Send + Sync>>,
log_flush_callback: Option<Arc<dyn Fn(&Attributes) + Send + Sync>>,
}
impl Attributes {
#[inline]
#[must_use]
pub fn new() -> Self {
let mut attr = Self {
target_mse: 0.,
max_mse: None,
max_colors: MAX_COLORS as PalLen,
last_index_transparent: false,
kmeans_iteration_limit: 0.,
max_histogram_entries: 0,
min_posterization_output: 0,
min_posterization_input: 0,
kmeans_iterations: 0,
feedback_loop_trials: 0,
use_contrast_maps: false,
use_dither_map: DitherMapMode::None,
single_threaded_dithering: false,
speed: 0,
progress_stage1: 0,
progress_stage2: 0,
progress_stage3: 0,
progress_callback: None,
log_callback: None,
log_flush_callback: None,
};
let _ = attr.set_speed(4);
attr
}
#[inline]
pub fn new_image<VecRGBA>(&self, pixels: VecRGBA, width: usize, height: usize, gamma: f64) -> Result<Image<'static>, Error> where VecRGBA: Into<Box<[RGBA]>> {
Image::new(self, pixels, width, height, gamma)
}
pub fn quantize(&self, image: &mut Image<'_>) -> Result<QuantizationResult, Error> {
let mut hist = Histogram::new(self);
hist.add_image(self, image)?;
hist.quantize_internal(self, false)
}
#[inline]
pub fn set_max_colors(&mut self, colors: u32) -> Result<(), Error> {
if !(2..=256).contains(&colors) {
return Err(Error::ValueOutOfRange);
}
self.max_colors = colors as PalLen;
Ok(())
}
pub fn set_quality(&mut self, minimum: u8, target: u8) -> Result<(), Error> {
if !(0..=100).contains(&target) || target < minimum {
return Err(Error::ValueOutOfRange);
}
if target < 30 {
self.verbose_print(" warning: quality set too low");
}
self.target_mse = quality_to_mse(target);
self.max_mse = Some(quality_to_mse(minimum));
Ok(())
}
#[inline]
pub fn set_speed(&mut self, value: i32) -> Result<(), Error> {
if !(1..=10).contains(&value) {
return Err(Error::ValueOutOfRange);
}
let mut iterations = (8 - value).max(0) as u16;
iterations += iterations * iterations / 2;
self.kmeans_iterations = iterations;
self.kmeans_iteration_limit = 1. / f64::from(1 << (23 - value));
self.feedback_loop_trials = (56 - 9 * value).max(0) as _;
self.max_histogram_entries = ((1 << 17) + (1 << 18) * (10 - value)) as _;
self.min_posterization_input = if value >= 8 { 1 } else { 0 };
self.use_dither_map = if value <= 6 { DitherMapMode::Enabled } else { DitherMapMode::None };
if self.use_dither_map != DitherMapMode::None && value < 3 {
self.use_dither_map = DitherMapMode::Always;
}
self.use_contrast_maps = (value <= 7) || self.use_dither_map != DitherMapMode::None;
self.single_threaded_dithering = value == 1;
self.speed = value as u8;
self.progress_stage1 = if self.use_contrast_maps { 20 } else { 8 };
if self.feedback_loop_trials < 2 {
self.progress_stage1 += 30;
}
self.progress_stage3 = (50 / (1 + value)) as u8;
self.progress_stage2 = 100 - self.progress_stage1 - self.progress_stage3;
Ok(())
}
#[inline]
pub fn set_min_posterization(&mut self, value: u8) -> Result<(), Error> {
if !(0..=4).contains(&value) {
return Err(Error::ValueOutOfRange);
}
self.min_posterization_output = value;
Ok(())
}
#[inline(always)]
#[must_use]
pub fn min_posterization(&self) -> u8 {
self.min_posterization_output
}
#[inline(always)]
#[must_use]
pub fn speed(&self) -> u32 {
self.speed.into()
}
#[inline(always)]
#[must_use]
pub fn max_colors(&self) -> u32 {
self.max_colors.into()
}
#[must_use]
pub fn quality(&self) -> (u8, u8) {
(
self.max_mse.map_or(0, mse_to_quality),
mse_to_quality(self.target_mse),
)
}
#[inline]
pub fn new_image_borrowed<'pixels>(&self, bitmap: &'pixels [RGBA], width: usize, height: usize, gamma: f64) -> Result<Image<'pixels>, Error> {
Image::new_borrowed(self, bitmap, width, height, gamma)
}
#[inline]
pub fn new_image_stride<VecRGBA>(&self, pixels: VecRGBA, width: usize, height: usize, stride: usize, gamma: f64) -> Result<Image<'static>, Error> where VecRGBA: Into<Box<[RGBA]>> {
Image::new_stride(self, pixels, width, height, stride, gamma)
}
#[doc(hidden)]
#[deprecated(note = "use new_image_stride")]
#[cold]
pub fn new_image_stride_copy(&self, bitmap: &[RGBA], width: usize, height: usize, stride: usize, gamma: f64) -> Result<Image<'static>, Error> {
self.new_image_stride(bitmap, width, height, stride, gamma)
}
#[inline]
pub fn set_log_callback<F: Fn(&Self, &str) + Send + Sync + 'static>(&mut self, callback: F) {
self.verbose_printf_flush();
self.log_callback = Some(Arc::new(callback));
}
#[inline]
pub fn set_log_flush_callback<F: Fn(&Self) + Send + Sync + 'static>(&mut self, callback: F) {
self.verbose_printf_flush();
self.log_flush_callback = Some(Arc::new(callback));
}
#[inline]
pub fn set_progress_callback<F: Fn(f32) -> ControlFlow + Send + Sync + 'static>(&mut self, callback: F) {
self.progress_callback = Some(Arc::new(callback));
}
#[inline(always)]
pub fn set_last_index_transparent(&mut self, is_last: bool) {
self.last_index_transparent = is_last;
}
#[inline]
#[must_use]
pub(crate) fn progress(&self, percent: f32) -> bool {
if let Some(f) = &self.progress_callback {
f(percent) == ControlFlow::Break
} else {
false
}
}
#[inline(always)]
pub(crate) fn verbose_print(&self, msg: impl AsRef<str>) {
fn print_(a: &Attributes, msg: &str) {
if let Some(f) = &a.log_callback {
f(a, msg);
}
}
print_(self, msg.as_ref());
}
#[inline]
pub(crate) fn verbose_printf_flush(&self) {
if let Some(f) = &self.log_flush_callback {
f(self);
}
}
#[must_use]
pub(crate) fn feedback_loop_trials(&self, hist_items: usize) -> u16 {
let mut feedback_loop_trials = self.feedback_loop_trials;
if hist_items > 5000 {
feedback_loop_trials = (feedback_loop_trials * 3 + 3) / 4;
}
if hist_items > 25000 {
feedback_loop_trials = (feedback_loop_trials * 3 + 3) / 4;
}
if hist_items > 50000 {
feedback_loop_trials = (feedback_loop_trials * 3 + 3) / 4;
}
if hist_items > 100_000 {
feedback_loop_trials = (feedback_loop_trials * 3 + 3) / 4;
}
feedback_loop_trials
}
pub(crate) fn target_mse(&self, hist_items_len: usize) -> (Option<f64>, f64, bool) {
let max_mse = self.max_mse.map(|mse| mse * if hist_items_len <= MAX_COLORS { 0.33 } else { 1. });
let aim_for_perfect_quality = self.target_mse == 0.;
let mut target_mse = self.target_mse.max((f64::from(1 << self.min_posterization_output) / 1024.).powi(2));
if let Some(max_mse) = max_mse {
target_mse = target_mse.min(max_mse);
}
(max_mse, target_mse, aim_for_perfect_quality)
}
#[must_use]
pub(crate) fn kmeans_iterations(&self, hist_items_len: usize, palette_error_is_known: bool) -> (u16, f64) {
let mut iteration_limit = self.kmeans_iteration_limit;
let mut iterations = self.kmeans_iterations;
if hist_items_len > 5000 {
iterations = (iterations * 3 + 3) / 4;
}
if hist_items_len > 25000 {
iterations = (iterations * 3 + 3) / 4;
}
if hist_items_len > 50000 {
iterations = (iterations * 3 + 3) / 4;
}
if hist_items_len > 100_000 {
iterations = (iterations * 3 + 3) / 4;
iteration_limit *= 2.;
}
if iterations == 0 && !palette_error_is_known && self.max_mse.is_some() {
iterations = 1;
}
(iterations, iteration_limit)
}
#[inline]
#[must_use]
pub(crate) fn posterize_bits(&self) -> u8 {
self.min_posterization_output.max(self.min_posterization_input)
}
}
impl Drop for Attributes {
fn drop(&mut self) {
self.verbose_printf_flush();
}
}
impl Default for Attributes {
#[inline(always)]
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(C)]
pub enum ControlFlow {
Continue = 1,
Break = 0,
}
#[test]
fn counters() {
let mut a = Attributes::new();
a.set_speed(10).unwrap();
let (iter, _) = a.kmeans_iterations(1000, false);
assert_eq!(iter, 0);
a.set_quality(80, 90).unwrap();
let (iter, limit) = a.kmeans_iterations(1000, false);
assert_eq!(iter, 1);
assert!(limit > 0. && limit < 0.01);
let (iter, _) = a.kmeans_iterations(1000, true);
assert_eq!(iter, 0);
let mut a = Attributes::new();
a.set_quality(50, 80).unwrap();
let (max_mse, target_mse, aim_perfect) = a.target_mse(10000);
let max_mse = max_mse.unwrap();
assert!(!aim_perfect);
assert!(target_mse > 0. && target_mse < 0.01);
assert!(max_mse > 0. && max_mse > target_mse && max_mse < 0.01);
}
#[test]
fn getset() {
let mut a = Attributes::new();
assert!(a.set_quality(0, 101).is_err());
assert!(a.set_quality(50, 49).is_err());
assert!(a.feedback_loop_trials(1000) > 0);
let (max_mse, target_mse, aim_perfect) = a.target_mse(10000);
assert!(aim_perfect);
assert!(target_mse < 0.0001);
assert_eq!(max_mse, None);
a.set_speed(5).unwrap();
assert_eq!(5, a.speed());
assert!(a.set_speed(99).is_err());
assert!(a.set_speed(0).is_err());
a.set_max_colors(5).unwrap();
assert_eq!(5, a.max_colors());
assert!(a.set_max_colors(0).is_err());
a.set_min_posterization(2).unwrap();
assert_eq!(2, a.min_posterization());
assert_eq!(2, a.posterize_bits());
assert!(a.set_min_posterization(8).is_err());
let mut a = Attributes::new();
a.set_speed(10).unwrap();
assert_eq!(1, a.posterize_bits());
}