Skip to content

Commit 3305f1d

Browse files
iWangJiaxiangKeats
authored andcommitted
Add AVIF support for resize_image() function (#2780)
* Add AVIF format in resize_image() function. * feat: Add avif support * feat: add AVIF decoding suppoty * fmt: fix cargo fmt error * feat: add support for rgb8 and rgba8 * feat: improve encoding speed * chore: Remove submodule for test * bug: fix quality to lossy and update doc
1 parent c8a1d69 commit 3305f1d

File tree

8 files changed

+63
-4
lines changed

8 files changed

+63
-4
lines changed

components/imageproc/src/format.rs

+6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ pub enum Format {
1212
Png,
1313
/// WebP, The `u8` argument is WebP quality (in percent), None meaning lossless.
1414
WebP(Option<u8>),
15+
/// AVIF, The `u8` argument is AVIF quality (in percent), None meaning lossless.
16+
Avif(Option<u8>),
1517
}
1618

1719
impl Format {
@@ -32,6 +34,7 @@ impl Format {
3234
"jpeg" | "jpg" => Ok(Jpeg(jpg_quality)),
3335
"png" => Ok(Png),
3436
"webp" => Ok(WebP(quality)),
37+
"avif" => Ok(Avif(quality)),
3538
_ => Err(anyhow!("Invalid image format: {}", format)),
3639
}
3740
}
@@ -44,6 +47,7 @@ impl Format {
4447
Png => "png",
4548
Jpeg(_) => "jpg",
4649
WebP(_) => "webp",
50+
Avif(_) => "avif",
4751
}
4852
}
4953
}
@@ -58,6 +62,8 @@ impl Hash for Format {
5862
Jpeg(q) => 1001 + q as u16,
5963
WebP(None) => 2000,
6064
WebP(Some(q)) => 2001 + q as u16,
65+
Avif(None) => 3000,
66+
Avif(Some(q)) => 3001 + q as u16,
6167
};
6268

6369
hasher.write_u16(q);

components/imageproc/src/meta.rs

+16
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
use errors::{anyhow, Context, Result};
2+
use libs::avif_parse::read_avif;
23
use libs::image::ImageReader;
34
use libs::image::{ImageFormat, ImageResult};
45
use libs::svg_metadata::Metadata as SvgMetadata;
56
use serde::Serialize;
67
use std::ffi::OsStr;
8+
use std::fs::File;
9+
use std::io::BufReader;
710
use std::path::Path;
811

912
/// Size and format read cheaply with `image`'s `Reader`.
@@ -44,6 +47,10 @@ impl ImageMetaResponse {
4447
pub fn new_svg(width: u32, height: u32) -> Self {
4548
Self { width, height, format: Some("svg"), mime: Some("text/svg+xml") }
4649
}
50+
51+
pub fn new_avif(width: u32, height: u32) -> Self {
52+
Self { width, height, format: Some("avif"), mime: Some("image/avif") }
53+
}
4754
}
4855

4956
impl From<ImageMeta> for ImageMetaResponse {
@@ -75,6 +82,15 @@ pub fn read_image_metadata<P: AsRef<Path>>(path: P) -> Result<ImageMetaResponse>
7582
// this is not a typo, this returns the correct values for width and height.
7683
.map(|(h, w)| ImageMetaResponse::new_svg(w as u32, h as u32))
7784
}
85+
"avif" => {
86+
let avif_data =
87+
read_avif(&mut BufReader::new(File::open(path)?)).with_context(err_context)?;
88+
let meta = avif_data.primary_item_metadata()?;
89+
return Ok(ImageMetaResponse::new_avif(
90+
meta.max_frame_width.get(),
91+
meta.max_frame_height.get(),
92+
));
93+
}
7894
_ => ImageMeta::read(path).map(ImageMetaResponse::from).with_context(err_context),
7995
}
8096
}

components/imageproc/src/processor.rs

