From cf3ad87360b153360f2a20edea28ca747968f908 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 6 Nov 2021 22:31:58 +0100 Subject: [PATCH] Initial version with image thumbnails Signed-off-by: trivernis --- .gitignore | 2 + .idea/.gitignore | 8 ++++ .idea/discord.xml | 7 ++++ .idea/modules.xml | 8 ++++ .idea/thumbnailer.iml | 12 ++++++ .idea/vcs.xml | 6 +++ Cargo.toml | 15 +++++++ src/error.rs | 20 +++++++++ src/formats/image_format.rs | 47 +++++++++++++++++++++ src/formats/mod.rs | 15 +++++++ src/lib.rs | 63 ++++++++++++++++++++++++++++ src/size.rs | 24 +++++++++++ tests/assets/test.jpg | Bin 0 -> 33406 bytes tests/assets/test.png | Bin 0 -> 18118 bytes tests/assets/test.webp | Bin 0 -> 6578 bytes tests/image_reading.rs | 79 +++++++++++++++++++++++++++++++++++ tests/image_writing.rs | 81 ++++++++++++++++++++++++++++++++++++ 17 files changed, 387 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/discord.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/thumbnailer.iml create mode 100644 .idea/vcs.xml create mode 100644 Cargo.toml create mode 100644 src/error.rs create mode 100644 src/formats/image_format.rs create mode 100644 src/formats/mod.rs create mode 100644 src/lib.rs create mode 100644 src/size.rs create mode 100644 tests/assets/test.jpg create mode 100644 tests/assets/test.png create mode 100644 tests/assets/test.webp create mode 100644 tests/image_reading.rs create mode 100644 tests/image_writing.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/discord.xml b/.idea/discord.xml new file mode 100644 index 0000000..30bab2a --- /dev/null +++ b/.idea/discord.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..cb3dd20 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/thumbnailer.iml b/.idea/thumbnailer.iml new file mode 100644 index 0000000..457e3e6 --- /dev/null +++ b/.idea/thumbnailer.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e3a2c52 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "thumbnailer" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +webp = "0.2.0" +mime = "0.3.16" +thiserror = "1.0.30" +rayon = "1.5.1" + +[dependencies.image] +version = "0.23.14" \ No newline at end of file diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..1bf2a8d --- /dev/null +++ b/src/error.rs @@ -0,0 +1,20 @@ +use std::io; +use mime::Mime; +use thiserror::Error; + +pub type ThumbResult = Result; + +#[derive(Debug, Error)] +pub enum ThumbError { + #[error("IO Error {0}")] + IO(#[from] io::Error), + + #[error("Image Error {0}")] + Image(#[from] image::error::ImageError), + + #[error("Failed to decode image")] + Decode, + + #[error("Unsupported media type {0}")] + Unsupported(Mime), +} \ No newline at end of file diff --git a/src/formats/image_format.rs b/src/formats/image_format.rs new file mode 100644 index 0000000..47cb672 --- /dev/null +++ b/src/formats/image_format.rs @@ -0,0 +1,47 @@ +use std::io::{BufRead, Read, Seek}; +use image::{DynamicImage, ImageFormat}; +use mime::Mime; +use image::io::Reader as ImageReader; +use webp::Decoder as WebpDecoder; +use crate::error::{ThumbError, ThumbResult}; + +const IMAGE_WEBP_MIME: &str = "image/webp"; + +/// Reads an image with a known mime type +pub fn read_image(reader: R, mime: Mime) -> ThumbResult { + match mime.essence_str() { + IMAGE_WEBP_MIME => read_webp_image(reader), + _ => read_generic_image(reader, mime_to_image_format(mime)), + } +} + +/// Reads a webp image +fn read_webp_image(mut reader: R) -> ThumbResult { + let mut buf = Vec::new(); + reader.read_to_end(&mut buf)?; + let webp_image = WebpDecoder::new(&buf).decode().ok_or_else(|| ThumbError::Decode)?; + + Ok(webp_image.to_image()) +} + +/// Reads a generic image +fn read_generic_image(reader: R, format: Option) -> ThumbResult { + let reader = if let Some(format) = format { + ImageReader::with_format(reader, format) + } else { + ImageReader::new(reader).with_guessed_format()? + }; + let image = reader.decode()?; + + Ok(image) +} + +fn mime_to_image_format(mime: Mime) -> Option { + match mime.subtype().as_str() { + "png" => Some(ImageFormat::Png), + "jpeg" => Some(ImageFormat::Jpeg), + "bmp" => Some(ImageFormat::Bmp), + "gif" => Some(ImageFormat::Gif), + _ => None, + } +} \ No newline at end of file diff --git a/src/formats/mod.rs b/src/formats/mod.rs new file mode 100644 index 0000000..1e77a97 --- /dev/null +++ b/src/formats/mod.rs @@ -0,0 +1,15 @@ +use crate::error::{ThumbError, ThumbResult}; +use crate::formats::image_format::read_image; +use image::DynamicImage; +use mime::Mime; +use std::io::{BufRead, Seek}; + +pub mod image_format; + +/// Reads the buffer content into an image that can be used for thumbnail generation +pub fn get_base_image(reader: R, mime: Mime) -> ThumbResult { + match mime.type_() { + mime::IMAGE => read_image(reader, mime), + _ => Err(ThumbError::Unsupported(mime)), + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..c6ba972 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,63 @@ +use crate::error::ThumbResult; +use image; +use image::imageops::FilterType; +use image::{DynamicImage, ImageOutputFormat}; +use mime::Mime; +use rayon::prelude::*; +use std::io::{BufRead, Seek, Write}; + +use crate::formats::get_base_image; +pub use size::ThumbnailSize; + +pub mod error; +mod formats; +mod size; + +#[derive(Clone, Debug)] +pub struct Thumbnail { + inner: DynamicImage, +} + +impl Thumbnail { + /// Writes the bytes of the image in a png format + pub fn write_png(self, writer: &mut W) -> ThumbResult<()> { + self.inner.write_to(writer, ImageOutputFormat::Png)?; + + Ok(()) + } + + /// Writes the bytes of the image in a jpeg format + pub fn write_jpeg(self, writer: &mut W, compression: u8) -> ThumbResult<()> { + self.inner + .write_to(writer, ImageOutputFormat::Jpeg(compression))?; + + Ok(()) + } +} + +/// Creates thumbnails of the requested sizes for the given reader providing the content as bytes and +/// the mime describing the contents type +pub fn create_thumbnails>( + reader: R, + mime: Mime, + sizes: I, +) -> ThumbResult> { + let image = get_base_image(reader, mime)?; + let sizes: Vec = sizes.into_iter().collect(); + let thumbnails = resize_images(image, &sizes) + .into_iter() + .map(|image| Thumbnail { inner: image }) + .collect(); + + Ok(thumbnails) +} + +fn resize_images(image: DynamicImage, sizes: &[ThumbnailSize]) -> Vec { + sizes + .into_par_iter() + .map(|size| { + let (width, height) = size.dimensions(); + image.resize(width, height, FilterType::Nearest) + }) + .collect() +} diff --git a/src/size.rs b/src/size.rs new file mode 100644 index 0000000..09cae04 --- /dev/null +++ b/src/size.rs @@ -0,0 +1,24 @@ + +/// Represents fixed sizes of a thumbnail +#[derive(Clone, Copy, Debug)] +pub enum ThumbnailSize { + Icon, + Small, + Medium, + Large, + Larger, + Custom((u32, u32)) +} + +impl ThumbnailSize { + pub fn dimensions(&self) -> (u32, u32) { + match self { + ThumbnailSize::Icon => (64, 64), + ThumbnailSize::Small => (128, 128), + ThumbnailSize::Medium => (256, 256), + ThumbnailSize::Large => (512, 512), + ThumbnailSize::Larger => (1024, 1024), + ThumbnailSize::Custom(size) => *size, + } + } +} \ No newline at end of file diff --git a/tests/assets/test.jpg b/tests/assets/test.jpg new file mode 100644 index 0000000000000000000000000000000000000000..93771b9cc3361100d43d64c29d3d5b5d0727a08c GIT binary patch literal 33406 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%0D61-H)z4gwB!Qkrw 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 literal 0 HcmV?d00001 diff --git a/tests/assets/test.png b/tests/assets/test.png new file mode 100644 index 0000000000000000000000000000000000000000..7f9aea1c1ff99368c59bf0ffb10adc648bc25e11 GIT binary patch literal 18118 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 literal 0 HcmV?d00001 diff --git a/tests/image_reading.rs b/tests/image_reading.rs new file mode 100644 index 0000000..bcd4fd4 --- /dev/null +++ b/tests/image_reading.rs @@ -0,0 +1,79 @@ +const PNG_BYTES: &'static [u8] = include_bytes!("assets/test.png"); +const JPG_BYTES: &'static [u8] = include_bytes!("assets/test.jpg"); +const WEBP_BYTES: &'static [u8] = include_bytes!("assets/test.webp"); + +use crate::ImageType::{Jpeg, Png, Webp}; +use mime::Mime; +use std::io::Cursor; +use std::str::FromStr; +use thumbnailer::error::ThumbResult; +use thumbnailer::{create_thumbnails, Thumbnail, ThumbnailSize}; + +enum ImageType { + Png, + Jpeg, + Webp, +} + +#[test] +fn it_creates_small_thumbnails_for_png() { + create_thumbnail(Png, ThumbnailSize::Small).unwrap(); +} + +#[test] +fn it_creates_medium_thumbnails_for_png() { + create_thumbnail(Png, ThumbnailSize::Medium).unwrap(); +} + +#[test] +fn it_creates_large_thumbnails_for_png() { + create_thumbnail(Png, ThumbnailSize::Large).unwrap(); +} + +#[test] +fn it_creates_small_thumbnails_for_jpeg() { + create_thumbnail(Jpeg, ThumbnailSize::Small).unwrap(); +} + +#[test] +fn it_creates_medium_thumbnails_for_jpeg() { + create_thumbnail(Jpeg, ThumbnailSize::Medium).unwrap(); +} + +#[test] +fn it_creates_large_thumbnails_for_jpeg() { + create_thumbnail(Jpeg, ThumbnailSize::Large).unwrap(); +} + +#[test] +fn it_creates_small_thumbnails_for_webp() { + create_thumbnail(Webp, ThumbnailSize::Small).unwrap(); +} + +#[test] +fn it_creates_medium_thumbnails_for_webp() { + create_thumbnail(Webp, ThumbnailSize::Medium).unwrap(); +} + +#[test] +fn it_creates_large_thumbnails_for_webp() { + create_thumbnail(Webp, ThumbnailSize::Large).unwrap(); +} + +fn create_thumbnail(image_type: ImageType, size: ThumbnailSize) -> ThumbResult> { + match image_type { + ImageType::Png => { + let reader = Cursor::new(PNG_BYTES); + create_thumbnails(reader, mime::IMAGE_PNG, [size]) + } + ImageType::Jpeg => { + let reader = Cursor::new(JPG_BYTES); + create_thumbnails(reader, mime::IMAGE_JPEG, [size]) + } + ImageType::Webp => { + let reader = Cursor::new(WEBP_BYTES); + let webp_mime = Mime::from_str("image/webp").unwrap(); + create_thumbnails(reader, webp_mime, [size]) + } + } +} diff --git a/tests/image_writing.rs b/tests/image_writing.rs new file mode 100644 index 0000000..4e8aa76 --- /dev/null +++ b/tests/image_writing.rs @@ -0,0 +1,81 @@ +use mime::Mime; +use std::io::Cursor; +use std::str::FromStr; +use thumbnailer::error::ThumbResult; +use thumbnailer::{create_thumbnails, ThumbnailSize}; + +const PNG_BYTES: &'static [u8] = include_bytes!("assets/test.png"); +const JPG_BYTES: &'static [u8] = include_bytes!("assets/test.jpg"); +const WEBP_BYTES: &'static [u8] = include_bytes!("assets/test.webp"); + +enum SourceFormat { + Png, + Jpeg, + Webp, +} + +enum TargetFormat { + Png, + Jpeg, +} + +#[test] +fn it_converts_png_thumbnails_for_png() { + write_thumbnail(SourceFormat::Png, TargetFormat::Png).unwrap(); +} + +#[test] +fn it_converts_jpeg_thumbnails_for_png() { + write_thumbnail(SourceFormat::Png, TargetFormat::Jpeg).unwrap(); +} + +#[test] +fn it_converts_png_thumbnails_for_jpeg() { + write_thumbnail(SourceFormat::Jpeg, TargetFormat::Png).unwrap(); +} + +#[test] +fn it_converts_jpeg_thumbnails_for_jpeg() { + write_thumbnail(SourceFormat::Jpeg, TargetFormat::Jpeg).unwrap(); +} + +#[test] +fn it_converts_png_thumbnails_for_webp() { + write_thumbnail(SourceFormat::Webp, TargetFormat::Png).unwrap(); +} + +#[test] +fn it_converts_jpeg_thumbnails_for_webp() { + write_thumbnail(SourceFormat::Webp, TargetFormat::Jpeg).unwrap(); +} + +fn write_thumbnail( + source_format: SourceFormat, + target_format: TargetFormat, +) -> ThumbResult> { + let thumb = match source_format { + SourceFormat::Png => { + let reader = Cursor::new(PNG_BYTES); + create_thumbnails(reader, mime::IMAGE_PNG, [ThumbnailSize::Medium]).unwrap() + } + SourceFormat::Jpeg => { + let reader = Cursor::new(JPG_BYTES); + create_thumbnails(reader, mime::IMAGE_JPEG, [ThumbnailSize::Medium]).unwrap() + } + SourceFormat::Webp => { + let reader = Cursor::new(WEBP_BYTES); + let webp_mime = Mime::from_str("image/webp").unwrap(); + create_thumbnails(reader, webp_mime, [ThumbnailSize::Medium]).unwrap() + } + } + .pop() + .unwrap(); + + let mut buf = Vec::new(); + match target_format { + TargetFormat::Png => thumb.write_png(&mut buf)?, + TargetFormat::Jpeg => thumb.write_jpeg(&mut buf, 8)?, + } + + Ok(buf) +}