Skip to content

Commit 8eac5a5

Browse files
authored
WebP support in resize_image (getzola#1360)
* Removing unused webpl * Adding clarification comment * Updating documentation * Adding webp
1 parent d4db249 commit 8eac5a5

File tree

5 files changed

+77
-20
lines changed

5 files changed

+77
-20
lines changed

Cargo.lock

+34-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

components/imageproc/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ regex = "1.0"
1010
tera = "1"
1111
image = "0.23"
1212
rayon = "1"
13+
webp="0.1.1"
1314

1415
errors = { path = "../errors" }
1516
utils = { path = "../utils" }

components/imageproc/src/lib.rs

+35-12
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
use std::collections::hash_map::DefaultHasher;
1+
use std::{collections::hash_map::DefaultHasher, io::Write};
22
use std::collections::hash_map::Entry as HEntry;
33
use std::collections::HashMap;
44
use std::fs::{self, File};
55
use std::hash::{Hash, Hasher};
66
use std::path::{Path, PathBuf};
77

8-
use image::imageops::FilterType;
8+
use image::{EncodableLayout, imageops::FilterType};
99
use image::{GenericImageView, ImageOutputFormat};
1010
use lazy_static::lazy_static;
1111
use rayon::prelude::*;
@@ -18,7 +18,7 @@ static RESIZED_SUBDIR: &str = "processed_images";
1818

1919
lazy_static! {
2020
pub static ref RESIZED_FILENAME: Regex =
21-
Regex::new(r#"([0-9a-f]{16})([0-9a-f]{2})[.](jpg|png)"#).unwrap();
21+
Regex::new(r#"([0-9a-f]{16})([0-9a-f]{2})[.](jpg|png|webp)"#).unwrap();
2222
}
2323

2424
/// Describes the precise kind of a resize operation
@@ -132,6 +132,7 @@ impl Hash for ResizeOp {
132132
}
133133
}
134134
}
135+
const DEFAULT_Q_JPG: u8 = 75;
135136

136137
/// Thumbnail image format
137138
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -140,22 +141,26 @@ pub enum Format {
140141
Jpeg(u8),
141142
/// PNG
142143
Png,
144+
/// WebP, The `u8` argument is WebP quality (in percent), None meaning lossless.
145+
WebP(Option<u8>),
143146
}
144147

145148
impl Format {
146-
pub fn from_args(source: &str, format: &str, quality: u8) -> Result<Format> {
149+
pub fn from_args(source: &str, format: &str, quality: Option<u8>) -> Result<Format> {
147150
use Format::*;
148-
149-
assert!(quality > 0 && quality <= 100, "Jpeg quality must be within the range [1; 100]");
150-
151+
if let Some(quality) = quality {
152+
assert!(quality > 0 && quality <= 100, "Quality must be within the range [1; 100]");
153+
}
154+
let jpg_quality = quality.unwrap_or(DEFAULT_Q_JPG);
151155
match format {
152156
"auto" => match Self::is_lossy(source) {
153-
Some(true) => Ok(Jpeg(quality)),
157+
Some(true) => Ok(Jpeg(jpg_quality)),
154158
Some(false) => Ok(Png),
155159
None => Err(format!("Unsupported image file: {}", source).into()),
156160
},
157-
"jpeg" | "jpg" => Ok(Jpeg(quality)),
158-
"png" => Ok(Png),
161+
"jpeg" | "jpg" => Ok(Jpeg(jpg_quality)),
162+
"png" => Ok(Png),
163+
"webp" => Ok(WebP(quality)),
159164
_ => Err(format!("Invalid image format: {}", format).into()),
160165
}
161166
}
@@ -170,6 +175,8 @@ impl Format {
170175
"png" => Some(false),
171176
"gif" => Some(false),
172177
"bmp" => Some(false),
178+
// It is assumed that webp is lossless, but it can be both
179+
"webp" => Some(false),
173180
_ => None,
174181
})
175182
.unwrap_or(None)
@@ -182,6 +189,7 @@ impl Format {
182189
match *self {
183190
Png => "png",
184191
Jpeg(_) => "jpg",
192+
WebP(_) => "webp"
185193
}
186194
}
187195
}
@@ -193,7 +201,9 @@ impl Hash for Format {
193201

194202
let q = match *self {
195203
Png => 0,
196-
Jpeg(q) => q,
204+
Jpeg(q) => q,
205+
WebP(None) => 0,
206+
WebP(Some(q)) => q
197207
};
198208

199209
hasher.write_u8(q);
@@ -232,7 +242,7 @@ impl ImageOp {
232242
width: Option<u32>,
233243
height: Option<u32>,
234244
format: &str,
235-
quality: u8,
245+
quality: Option<u8>,
236246
) -> Result<ImageOp> {
237247
let op = ResizeOp::from_args(op, width, height)?;
238248
let format = Format::from_args(&source, format, quality)?;
@@ -303,6 +313,19 @@ impl ImageOp {
303313
Format::Jpeg(q) => {
304314
img.write_to(&mut f, ImageOutputFormat::Jpeg(q))?;
305315
}
316+
Format::WebP(q) => {
317+
let encoder = webp::Encoder::from_image(&img);
318+
let memory = match q {
319+
Some(q) => {
320+
encoder.encode(q as f32 / 100.)
321+
}
322+
None => {
323+
encoder.encode_lossless()
324+
}
325+
};
326+
let mut bytes = memory.as_bytes();
327+
f.write_all(&mut bytes)?;
328+
}
306329
}
307330

308331
Ok(())

components/templates/src/global_fns/mod.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,6 @@ impl ResizeImage {
221221

222222
static DEFAULT_OP: &str = "fill";
223223
static DEFAULT_FMT: &str = "auto";
224-
const DEFAULT_Q: u8 = 75;
225224

226225
impl TeraFn for ResizeImage {
227226
fn call(&self, args: &HashMap<String, Value>) -> Result<Value> {
@@ -248,10 +247,11 @@ impl TeraFn for ResizeImage {
248247
.unwrap_or_else(|| DEFAULT_FMT.to_string());
249248

250249
let quality =
251-
optional_arg!(u8, args.get("quality"), "`resize_image`: `quality` must be a number")
252-
.unwrap_or(DEFAULT_Q);
253-
if quality == 0 || quality > 100 {
254-
return Err("`resize_image`: `quality` must be in range 1-100".to_string().into());
250+
optional_arg!(u8, args.get("quality"), "`resize_image`: `quality` must be a number");
251+
if let Some(quality) = quality {
252+
if quality == 0 || quality > 100 {
253+
return Err("`resize_image`: `quality` must be in range 1-100".to_string().into());
254+
}
255255
}
256256

257257
let mut imageproc = self.imageproc.lock().unwrap();

docs/content/documentation/content/image-processing/index.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@ resize_image(path, width, height, op, format, quality)
2828
- `"auto"`
2929
- `"jpg"`
3030
- `"png"`
31+
- `"webp"`
3132

3233
The default is `"auto"`, this means that the format is chosen based on input image format.
3334
JPEG is chosen for JPEGs and other lossy formats, and PNG is chosen for PNGs and other lossless formats.
34-
- `quality` (_optional_): JPEG quality of the resized image, in percent. Only used when encoding JPEGs; default value is `75`.
35+
- `quality` (_optional_): JPEG or WebP quality of the resized image, in percent. Only used when encoding JPEGs or WebPs; for JPEG default value is `75`, for WebP default is lossless.
3536

3637
### Image processing and return value
3738

0 commit comments

Comments
 (0)