imagequant/
lib.rs

1//! <https://pngquant.org/lib/>
2//!
3//! Converts RGBA images to 8-bit with alpha channel.
4//!
5//! See `examples/` directory for example code.
6#![doc(html_logo_url = "https://pngquant.org/pngquant-logo.png")]
7#![deny(missing_docs)]
8#![allow(clippy::bool_to_int_with_if)]
9#![allow(clippy::cast_possible_truncation)]
10#![allow(clippy::doc_markdown)]
11#![allow(clippy::if_not_else)]
12#![allow(clippy::inline_always)]
13#![allow(clippy::items_after_statements)]
14#![allow(clippy::map_unwrap_or)]
15#![allow(clippy::missing_errors_doc)]
16#![allow(clippy::module_name_repetitions)]
17#![allow(clippy::redundant_closure_for_method_calls)]
18#![allow(clippy::unreadable_literal)]
19#![allow(clippy::wildcard_imports)]
20#![deny(clippy::semicolon_if_nothing_returned)]
21
22mod attr;
23mod blur;
24mod error;
25mod hist;
26mod image;
27mod kmeans;
28mod mediancut;
29mod nearest;
30mod pal;
31mod quant;
32mod remap;
33mod rows;
34mod seacow;
35
36#[cfg(not(feature = "threads"))]
37mod rayoff;
38
39#[cfg(feature = "threads")]
40mod rayoff {
41    pub(crate) fn num_cpus() -> usize { std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1) }
42    pub(crate) use rayon::prelude::{ParallelBridge, ParallelIterator, ParallelSliceMut};
43    pub(crate) use rayon::in_place_scope as scope;
44    pub(crate) use thread_local::ThreadLocal;
45}
46
47#[cfg_attr(feature = "threads", repr(align(128)))]
48pub(crate) struct CacheLineAlign<T>(pub T);
49
50/// Use imagequant-sys crate instead
51#[cfg(feature = "_internal_c_ffi")]
52pub mod capi;
53
54pub use attr::{Attributes, ControlFlow};
55pub use error::Error;
56pub use hist::{Histogram, HistogramEntry};
57pub use image::Image;
58#[doc(hidden)]
59pub use pal::Palette;
60pub use pal::RGBA;
61pub use quant::QuantizationResult;
62
63#[doc(hidden)]
64#[deprecated(note = "Please use the imagequant::Error type. This will be removed")]
65pub use error::Error as liq_error;
66
67const LIQ_HIGH_MEMORY_LIMIT: usize = 1 << 26;
68
69/// [Start here][Attributes]: creates new handle for library configuration
70///
71/// See [`Attributes`]
72#[inline(always)]
73#[must_use]
74pub fn new() -> Attributes {
75    Attributes::new()
76}
77
78#[test]
79fn copy_img() {
80    let tmp = vec![RGBA::new(1, 2, 3, 4); 10 * 100];
81    let liq = Attributes::new();
82    let _ = liq.new_image_stride(tmp, 10, 100, 10, 0.).unwrap();
83}
84
85#[test]
86fn takes_rgba() {
87    let liq = Attributes::new();
88
89    let img = vec![RGBA { r: 0, g: 0, b: 0, a: 0 }; 8];
90
91    liq.new_image_borrowed(&img, 1, 1, 0.0).unwrap();
92    liq.new_image_borrowed(&img, 4, 2, 0.0).unwrap();
93    liq.new_image_borrowed(&img, 8, 1, 0.0).unwrap();
94    assert!(liq.new_image_borrowed(&img, 9, 1, 0.0).is_err());
95    assert!(liq.new_image_borrowed(&img, 4, 3, 0.0).is_err());
96}
97
98#[test]
99fn histogram() {
100    let attr = Attributes::new();
101    let mut hist = Histogram::new(&attr);
102
103    let bitmap1 = [RGBA { r: 0, g: 0, b: 0, a: 0 }; 1];
104    let mut image1 = attr.new_image(&bitmap1[..], 1, 1, 0.0).unwrap();
105    hist.add_image(&attr, &mut image1).unwrap();
106
107    let bitmap2 = [RGBA { r: 255, g: 255, b: 255, a: 255 }; 1];
108    let mut image2 = attr.new_image(&bitmap2[..], 1, 1, 0.0).unwrap();
109    hist.add_image(&attr, &mut image2).unwrap();
110
111    hist.add_colors(&[HistogramEntry {
112        color: RGBA::new(255, 128, 255, 128),
113        count: 10,
114    }], 0.0).unwrap();
115
116    let mut res = hist.quantize(&attr).unwrap();
117    let pal = res.palette();
118    assert_eq!(3, pal.len());
119}
120
121#[test]
122fn poke_it() {
123    let width = 10usize;
124    let height = 10usize;
125    let mut fakebitmap = vec![RGBA::new(255, 255, 255, 255); width * height];
126
127    fakebitmap[0].r = 0x55;
128    fakebitmap[0].g = 0x66;
129    fakebitmap[0].b = 0x77;
130
131    // Configure the library
132    let mut liq = Attributes::new();
133    liq.set_speed(5).unwrap();
134    liq.set_quality(70, 99).unwrap();
135    liq.set_min_posterization(1).unwrap();
136    assert_eq!(1, liq.min_posterization());
137    liq.set_min_posterization(0).unwrap();
138
139    use std::sync::atomic::AtomicBool;
140    use std::sync::atomic::Ordering::SeqCst;
141    use std::sync::Arc;
142
143    let log_called = Arc::new(AtomicBool::new(false));
144    let log_called2 = log_called.clone();
145    liq.set_log_callback(move |_attr, _msg| {
146        log_called2.store(true, SeqCst);
147    });
148
149    let prog_called = Arc::new(AtomicBool::new(false));
150    let prog_called2 = prog_called.clone();
151    liq.set_progress_callback(move |_perc| {
152        prog_called2.store(true, SeqCst);
153        ControlFlow::Continue
154    });
155
156    // Describe the bitmap
157    let img = &mut liq.new_image(&fakebitmap[..], width, height, 0.0).unwrap();
158
159    // The magic happens in quantize()
160    let mut res = match liq.quantize(img) {
161        Ok(res) => res,
162        Err(err) => panic!("Quantization failed, because: {err:?}"),
163    };
164
165    // Enable dithering for subsequent remappings
166    res.set_dithering_level(1.0).unwrap();
167
168    // You can reuse the result to generate several images with the same palette
169    let (palette, pixels) = res.remapped(img).unwrap();
170
171    assert_eq!(width * height, pixels.len());
172    assert_eq!(100, res.quantization_quality().unwrap());
173    assert_eq!(RGBA { r: 255, g: 255, b: 255, a: 255 }, palette[0]);
174    assert_eq!(RGBA { r: 0x55, g: 0x66, b: 0x77, a: 255 }, palette[1]);
175
176    assert!(log_called.load(SeqCst));
177    assert!(prog_called.load(SeqCst));
178}
179
180#[test]
181fn set_importance_map() {
182    let liq = new();
183    let bitmap = &[RGBA::new(255, 0, 0, 255), RGBA::new(0u8, 0, 255, 255)];
184    let mut img = liq.new_image(&bitmap[..], 2, 1, 0.).unwrap();
185    let map = &[255, 0];
186    img.set_importance_map(&map[..]).unwrap();
187    let mut res = liq.quantize(&mut img).unwrap();
188    let pal = res.palette();
189    assert_eq!(1, pal.len(), "{pal:?}");
190    assert_eq!(bitmap[0], pal[0]);
191}
192
193#[test]
194fn thread() {
195    let liq = Attributes::new();
196    std::thread::spawn(move || {
197        let b = vec![RGBA::new(0, 0, 0, 0); 1];
198        liq.new_image_borrowed(&b, 1, 1, 0.).unwrap();
199    }).join().unwrap();
200}
201
202#[test]
203fn r_callback_test() {
204    use std::mem::MaybeUninit;
205    use std::sync::atomic::AtomicU16;
206    use std::sync::atomic::Ordering::SeqCst;
207    use std::sync::Arc;
208
209    let called = Arc::new(AtomicU16::new(0));
210    let called2 = called.clone();
211    let mut res = {
212        let a = new();
213        let get_row = move |output_row: &mut [MaybeUninit<RGBA>], y: usize| {
214            assert!((0..5).contains(&y));
215            assert_eq!(123, output_row.len());
216            for (n, out) in output_row.iter_mut().enumerate() {
217                let n = n as u8;
218                out.write(RGBA::new(n, n, n, n));
219            }
220            called2.fetch_add(1, SeqCst);
221        };
222        let mut img = unsafe {
223            Image::new_fn(&a, get_row, 123, 5, 0.).unwrap()
224        };
225        a.quantize(&mut img).unwrap()
226    };
227    let called = called.load(SeqCst);
228    assert!(called > 5 && called < 50);
229    assert_eq!(123, res.palette().len());
230}
231
232#[test]
233fn sizes() {
234    use pal::PalF;
235    use pal::Palette;
236    assert!(std::mem::size_of::<PalF>() < crate::pal::MAX_COLORS*(8*4)+32, "{}", std::mem::size_of::<PalF>());
237    assert!(std::mem::size_of::<QuantizationResult>() < std::mem::size_of::<PalF>() + std::mem::size_of::<Palette>() + 100, "{}", std::mem::size_of::<QuantizationResult>());
238    assert!(std::mem::size_of::<Attributes>() < 200);
239    assert!(std::mem::size_of::<Image>() < 300);
240    assert!(std::mem::size_of::<Histogram>() < 200);
241    assert!(std::mem::size_of::<crate::hist::HistItem>() <= 32);
242}
243
244#[doc(hidden)]
245pub fn _unstable_internal_kmeans_bench() -> impl FnMut() {
246    use crate::pal::{PalF, PalPop};
247
248    let attr = new();
249    let mut h = hist::Histogram::new(&attr);
250
251    let e = (0..10000u32).map(|i| HistogramEntry {
252        count: i.wrapping_mul(17) % 12345,
253        color: RGBA::new(i as u8, (i.wrapping_mul(7) >> 2) as u8, (i.wrapping_mul(11) >> 11) as u8, 255),
254    }).collect::<Vec<_>>();
255
256    h.add_colors(&e, 0.).unwrap();
257    let mut hist = h.finalize_builder(0.45455).unwrap();
258
259    let lut = pal::gamma_lut(0.45455);
260    let mut p = PalF::new();
261    for i in 0..=255 {
262        p.push(pal::f_pixel::from_rgba(&lut, RGBA::new(i|7, i, i, 255)), PalPop::new(1.));
263    }
264
265    move || {
266        kmeans::Kmeans::iteration(&mut hist, &mut p, false).unwrap();
267    }
268}
269
270trait PushInCapacity<T> {
271    fn push_in_cap(&mut self, val: T);
272}
273
274impl<T> PushInCapacity<T> for Vec<T> {
275    #[track_caller]
276    #[inline(always)]
277    fn push_in_cap(&mut self, val: T) {
278        debug_assert!(self.capacity() != self.len());
279        if self.capacity() != self.len() {
280            self.push(val);
281        }
282    }
283}
284
285/// Rust is too conservative about sorting floats.
286/// This library uses only finite values, so they're sortable.
287#[derive(Debug, PartialEq, PartialOrd, Copy, Clone)]
288#[repr(transparent)]
289struct OrdFloat<T>(pub(crate) T);
290
291impl Eq for OrdFloat<f32> {
292}
293
294impl Ord for OrdFloat<f32> {
295    #[inline]
296    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
297        self.0.partial_cmp(&other.0).unwrap_or(std::cmp::Ordering::Equal)
298    }
299}
300
301impl Eq for OrdFloat<f64> {
302}
303
304impl Ord for OrdFloat<f64> {
305    #[inline]
306    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
307        self.0.partial_cmp(&other.0).unwrap_or(std::cmp::Ordering::Equal)
308    }
309}
310
311impl OrdFloat<f32> {
312    pub fn new(v: f32) -> Self {
313        debug_assert!(v.is_finite());
314        Self(v)
315    }
316}
317
318impl OrdFloat<f64> {
319    pub fn new64(v: f64) -> Self {
320        debug_assert!(v.is_finite());
321        Self(v)
322    }
323}
324
325#[test]
326fn test_fixed_colors() {
327    let attr = Attributes::new();
328    let mut h = Histogram::new(&attr);
329    let tmp = (0..128).map(|c| HistogramEntry {
330        color: RGBA::new(c,c,c,255),
331        count: 1,
332    }).collect::<Vec<_>>();
333    h.add_colors(&tmp, 0.).unwrap();
334    for f in 200..255 {
335        h.add_fixed_color(RGBA::new(f, f, f, 255), 0.).unwrap();
336    }
337    let mut r = h.quantize(&attr).unwrap();
338    let pal = r.palette();
339
340    for (i, c) in (200..255).enumerate() {
341        assert_eq!(pal[i], RGBA::new(c, c, c, 255));
342    }
343
344    for c in 0..128 {
345        assert!(pal[55..].iter().any(|&p| p == RGBA::new(c, c, c, 255)));
346    }
347}