From 85f20bbe0edcfc861479510ca824425d4a64ffbe Mon Sep 17 00:00:00 2001 From: asonix Date: Mon, 26 Sep 2022 18:14:25 -0500 Subject: [PATCH] Add new resize options, update README with current APIs --- README.md | 23 ++++++++- src/processor.rs | 120 ++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 136 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2b0f5f1..56aca5b 100644 --- a/README.md +++ b/README.md @@ -306,6 +306,19 @@ pict-rs offers the following endpoints: square using raw pixel sampling - `resize={int}`: produce a thumbnail of the image fitting inside an `{int}` by `{int}` square using a Lanczos2 filter. This is slower than sampling but looks a bit better in some cases + - `resize={filter}.(a){int}`: produce a thumbnail of the image fitting inside an `{int}` by + `{int}` square, or when `(a)` is present, produce a thumbnail whose area is smaller than + `{int}`. `{filter}` is optional, and indicates what filter to use when resizing the image. + Available filters are `Lanczos`, `Lanczos2`, `LanczosSharp`, `Lanczos2Sharp`, `Mitchell`, + and `RobidouxSharp`. + + Examples: + - `resize=300`: Produce an image fitting inside a 300x300 px square + - `reizie=.a10000`: Produce an image whose area is at most 10000 px + - `resize=Mitchell.200`: Produce an image fitting inside a 200x200 px square using the + Mitchell filter + - `resize=RobidouxSharp.a40000`: Produce an image whose area is at most 40000 px using the + RobidouxSharp filter - `crop={int-w}x{int-h}`: produce a cropped version of the image with an `{int-w}` by `{int-h}` aspect ratio. The resulting crop will be centered on the image. Either the width or height of the image will remain full-size, depending on the image's aspect ratio and the requested @@ -344,12 +357,20 @@ A secure API key can be generated by any password generator. } ``` - `GET /internal/aliases?alias={alias}` Get the aliases for a file by it's alias - - `?alias={alias}` get aliases by alias This endpiont returns the same JSON as the purge endpoint - `DELETE /internal/variants` Queue a cleanup for generated variants of uploaded images. If any of the cleaned variants are fetched again, they will be re-generated. +- `GET /internal/identifier` Get the image identifier (file path or object path) for a given alias + + On success, the returned json should look like this: + ```json + { + "msg": "ok", + "identifier": "/path/to/object" + } + ``` Additionally, all endpoints support setting deadlines, after which the request will cease processing. To enable deadlines for your requests, you can set the `X-Request-Deadline` header to an diff --git a/src/processor.rs b/src/processor.rs index 9e264ec..97c43f9 100644 --- a/src/processor.rs +++ b/src/processor.rs @@ -15,10 +15,79 @@ pub(crate) trait Processor { pub(crate) struct Identity; pub(crate) struct Thumbnail(usize); -pub(crate) struct Resize(usize); +pub(crate) struct Resize { + filter: Option, + kind: ResizeKind, +} +#[derive(Copy, Clone)] +pub(crate) enum ResizeFilter { + Lanczos, + LanczosSharp, + Lanczos2, + Lanczos2Sharp, + Mitchell, + RobidouxSharp, +} +#[derive(Copy, Clone)] +pub(crate) enum ResizeKind { + Bounds(usize), + Area(usize), +} pub(crate) struct Crop(usize, usize); pub(crate) struct Blur(f64); +impl ResizeFilter { + fn from_str(s: &str) -> Option { + match s.to_lowercase().as_str() { + "lanczos" => Some(Self::Lanczos), + "lanczossharp" => Some(Self::LanczosSharp), + "lanczos2" => Some(Self::Lanczos2), + "lanczos2sharp" => Some(Self::Lanczos2Sharp), + "mitchell" => Some(Self::Mitchell), + "robidouxsharp" => Some(Self::RobidouxSharp), + _ => None, + } + } + + fn to_magick_str(self) -> &'static str { + match self { + Self::Lanczos => "Lanczos", + Self::LanczosSharp => "LanczosSharp", + Self::Lanczos2 => "Lanczos2", + Self::Lanczos2Sharp => "Lanczos2Sharp", + Self::Mitchell => "Mitchell", + Self::RobidouxSharp => "RobidouxSharp", + } + } +} + +impl Default for ResizeFilter { + fn default() -> Self { + Self::Lanczos2 + } +} + +impl ResizeKind { + fn from_str(s: &str) -> Option { + let kind = if s.starts_with('a') { + let size = s.trim_start_matches('a').parse().ok()?; + ResizeKind::Area(size) + } else { + let size = s.parse().ok()?; + ResizeKind::Bounds(size) + }; + + Some(kind) + } + + fn to_magick_string(self) -> String { + match self { + Self::Area(size) => format!("{}@>", size), + Self::Bounds(size) => format!("{}x{}>", size, size), + } + } +} + #[instrument] pub(crate) fn build_chain( args: &[(String, String)], @@ -111,22 +180,61 @@ impl Processor for Resize { where Self: Sized, { - let size = v.parse().ok()?; - Some(Resize(size)) + if let Some((first, second)) = v.split_once('.') { + let filter = ResizeFilter::from_str(first); + + let kind = ResizeKind::from_str(second)?; + + Some(Resize { filter, kind }) + } else { + let size = v.parse().ok()?; + Some(Resize { + filter: None, + kind: ResizeKind::Bounds(size), + }) + } } fn path(&self, mut path: PathBuf) -> PathBuf { path.push(Self::NAME); - path.push(self.0.to_string()); + match self { + Resize { + filter: None, + kind: ResizeKind::Bounds(size), + } => { + path.push(size.to_string()); + } + Resize { + filter: None, + kind: ResizeKind::Area(size), + } => { + let node = format!(".a{}", size); + path.push(node); + } + Resize { + filter: Some(filter), + kind: ResizeKind::Bounds(size), + } => { + let node = format!("{}.{}", filter.to_magick_str(), size); + path.push(node); + } + Resize { + filter: Some(filter), + kind: ResizeKind::Area(size), + } => { + let node = format!("{}.a{}", filter.to_magick_str(), size); + path.push(node); + } + } path } fn command(&self, mut args: Vec) -> Vec { args.extend([ "-filter".to_string(), - "Lanczos2".to_string(), + self.filter.unwrap_or_default().to_magick_str().to_string(), "-resize".to_string(), - format!("{}x{}>", self.0, self.0), + self.kind.to_magick_string(), ]); args