resize-0.4.3/.cargo_vcs_info.json0000644000000001121371476053500124440ustar00{ "git": { "sha1": "01dd239154dac7b9716f851bf764b7c6cb7cb234" } } resize-0.4.3/Cargo.lock0000644000000035261371476053500104330ustar00# This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] name = "adler32" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" [[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "byteorder" version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" [[package]] name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "crc32fast" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" dependencies = [ "cfg-if", ] [[package]] name = "deflate" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" dependencies = [ "adler32", "byteorder", ] [[package]] name = "miniz_oxide" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" dependencies = [ "adler32", ] [[package]] name = "png" version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfe7f9f1c730833200b134370e1d5098964231af8450bce9b78ee3ab5278b970" dependencies = [ "bitflags", "crc32fast", "deflate", "miniz_oxide", ] [[package]] name = "resize" version = "0.4.3" dependencies = [ "png", ] resize-0.4.3/Cargo.toml0000644000000021671371476053500104560ustar00# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "resize" version = "0.4.3" authors = ["Kagami Hiiragi ", "Kornel "] include = ["Cargo.toml", "README.md", "LICENSE", "src/*.rs"] description = "Simple image resampling library in pure Rust." homepage = "https://github.com/PistonDevelopers/resize" documentation = "https://docs.rs/resize" readme = "README.md" keywords = ["resize", "scale", "resample", "image", "graphics"] categories = ["graphics", "multimedia::images"] license = "MIT" repository = "https://github.com/PistonDevelopers/resize.git" [dev-dependencies.png] version = "0.16.7" resize-0.4.3/Cargo.toml.orig010064400007660000024000000011431371476030300141310ustar0000000000000000[package] name = "resize" version = "0.4.3" authors = ["Kagami Hiiragi ", "Kornel "] description = "Simple image resampling library in pure Rust." keywords = ["resize", "scale", "resample", "image", "graphics"] license = "MIT" readme = "README.md" categories = ["graphics", "multimedia::images"] homepage = "https://github.com/PistonDevelopers/resize" repository = "https://github.com/PistonDevelopers/resize.git" documentation = "https://docs.rs/resize" edition = "2018" include = ["Cargo.toml", "README.md", "LICENSE", "src/*.rs"] [dev-dependencies] png = "0.16.7" resize-0.4.3/LICENSE010064400007660000024000000020731363425524500122570ustar0000000000000000The MIT License (MIT) Copyright (c) 2015 PistonDevelopers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. resize-0.4.3/README.md010064400007660000024000000037671371475745500125550ustar0000000000000000# resize [![Build Status](https://travis-ci.org/PistonDevelopers/resize.png?branch=master)](https://travis-ci.org/PistonDevelopers/resize) [![crates.io](https://img.shields.io/crates/v/resize.svg)](https://crates.io/crates/resize) Simple resampling library in pure Rust. ## Features * No dependencies, minimal abstractions * No encoders/decoders, meant to be used with some external library * Tuned for resizing to the same dimensions multiple times: uses preallocated buffers and matrixes ## Usage ```rust use resize::Pixel::RGB24; use resize::Type::Lanczos3; // Downscale by 2x. let (w1, h1) = (640, 480); let (w2, h2) = (320, 240); // Don't forget to fill `src` with image data (RGB24). let src = vec![0;w1*h1*3]; // Destination buffer. Must be mutable. let mut dst = vec![0;w2*h2*3]; // Create reusable instance. let mut resizer = resize::new(w1, h1, w2, h2, RGB24, Lanczos3); // Do resize without heap allocations. // Might be executed multiple times for different `src` or `dst`. resizer.resize(&src, &mut dst); ``` See [API documentation](http://docs.piston.rs/resize/resize/) for overview of all available methods. See also [this example](examples/resize.rs). ## Recommendations Read [this](http://www.imagemagick.org/Usage/filter/) and [this](http://www.imagemagick.org/Usage/filter/nicolas/) great articles on image resizing technics and resampling filters. Tldr; (with built-in filters of this library) use `Lanczos3` for downscaling, use `Mitchell` for upscaling. You may also want to [downscale in linear colorspace](http://www.imagemagick.org/Usage/resize/#resize_colorspace) (but not upscale). Gamma correction routines currently not included to the library, but actually quite simple to accomplish manually, see [here](https://en.wikipedia.org/wiki/Gamma_correction) for some basic theory. ## License * Library is licensed under [MIT](LICENSE) * Image used in examples is licensed under [CC BY-SA 3.0](https://commons.wikimedia.org/wiki/File%3A08-2011._Panthera_tigris_tigris_-_Texas_Park_-_Lanzarote_-TP04.jpg) resize-0.4.3/src/lib.rs010064400007660000024000000273741371476027600131740ustar0000000000000000//! Simple resampling library in pure Rust. //! //! # Examples //! //! ``` //! use resize::Pixel::RGB24; //! use resize::Type::Lanczos3; //! //! // Downscale by 2x. //! let (w1, h1) = (640, 480); //! let (w2, h2) = (320, 240); //! // Don't forget to fill `src` with image data (RGB24). //! let src = vec![0;w1*h1*3]; //! // Destination buffer. Must be mutable. //! let mut dst = vec![0;w2*h2*3]; //! // Create reusable instance. //! let mut resizer = resize::new(w1, h1, w2, h2, RGB24, Lanczos3); //! // Do resize without heap allocations. //! // Might be executed multiple times for different `src` or `dst`. //! resizer.resize(&src, &mut dst); //! ``` // Current implementation is based on: // * https://github.com/sekrit-twc/zimg/tree/master/src/zimg/resize // * https://github.com/PistonDevelopers/image/blob/master/src/imageops/sample.rs #![deny(missing_docs)] use std::sync::Arc; use std::collections::HashMap; use std::f32; mod px; pub use px::*; /// Resizing type to use. pub enum Type { /// Point resizing. Point, /// Triangle (bilinear) resizing. Triangle, /// Catmull-Rom (bicubic) resizing. Catrom, /// Resize using Mitchell-Netravali filter. Mitchell, /// Resize using Sinc-windowed Sinc with radius of 3. Lanczos3, /// Resize with custom filter. Custom(Filter), } /// Resampling filter. pub struct Filter { kernel: Box f32>, support: f32, } impl Filter { /// Create a new filter. /// /// # Examples /// /// ``` /// use resize::Filter; /// fn kernel(x: f32) -> f32 { f32::max(1.0 - x.abs(), 0.0) } /// let filter = Filter::new(Box::new(kernel), 1.0); /// ``` #[must_use] pub fn new(kernel: Box f32>, support: f32) -> Self { Self { kernel, support } } /// Helper to create Cubic filter with custom B and C parameters. #[must_use] pub fn new_cubic(b: f32, c: f32) -> Self { Self::new(Box::new(move |x| cubic_bc(b, c, x)), 2.0) } /// Helper to create Lanczos filter with custom radius. #[must_use] pub fn new_lanczos(radius: f32) -> Self { Self::new(Box::new(move |x| lanczos(radius, x)), radius) } } #[inline] fn point_kernel(_: f32) -> f32 { 1.0 } #[inline] fn triangle_kernel(x: f32) -> f32 { f32::max(1.0 - x.abs(), 0.0) } // Taken from // https://github.com/PistonDevelopers/image/blob/2921cd7/src/imageops/sample.rs#L68 // TODO(Kagami): Could be optimized for known B and C, see e.g. // https://github.com/sekrit-twc/zimg/blob/1a606c0/src/zimg/resize/filter.cpp#L149 #[inline] fn cubic_bc(b: f32, c: f32, x: f32) -> f32 { let a = x.abs(); let k = if a < 1.0 { (12.0 - 9.0 * b - 6.0 * c) * a.powi(3) + (-18.0 + 12.0 * b + 6.0 * c) * a.powi(2) + (6.0 - 2.0 * b) } else if a < 2.0 { (-b - 6.0 * c) * a.powi(3) + (6.0 * b + 30.0 * c) * a.powi(2) + (-12.0 * b - 48.0 * c) * a + (8.0 * b + 24.0 * c) } else { 0.0 }; k / 6.0 } #[inline] fn sinc(x: f32) -> f32 { if x == 0.0 { 1.0 } else { let a = x * f32::consts::PI; a.sin() / a } } #[inline] fn lanczos(taps: f32, x: f32) -> f32 { if x.abs() < taps { sinc(x) * sinc(x / taps) } else { 0.0 } } /// Supported pixel formats. // TODO(Kagami): YUV planes? #[allow(non_snake_case)] pub mod Pixel { /// Grayscale, 8-bit. #[derive(Debug, Clone, Copy)] pub struct Gray8; /// Grayscale, 16-bit, native endian. #[derive(Debug, Clone, Copy)] pub struct Gray16; /// RGB, 8-bit per component. #[derive(Debug, Clone, Copy)] pub struct RGB24; /// RGB, 16-bit per component, native endian. #[derive(Debug, Clone, Copy)] pub struct RGB48; /// RGBA, 8-bit per component. #[derive(Debug, Clone, Copy)] pub struct RGBA; /// RGBA, 16-bit per component, native endian. #[derive(Debug, Clone, Copy)] pub struct RGBA64; } /// Resampler with preallocated buffers and coeffecients for the given /// dimensions and filter type. #[derive(Debug)] pub struct Resizer { // Source/target dimensions. w1: usize, h1: usize, w2: usize, h2: usize, pix_fmt: Format, // Temporary/preallocated stuff. tmp: Vec, coeffs_w: Vec, coeffs_h: Vec, } #[derive(Debug, Clone)] struct CoeffsLine { start: usize, coeffs: Arc<[f32]>, } impl Resizer { /// Create a new resizer instance. pub fn new(source_width: usize, source_heigth: usize, dest_width: usize, dest_height: usize, pixel_format: Format, filter_type: Type) -> Self { let filter = match filter_type { Type::Point => Filter::new(Box::new(point_kernel), 0.0), Type::Triangle => Filter::new(Box::new(triangle_kernel), 1.0), Type::Catrom => Filter::new_cubic(0.0, 0.5), Type::Mitchell => Filter::new_cubic(1.0/3.0, 1.0/3.0), Type::Lanczos3 => Filter::new_lanczos(3.0), Type::Custom(f) => f, }; // filters very often create repeating patterns, // so overall memory used by them can be reduced // which should save some cache space let mut recycled_coeffs = HashMap::new(); let coeffs_w = Self::calc_coeffs(source_width, dest_width, &filter, &mut recycled_coeffs); let coeffs_h = if source_heigth == source_width && dest_height == dest_width { coeffs_w.clone() } else { Self::calc_coeffs(source_heigth, dest_height, &filter, &mut recycled_coeffs) }; Self { w1: source_width, h1: source_heigth, w2: dest_width, h2: dest_height, tmp: Vec::with_capacity(source_width * dest_height * pixel_format.get_ncomponents()), pix_fmt: pixel_format, coeffs_w, coeffs_h, } } fn calc_coeffs(s1: usize, s2: usize, f: &Filter, recycled_coeffs: &mut HashMap<(usize, [u8; 4], [u8; 4]), Arc<[f32]>>) -> Vec { let ratio = s1 as f32 / s2 as f32; // Scale the filter when downsampling. let filter_scale = ratio.max(1.); let filter_radius = (f.support * filter_scale).ceil(); (0..s2).map(|x2| { let x1 = (x2 as f32 + 0.5) * ratio - 0.5; let start = (x1 - filter_radius).ceil() as isize; let start = Self::clamp(start, 0, s1 as isize - 1) as usize; let end = (x1 + filter_radius).floor() as isize; let end = Self::clamp(end, 0, s1 as isize - 1) as usize; let sum: f32 = (start..=end).map(|i| (f.kernel)((i as f32 - x1) / filter_scale)).sum(); let key = (end - start, filter_scale.to_ne_bytes(), (x1 - start as f32).to_ne_bytes()); let coeffs = recycled_coeffs.entry(key).or_insert_with(|| { (start..=end).map(|i| { let v = (f.kernel)((i as f32 - x1) / filter_scale); v / sum }).collect::>() }).clone(); CoeffsLine { start, coeffs } }).collect() } #[inline] fn clamp(input: N, min: N, max: N) -> N { if input > max { max } else if input < min { min } else { input } } // Resample W1xH1 to W1xH2. // Stride is a length of the source row (>= W1) fn sample_rows(&mut self, src: &[Format::Subpixel], stride: usize) { let ncomp = self.pix_fmt.get_ncomponents(); self.tmp.clear(); assert!(self.tmp.capacity() <= self.w1 * self.h2 * ncomp); // no reallocations for x1 in 0..self.w1 { let h2 = self.h2; let coeffs_h = &self.coeffs_h[0..h2]; for y2 in 0..h2 { let mut accum = Format::new_accum(); let line = &coeffs_h[y2]; let src = &src[(line.start * stride + x1) * ncomp..]; for (i, coeff) in line.coeffs.iter().copied().enumerate() { let base = (i * stride) * ncomp; let src = &src[base..base + ncomp]; for (acc, s) in accum.as_mut().iter_mut().zip(src) { *acc += Format::from_subpixel(s) * coeff; } } for &v in accum.as_ref().iter() { self.tmp.push(v); } } } } // Resample W1xH2 to W2xH2. fn sample_cols(&mut self, dst: &mut [Format::Subpixel]) { let ncomp = self.pix_fmt.get_ncomponents(); let mut offset = 0; // Assert that dst is large enough let dst = &mut dst[0..self.h2 * self.w2 * ncomp]; for y2 in 0..self.h2 { let w2 = self.w2; let coeffs_w = &self.coeffs_w[0..w2]; for x2 in 0..w2 { let mut accum = Format::new_accum(); let line = &coeffs_w[x2]; for (i, coeff) in line.coeffs.iter().copied().enumerate() { let x0 = line.start + i; let base = (x0 * self.h2 + y2) * ncomp; let tmp = &self.tmp[base..base + ncomp]; for (acc, &p) in accum.as_mut().iter_mut().zip(tmp) { *acc += p * coeff; } } for &v in accum.as_ref().iter() { dst[offset] = Format::into_subpixel(v); offset += 1; } } } } /// Resize `src` image data into `dst`. pub fn resize(&mut self, src: &[Format::Subpixel], dst: &mut [Format::Subpixel]) { let stride = self.w1; self.resize_stride(src, stride, dst) } /// Resize `src` image data into `dst`, skipping `stride` pixels each row. pub fn resize_stride(&mut self, src: &[Format::Subpixel], src_stride: usize, dst: &mut [Format::Subpixel]) { // TODO(Kagami): // * Multi-thread // * Bound checkings // * SIMD assert!(self.w1 <= src_stride); assert!(src.len() >= src_stride * self.h1 * self.pix_fmt.get_ncomponents()); assert_eq!(dst.len(), self.w2 * self.h2 * self.pix_fmt.get_ncomponents()); self.sample_rows(src, src_stride); self.sample_cols(dst) } } /// Create a new resizer instance. Alias for `Resizer::new`. pub fn new(src_width: usize, src_height: usize, dest_width: usize, dest_height: usize, pixel_format: Format, filter_type: Type) -> Resizer { Resizer::new(src_width, src_height, dest_width, dest_height, pixel_format, filter_type) } /// Resize image data to the new dimension in a single step. /// /// **NOTE:** If you need to resize to the same dimension multiple times, /// consider creating an resizer instance since it's faster. #[deprecated(note="Use resize::new().resize()")] #[allow(deprecated)] pub fn resize( src_width: usize, src_height: usize, dest_width: usize, dest_height: usize, pixel_format: Format, filter_type: Type, src: &[Format::Subpixel], dst: &mut [Format::Subpixel], ) { Resizer::new(src_width, src_height, dest_width, dest_height, pixel_format, filter_type).resize(src, dst) } #[test] fn pixel_sizes() { assert_eq!(Pixel::RGB24.get_ncomponents(), 3); assert_eq!(Pixel::RGB24.get_size(), 3 * 1); assert_eq!(Pixel::RGBA.get_size(), 4 * 1); assert_eq!(Pixel::RGB48.get_ncomponents(), 3); assert_eq!(Pixel::RGB48.get_size(), 3 * 2); assert_eq!(Pixel::RGBA64.get_ncomponents(), 4); assert_eq!(Pixel::RGBA64.get_size(), 4 * 2); } #[test] fn resize_stride() { let mut r = new(2, 2, 3, 4, Pixel::Gray16, Type::Triangle); let mut dst = vec![0; 12]; r.resize_stride(&[ 65535,65535,1,2, 65535,65535,3,4, ], 4, &mut dst); assert_eq!(&dst, &[65535; 12]); } resize-0.4.3/src/px.rs010064400007660000024000000071231371476027500130420ustar0000000000000000use std::mem; use crate::Pixel; /// See `Pixel` pub trait PixelFormat { /// Array to hold temporary values. type Accumulator: AsRef<[f32]> + AsMut<[f32]>; /// Type of a Subpixel of each pixel (8 or 16 bits). type Subpixel: Copy; /// New empty Accumulator. fn new_accum() -> Self::Accumulator; /// Convert float to integer value in range appropriate for this pixel format. fn into_subpixel(v: f32) -> Self::Subpixel; /// Convert pixel component to float fn from_subpixel(v: &Self::Subpixel) -> f32; /// Size of one pixel in that format in bytes. #[inline(always)] fn get_size(&self) -> usize { self.get_ncomponents() * mem::size_of::() } /// Return number of components of that format. #[inline(always)] fn get_ncomponents(&self) -> usize { Self::new_accum().as_ref().len() } } impl PixelFormat for Pixel::Gray8 { type Accumulator = [f32; 1]; type Subpixel = u8; #[must_use] #[inline(always)] fn new_accum() -> Self::Accumulator { [0.0; 1] } #[must_use] #[inline(always)] fn into_subpixel(v: f32) -> Self::Subpixel { pack_u8(v) } #[inline(always)] fn from_subpixel(px: &Self::Subpixel) -> f32 { *px as f32 } } impl PixelFormat for Pixel::Gray16 { type Accumulator = [f32; 1]; type Subpixel = u16; #[must_use] #[inline(always)] fn new_accum() -> Self::Accumulator { [0.0; 1] } #[must_use] #[inline(always)] fn into_subpixel(v: f32) -> Self::Subpixel { pack_u16(v) } #[inline(always)] fn from_subpixel(px: &Self::Subpixel) -> f32 { *px as f32 } } impl PixelFormat for Pixel::RGB24 { type Accumulator = [f32; 3]; type Subpixel = u8; #[must_use] #[inline(always)] fn new_accum() -> Self::Accumulator { [0.0; 3] } #[must_use] #[inline(always)] fn into_subpixel(v: f32) -> Self::Subpixel { pack_u8(v) } #[inline(always)] fn from_subpixel(px: &Self::Subpixel) -> f32 { *px as f32 } } impl PixelFormat for Pixel::RGBA { type Accumulator = [f32; 4]; type Subpixel = u8; #[must_use] #[inline(always)] fn new_accum() -> Self::Accumulator { [0.0; 4] } #[must_use] #[inline(always)] fn into_subpixel(v: f32) -> Self::Subpixel { pack_u8(v) } #[inline(always)] fn from_subpixel(px: &Self::Subpixel) -> f32 { *px as f32 } } impl PixelFormat for Pixel::RGB48 { type Accumulator = [f32; 3]; type Subpixel = u16; #[must_use] #[inline(always)] fn new_accum() -> Self::Accumulator { [0.0; 3] } #[must_use] #[inline(always)] fn into_subpixel(v: f32) -> Self::Subpixel { pack_u16(v) } #[inline(always)] fn from_subpixel(px: &Self::Subpixel) -> f32 { *px as f32 } } impl PixelFormat for Pixel::RGBA64 { type Accumulator = [f32; 4]; type Subpixel = u16; #[must_use] #[inline(always)] fn new_accum() -> Self::Accumulator { [0.0; 4] } #[must_use] #[inline(always)] fn into_subpixel(v: f32) -> Self::Subpixel { pack_u16(v) } #[inline(always)] fn from_subpixel(px: &Self::Subpixel) -> f32 { *px as f32 } } #[inline] fn pack_u8(v: f32) -> u8 { if v > 255.0 { 255 } else if v < 0.0 { 0 } else { v.round() as u8 } } #[inline] fn pack_u16(v: f32) -> u16 { if v > 65535.0 { 65535 } else if v < 0.0 { 0 } else { v.round() as u16 } }