1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
pub mod palette;
pub mod utils;

use color_quant::NeuQuant;
use image::{
    imageops::{blur, dither, resize, ColorMap, FilterType::Triangle},
    Rgba, RgbaImage,
};

pub use palette::palettes::*;

#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

/// These options modify the algorithm(s) used by `convert`. You can specify
/// built-in pre-processing operations like resizing and quantization,
/// processing parameters like transparency tolerance and average kernel, and
/// post-processing operations like Gaussian blur.
///
/// `Options` implements [`std::default::Default`], so you can use struct
/// builder syntax to easily make an `Options` struct that "overrides" the
/// default struct.
///
/// ```ignore
/// let options = Options {
///     resize: [1920, 1080],
///     blur: 0.4,
///     ..Default::default()
/// };
/// assert_eq!(options, Options {
///     resize: [1920, 1080],
///     quantize: 0,
///     avg: [0, 0],
///     transparency_tolerance: 0,
///     blur: 0.4,
/// })
/// ```
#[cfg_attr(feature = "ffi", repr(C))]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct Options {
    /// Resize image by a certain factor before performing other processing. The
    /// image will be resized using linear filtering back to the original
    /// dimensions before it is output. This will cause the algorithm to
    /// consider bigger "pixels" in the image, which will result in a
    /// lower quality conversion.
    #[deprecated]
    pub resize: u32,
    /// Quantize the image by the given balance. The value must be between 1 and
    /// 30. A value closer to 1 will be slower, but provide better
    /// quantization. The default value of 10 is a good balance between
    /// speed and quality.
    ///
    /// Passing any invalid value (like 0) disables quantization, which is the
    /// default behavior
    pub quantize: i32,
    /// Perform a Gaussian blur on the output image. This can help smooth
    /// gradients and remove unwanted artifacts.
    ///
    /// `0.0` means don't blur.
    pub blur: f32,
}

impl Default for Options {
    #[allow(deprecated)]
    fn default() -> Self {
        Options {
            blur: 0.,
            resize: 0,
            quantize: 0,
        }
    }
}

// TODO: make generic over different image types
#[allow(deprecated)]
pub fn convert(
    img: &RgbaImage,
    opt: Options,
    palette: &impl ColorMap<Color = Rgba<u8>>,
) -> RgbaImage {
    // resize the image to simulate averaging of pixels
    let (w, h) = img.dimensions(); // save width and height for later
    let mut img = if opt.resize > 1 {
        // sample using linear filtering
        resize(img, w - w / opt.resize, h - h / opt.resize, Triangle)
    } else {
        img.clone()
    };

    // dither image using neu-quant quantization
    if (1..=30).contains(&opt.quantize) {
        let ref q = NeuQuant::new(opt.quantize, 256, img.as_raw()); // train neural network
        dither(&mut img, q);
    }

    // re-color the image using the provided palette
    dither(&mut img, palette);

    // blur image
    let img = if opt.blur > 0. {
        blur(&img, opt.blur)
    } else {
        img
    };

    // if we resized earlier, restore original size
    if opt.resize > 1 {
        resize(&img, w, h, Triangle)
    } else {
        img
    }
}