+18-1
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ use std::path::{Path, PathBuf};
66
use config::Config;
77
use errors::{anyhow, Context, Result};
88
use libs::ahash::{HashMap, HashSet};
9+
use libs::image::codecs::avif::AvifEncoder;
910
use libs::image::codecs::jpeg::JpegEncoder;
1011
use libs::image::imageops::FilterType;
11-
use libs::image::{EncodableLayout, ImageFormat};
12+
use libs::image::GenericImageView;
13+
use libs::image::{EncodableLayout, ExtendedColorType, ImageEncoder, ImageFormat};
1214
use libs::rayon::prelude::*;
1315
use libs::{image, webp};
1416
use serde::{Deserialize, Serialize};
@@ -71,6 +73,21 @@ impl ImageOp {
7173
};
7274
buffered_f.write_all(memory.as_bytes())?;
7375
}
76+
Format::Avif(q) => {
77+
let mut avif: Vec<u8> = Vec::new();
78+
let color_type = match img.color().has_alpha() {
79+
true => ExtendedColorType::Rgba8,
80+
false => ExtendedColorType::Rgb8,
81+
};
82+
let encoder = AvifEncoder::new_with_speed_quality(&mut avif, 10, q.unwrap_or(70));
83+
encoder.write_image(
84+
&img.as_bytes(),
85+
img.dimensions().0,
86+
img.dimensions().1,
87+
color_type,
88+
)?;
89+
buffered_f.write_all(&avif.as_bytes())?;
90+
}
7491
}
7592

7693
Ok(())

components/imageproc/tests/resize_image.rs

+18
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,11 @@ fn resize_image_png_webp() {
127127
image_op_test("png.png", "scale", Some(150), Some(150), "webp", "webp", 150, 150, 300, 380);
128128
}
129129

130+
#[test]
131+
fn resize_image_png_avif() {
132+
image_op_test("png.png", "scale", Some(150), Some(150), "avif", "avif", 150, 150, 300, 380);
133+
}
134+
130135
#[test]
131136
fn resize_image_webp_jpg() {
132137
image_op_test("webp.webp", "scale", Some(150), Some(150), "auto", "jpg", 150, 150, 300, 380);
@@ -179,6 +184,19 @@ fn read_image_metadata_webp() {
179184
);
180185
}
181186

187+
#[test]
188+
fn read_image_metadata_avif() {
189+
assert_eq!(
190+
image_meta_test("avif.avif"),
191+
ImageMetaResponse {
192+
width: 300,
193+
height: 380,
194+
format: Some("avif"),
195+
mime: Some("image/avif")
196+
}
197+
);
198+
}
199+
182200
#[test]
183201
fn fix_orientation_test() {
184202
fn load_img_and_fix_orientation(img_name: &str) -> DynamicImage {
19.6 KB
Binary file not shown.

components/libs/Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ filetime = "0.2"
1414
gh-emoji = "1"
1515
glob = "0.3"
1616
globset = "0.4"
17-
image = "0.25"
17+
image = {version = "0.25", default-features = true, features = ["avif"]}
1818
lexical-sort = "0.3"
1919
minify-html = "0.15"
2020
nom-bibtex = "0.5"
@@ -44,7 +44,7 @@ unicode-segmentation = "1.2"
4444
url = "2"
4545
walkdir = "2"
4646
webp = "0.3"
47-
47+
avif-parse = "1.3.2"
4848

4949
[features]
5050
# TODO: fix me, it doesn't pick up the reqwuest feature if not set as default

components/libs/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
pub use ahash;
88
pub use ammonia;
99
pub use atty;
10+
pub use avif_parse;
1011
pub use base64;
1112
pub use csv;
1213
pub use elasticlunr;

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@ resize_image(path, width, height, op, format, quality)
3434
- `"jpg"`
3535
- `"png"`
3636
- `"webp"`
37+
- `"avif"`
3738

3839
The default is `"auto"`, this means that the format is chosen based on input image format.
3940
JPEG is chosen for JPEGs and other lossy formats, and PNG is chosen for PNGs and other lossless formats.
40-
- `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.
41+
- `quality` (_optional_): Quality of the resized image, in percent. Only used when encoding JPEGs, WebPs or AVIFs; for JPEG default value is `75`, for WebP default is lossless, for Avif default is `70`.
4142

4243
### Image processing and return value
4344

0 commit comments

Comments
 (0)