Skip to content

Commit

Permalink
Configurable depth
Browse files Browse the repository at this point in the history
  • Loading branch information
kornelski committed May 9, 2023
1 parent aff2bb2 commit 01541ae
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 33 deletions.
8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "cavif"
description = "Encodes images in AVIF format (image2avif converter) using a pure-Rust encoder."
version = "1.5.2"
version = "1.5.3"
authors = ["Kornel Lesiński <[email protected]>"]
edition = "2021"
license = "BSD-3-Clause"
Expand All @@ -15,11 +15,11 @@ rust-version = "1.60"

[dependencies]
ravif = { version = "0.11.1", path = "./ravif", default-features = false }
rayon = "1.6.0"
rgb = "0.8.34"
rayon = "1.7.0"
rgb = "0.8.36"
cocoa_image = { version = "1.0.5", optional = true }
imgref = "1.9.4"
clap = { version = "4.1.8", default-features = false, features = ["color", "suggestions", "wrap_help", "std", "cargo"] }
clap = { version = "4.2.7", default-features = false, features = ["color", "suggestions", "wrap_help", "std", "cargo"] }
load_image = "3.0.3"

[features]
Expand Down
102 changes: 74 additions & 28 deletions ravif/src/av1encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ pub struct Encoder {
threads: Option<usize>,
/// [`AlphaColorMode`]
alpha_color_mode: AlphaColorMode,
/// 8 or 10
depth: Option<u8>,
}

