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
//! A collection of commonly used resampling filters.

use std::io;
use image::{imageops, DynamicImage, ImageBuffer, GenericImageView, FilterType, Bgra};
use resvg::{usvg::{self, Tree}, raqote::DrawTarget , FitTo};
pub use error::ResampleError;

mod error;

/// [Linear resampling filter](https://en.wikipedia.org/wiki/Linear_interpolation).
pub fn linear(source: &DynamicImage, size: u32) -> io::Result<DynamicImage> {
    overfit(&scale(source, size, FilterType::Triangle)?, size)
}

/// [Lanczos resampling filter](https://en.wikipedia.org/wiki/Lanczos_resampling).
pub fn cubic(source: &DynamicImage, size: u32) -> io::Result<DynamicImage> {
    overfit(&scale(source, size, FilterType::Lanczos3)?, size)
}

/// [Nearest-Neighbor resampling filter](https://en.wikipedia.org/wiki/Nearest-neighbor_interpolation).
pub fn nearest(source: &DynamicImage, size: u32) -> io::Result<DynamicImage> {
    let scaled = if source.width() < size && source.height() < size {
        nearest_upscale_integer(source, size)?
    } else {
        scale(source, size, FilterType::Nearest)?
    };

    overfit(&scaled, size)
}

/// Aplies a resampling filter to `source` and checks if the dimensions
/// of the output match the ones specified by `size`.
pub fn apply<F: FnMut(&DynamicImage, u32) -> io::Result<DynamicImage>>(
    mut filter: F,
    source: &DynamicImage,
    size: u32
) -> Result<DynamicImage, ResampleError> {
    let icon = filter(source, size)?;
    let dims = icon.dimensions();

    if dims != (size, size) {
        Err(ResampleError::MismatchedDimensions(size, dims))
    } else {
        Ok(icon)
    }
}

/// Rescales `source` to fit in _`size`x`size`_ image while only scaling it on an integer scale.
fn nearest_upscale_integer(source: &DynamicImage, size: u32) -> io::Result<DynamicImage> {
    let (w ,  h) = source.dimensions();

    let scale = if w > h { size / w } else { size / h };
    let (nw, nh) = (w * scale, h * scale);

    Ok(DynamicImage::ImageRgba8(imageops::resize(source, nw, nh, FilterType::Nearest)))
}

/// Rescales `source` to fit in _`size`x`size`_ image.
fn scale(source: &DynamicImage, size: u32, filter: FilterType) -> io::Result<DynamicImage> {
    let (w ,  h) = source.dimensions();

    let (nw, nh) = if w > h { (size, (size * h) / w) } else { ((size * w) / h, size) };

    Ok(DynamicImage::ImageRgba8(imageops::resize(source, nw, nh, filter)))
}

/// Adds transparent borders to an image so that the output is square.
fn overfit(source: &DynamicImage, size: u32) -> io::Result<DynamicImage> {
    let mut output = DynamicImage::new_rgba8(size, size);

    let dx = (output.width()  - source.width() ) / 2;
    let dy = (output.height() - source.height()) / 2;

    imageops::overlay(&mut output, source, dx, dy);
    Ok(output)
}

/// Rasterizes an _SVG_ tree to a `DynamicImage`.
pub(crate) fn svg(source: &Tree, size: u32) -> Result<DynamicImage, ResampleError> {
    let rect = source.svg_node().view_box.rect;
    let (w, h) = (rect.width(), rect.height());
    let fit_to = if w > h { FitTo::Width(size) } else { FitTo::Height(size) };

    let opts = resvg::Options {
        usvg: usvg::Options::default(),
        fit_to,
        background: None
    };

    // In this context it's safe to assume render_to_image will return Some(_)
    // https://github.com/RazrFalcon/resvg/issues/175#issuecomment-531477376
    let draw_target = resvg::backend_raqote::render_to_image(source, &opts)
        .expect("Could not render svg tree to image buffer");

    Ok(draw_target_to_rgba(draw_target, size)?)
}

#[inline]
/// Converts a `DrawTarget` to a `DynamicImage`.
fn draw_target_to_rgba(mut surface: DrawTarget, size: u32) -> io::Result<DynamicImage> {
    let (w, h) = (surface.width() as u32, surface.height() as u32);
    let data = surface.get_data_u8_mut().to_vec();

    // If ImageBuffer::from_vec returns None then there's a bug in
    // resvg
    match ImageBuffer::<Bgra<u8>, Vec<u8>>::from_vec(w, h, data) {
        Some(buf) => overfit(&DynamicImage::ImageBgra8(buf), size),
        None      => panic!("Buffer in not big enought")
    }
}