/// Builder methods
Expand All @@ -78,6 +80,7 @@ impl Encoder {
quantizer: quality_to_quantizer(80.),
alpha_quantizer: quality_to_quantizer(80.),
speed: 5,
depth: None,
premultiplied_alpha: false,
color_space: ColorSpace::YCbCr,
threads: None,
Expand All @@ -95,6 +98,16 @@ impl Encoder {
self
}

/// Depth 8 or 10. `None` picks automatically.
#[inline(always)]
#[track_caller]
#[must_use]
pub fn with_depth(mut self, depth: Option<u8>) -> Self {
assert!(depth.map_or(true, |d| d == 8 || d == 10));
self.depth = depth;
self
}

/// Quality for the alpha channel only. `1..=100`. Panics if out of range.
#[inline(always)]
#[track_caller]
Expand Down Expand Up @@ -181,23 +194,39 @@ impl Encoder {

let width = buffer.width();
let height = buffer.height();
let planes = buffer.pixels().map(|px| {
let (y,u,v) = match self.color_space {
ColorSpace::YCbCr => {
rgb_to_8_bit_ycbcr(px.rgb(), BT601)
},
ColorSpace::RGB => {
rgb_to_8_bit_gbr(px.rgb())
},
};
[y, u, v]
});
let alpha = buffer.pixels().map(|px| px.a);
let matrix_coefficients = match self.color_space {
ColorSpace::YCbCr => MatrixCoefficients::BT601,
ColorSpace::RGB => MatrixCoefficients::Identity,
};
self.encode_raw_planes_8_bit(width, height, planes, Some(alpha), PixelRange::Full, matrix_coefficients)
if self.depth == Some(10) {
let planes = buffer.pixels().map(|px| {
let (y,u,v) = match self.color_space {
ColorSpace::YCbCr => {
rgb_to_10_bit_ycbcr(px.rgb(), BT601)
},
ColorSpace::RGB => {
rgb_to_10_bit_gbr(px.rgb())
},
};
[y, u, v]
});
let alpha = buffer.pixels().map(|px| to_ten(px.a));
self.encode_raw_planes_10_bit(width, height, planes, Some(alpha), PixelRange::Full, matrix_coefficients)
} else {
let planes = buffer.pixels().map(|px| {
let (y,u,v) = match self.color_space {
ColorSpace::YCbCr => {
rgb_to_8_bit_ycbcr(px.rgb(), BT601)
},
ColorSpace::RGB => {
rgb_to_8_bit_gbr(px.rgb())
},
};
[y, u, v]
});
let alpha = buffer.pixels().map(|px| px.a);
self.encode_raw_planes_8_bit(width, height, planes, Some(alpha), PixelRange::Full, matrix_coefficients)
}
}

fn convert_alpha(&self, in_buffer: Img<&[RGBA8]>) -> Option<ImgVec<RGBA8>> {
Expand Down Expand Up @@ -243,22 +272,37 @@ pub fn encode_rgb(&self, buffer: Img<&[RGB8]>) -> Result<EncodedImage, Error> {
}

fn encode_rgb_internal(&self, width: usize, height: usize, pixels: impl Iterator<Item = RGB8> + Send + Sync) -> Result<EncodedImage, Error> {
let planes = pixels.map(|px| {
let (y,u,v) = match self.color_space {
ColorSpace::YCbCr => {
rgb_to_10_bit_ycbcr(px, BT601)
},
ColorSpace::RGB => {
rgb_to_10_bit_gbr(px)
},
};
[y, u, v]
});
let matrix_coefficients = match self.color_space {
ColorSpace::YCbCr => MatrixCoefficients::BT601,
ColorSpace::RGB => MatrixCoefficients::Identity,
};
self.encode_raw_planes_10_bit(width, height, planes, None::<[_; 0]>, PixelRange::Full, matrix_coefficients)
if self.depth == Some(8) {
let planes = pixels.map(|px| {
let (y,u,v) = match self.color_space {
ColorSpace::YCbCr => {
rgb_to_8_bit_ycbcr(px, BT601)
},
ColorSpace::RGB => {
rgb_to_8_bit_gbr(px)
},
};
[y, u, v]
});
self.encode_raw_planes_8_bit(width, height, planes, None::<[_; 0]>, PixelRange::Full, matrix_coefficients)
} else {
let planes = pixels.map(|px| {
let (y,u,v) = match self.color_space {
ColorSpace::YCbCr => {
rgb_to_10_bit_ycbcr(px, BT601)
},
ColorSpace::RGB => {
rgb_to_10_bit_gbr(px)
},
};
[y, u, v]
});
self.encode_raw_planes_10_bit(width, height, planes, None::<[_; 0]>, PixelRange::Full, matrix_coefficients)
}
}

/// Encodes AVIF from 3 planar channels that are in the color space described by `matrix_coefficients`,
Expand Down Expand Up @@ -349,11 +393,13 @@ fn encode_raw_planes<P: rav1e::Pixel + Default>(&self, width: usize, height: usi
}
}

#[inline(always)]
fn to_ten(x: u8) -> u16 {
((x as u16) << 2) | ((x as u16) >> 6)
}

#[inline(always)]
fn rgb_to_10_bit_gbr(px: rgb::RGB<u8>) -> (u16, u16, u16) {
fn to_ten(x: u8) -> u16 {
((x as u16) << 2) | ((x as u16) >> 6)
}
(to_ten(px.g), to_ten(px.b), to_ten(px.r))
}

Expand Down
14 changes: 13 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use clap::ArgAction;
use clap::builder::ValueParser;
use clap::value_parser;
use load_image::export::rgb::ComponentMap;
use clap::{Arg, Command};
Expand Down Expand Up @@ -102,6 +101,11 @@ fn run() -> Result<(), BoxError> {
.default_value("ycbcr")
.value_parser(["ycbcr", "rgb"])
.help("Internal AVIF color space. YCbCr works better for human eyes."))
.arg(Arg::new("depth")
.long("depth")
.default_value("auto")
.value_parser(["8", "10", "auto"])
.help("Write 8-bit (more compatible) or 10-bit (better quality) images"))
.arg(Arg::new("IMAGES")
.index(1)
.num_args(1..)
Expand All @@ -128,6 +132,13 @@ fn run() -> Result<(), BoxError> {
"rgb" => ColorSpace::RGB,
x => Err(format!("bad color type: {x}"))?,
};

let depth = match args.get_one::<String>("depth").expect("default").as_str() {
"8" => Some(8),
"10" => Some(10),
_ => None,
};

let files = args.get_many::<PathBuf>("IMAGES").ok_or("Please specify image paths to convert")?;
let files: Vec<_> = files
.filter(|pathstr| {
Expand Down Expand Up @@ -196,6 +207,7 @@ fn run() -> Result<(), BoxError> {
}
let enc = Encoder::new()
.with_quality(quality)
.with_depth(depth)
.with_speed(speed)
.with_alpha_quality(alpha_quality)
.with_internal_color_space(color_space)
Expand Down

0 comments on commit 01541ae

Please sign in to comment.