diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..a9967c0 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,12 @@ +name: Rust + +on: [push, pull_request] + +jobs: + formatting: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Format + run: cargo fmt --all -- --check diff --git a/ravif/src/av1encoder.rs b/ravif/src/av1encoder.rs index 0087a60..644e72b 100644 --- a/ravif/src/av1encoder.rs +++ b/ravif/src/av1encoder.rs @@ -1,13 +1,13 @@ #![allow(deprecated)] use crate::dirtyalpha::blurred_dirty_alpha; use crate::error::Error; +#[cfg(not(feature = "threading"))] +use crate::rayoff as rayon; use imgref::Img; use imgref::ImgVec; use rav1e::prelude::*; use rgb::RGB8; use rgb::RGBA8; -#[cfg(not(feature = "threading"))] -use crate::rayoff as rayon; /// For [`Encoder::with_internal_color_model`] #[derive(Debug, Copy, Clone, Eq, PartialEq)] @@ -112,7 +112,17 @@ impl Encoder { #[doc(hidden)] #[deprecated(note = "Renamed to with_bit_depth")] pub fn with_depth(self, depth: Option) -> Self { - self.with_bit_depth(depth.map(|d| if d >= 10 { BitDepth::Ten } else { BitDepth::Eight }).unwrap_or(BitDepth::Auto)) + self.with_bit_depth( + depth + .map(|d| { + if d >= 10 { + BitDepth::Ten + } else { + BitDepth::Eight + } + }) + .unwrap_or(BitDepth::Auto), + ) } /// Depth 8 or 10. @@ -184,7 +194,6 @@ impl Encoder { /// Once done with config, call one of the `encode_*` functions impl Encoder { - /// Make a new AVIF image from RGBA pixels (non-premultiplied, alpha last) /// /// Make the `Img` for the `buffer` like this: @@ -210,7 +219,11 @@ impl Encoder { let buffer = new_alpha.as_ref().map(|b| b.as_ref()).unwrap_or(in_buffer); let use_alpha = buffer.pixels().any(|px| px.a != 255); if !use_alpha { - return self.encode_rgb_internal(buffer.width(), buffer.height(), buffer.pixels().map(|px| px.rgb())) + return self.encode_rgb_internal( + buffer.width(), + buffer.height(), + buffer.pixels().map(|px| px.rgb()), + ); } let width = buffer.width(); @@ -262,44 +275,49 @@ impl Encoder { fn convert_alpha(&self, in_buffer: Img<&[RGBA8]>) -> Option> { match self.alpha_color_mode { AlphaColorMode::UnassociatedDirty => None, - AlphaColorMode::UnassociatedClean => { - blurred_dirty_alpha(in_buffer) - }, + AlphaColorMode::UnassociatedClean => blurred_dirty_alpha(in_buffer), AlphaColorMode::Premultiplied => { - let prem = in_buffer.pixels() + let prem = in_buffer + .pixels() .filter(|px| px.a != 255) - .map(|px| if px.a == 0 { RGBA8::default() } else { RGBA8::new( - (u16::from(px.r) * 255 / u16::from(px.a)) as u8, - (u16::from(px.g) * 255 / u16::from(px.a)) as u8, - (u16::from(px.b) * 255 / u16::from(px.a)) as u8, - px.a, - )}) + .map(|px| { + if px.a == 0 { + RGBA8::default() + } else { + RGBA8::new( + (u16::from(px.r) * 255 / u16::from(px.a)) as u8, + (u16::from(px.g) * 255 / u16::from(px.a)) as u8, + (u16::from(px.b) * 255 / u16::from(px.a)) as u8, + px.a, + ) + } + }) .collect(); Some(ImgVec::new(prem, in_buffer.width(), in_buffer.height())) - }, + } } } -/// Make a new AVIF image from RGB pixels -/// -/// Make the `Img` for the `buffer` like this: -/// -/// ```rust,ignore -/// Img::new(&pixels_rgb[..], width, height) -/// ``` -/// -/// If you have pixels as `u8` slice, then first do: -/// -/// ```rust,ignore -/// use rgb::ComponentSlice; -/// let pixels_rgb = pixels_u8.as_rgb(); -/// ``` -/// -/// returns AVIF file, size of color metadata -#[inline] -pub fn encode_rgb(&self, buffer: Img<&[RGB8]>) -> Result { - self.encode_rgb_internal(buffer.width(), buffer.height(), buffer.pixels()) -} + /// Make a new AVIF image from RGB pixels + /// + /// Make the `Img` for the `buffer` like this: + /// + /// ```rust,ignore + /// Img::new(&pixels_rgb[..], width, height) + /// ``` + /// + /// If you have pixels as `u8` slice, then first do: + /// + /// ```rust,ignore + /// use rgb::ComponentSlice; + /// let pixels_rgb = pixels_u8.as_rgb(); + /// ``` + /// + /// returns AVIF file, size of color metadata + #[inline] + pub fn encode_rgb(&self, buffer: Img<&[RGB8]>) -> Result { + self.encode_rgb_internal(buffer.width(), buffer.height(), buffer.pixels()) + } fn encode_rgb_internal( &self, @@ -350,93 +368,163 @@ pub fn encode_rgb(&self, buffer: Img<&[RGB8]>) -> Result { } } + /// Encodes AVIF from 3 planar channels that are in the color space described by `matrix_coefficients`, + /// with sRGB transfer characteristics and color primaries. + /// + /// Alpha always uses full range. Chroma subsampling is not supported, and it's a bad idea for AVIF anyway. + /// If there's no alpha, use `None::<[_; 0]>`. + /// + /// returns AVIF file, size of color metadata, size of alpha metadata overhead + #[inline] + pub fn encode_raw_planes_8_bit( + &self, + width: usize, + height: usize, + planes: impl IntoIterator + Send, + alpha: Option + Send>, + color_pixel_range: PixelRange, + matrix_coefficients: MatrixCoefficients, + ) -> Result { + self.encode_raw_planes( + width, + height, + planes, + alpha, + color_pixel_range, + matrix_coefficients, + 8, + ) + } -/// Encodes AVIF from 3 planar channels that are in the color space described by `matrix_coefficients`, -/// with sRGB transfer characteristics and color primaries. -/// -/// Alpha always uses full range. Chroma subsampling is not supported, and it's a bad idea for AVIF anyway. -/// If there's no alpha, use `None::<[_; 0]>`. -/// -/// returns AVIF file, size of color metadata, size of alpha metadata overhead -#[inline] -pub fn encode_raw_planes_8_bit(&self, width: usize, height: usize, planes: impl IntoIterator + Send, alpha: Option + Send>, color_pixel_range: PixelRange, matrix_coefficients: MatrixCoefficients) -> Result { - self.encode_raw_planes(width, height, planes, alpha, color_pixel_range, matrix_coefficients, 8) -} - -/// Encodes AVIF from 3 planar channels that are in the color space described by `matrix_coefficients`, -/// with sRGB transfer characteristics and color primaries. -/// -/// The pixels are 10-bit (values `0.=1023`). -/// -/// Alpha always uses full range. Chroma subsampling is not supported, and it's a bad idea for AVIF anyway. -/// If there's no alpha, use `None::<[_; 0]>`. -/// -/// returns AVIF file, size of color metadata, size of alpha metadata overhead -#[inline] -pub fn encode_raw_planes_10_bit(&self, width: usize, height: usize, planes: impl IntoIterator + Send, alpha: Option + Send>, color_pixel_range: PixelRange, matrix_coefficients: MatrixCoefficients) -> Result { - self.encode_raw_planes(width, height, planes, alpha, color_pixel_range, matrix_coefficients, 10) -} - -#[inline(never)] -fn encode_raw_planes(&self, width: usize, height: usize, planes: impl IntoIterator + Send, alpha: Option + Send>, color_pixel_range: PixelRange, matrix_coefficients: MatrixCoefficients, bit_depth: u8) -> Result { - let color_description = Some(ColorDescription { - transfer_characteristics: TransferCharacteristics::SRGB, - color_primaries: ColorPrimaries::BT709, // sRGB-compatible - matrix_coefficients, - }); + /// Encodes AVIF from 3 planar channels that are in the color space described by `matrix_coefficients`, + /// with sRGB transfer characteristics and color primaries. + /// + /// The pixels are 10-bit (values `0.=1023`). + /// + /// Alpha always uses full range. Chroma subsampling is not supported, and it's a bad idea for AVIF anyway. + /// If there's no alpha, use `None::<[_; 0]>`. + /// + /// returns AVIF file, size of color metadata, size of alpha metadata overhead + #[inline] + pub fn encode_raw_planes_10_bit( + &self, + width: usize, + height: usize, + planes: impl IntoIterator + Send, + alpha: Option + Send>, + color_pixel_range: PixelRange, + matrix_coefficients: MatrixCoefficients, + ) -> Result { + self.encode_raw_planes( + width, + height, + planes, + alpha, + color_pixel_range, + matrix_coefficients, + 10, + ) + } - let threads = self.threads.map(|threads| { - if threads > 0 { threads } else { rayon::current_num_threads() } - }); + #[inline(never)] + fn encode_raw_planes( + &self, + width: usize, + height: usize, + planes: impl IntoIterator + Send, + alpha: Option + Send>, + color_pixel_range: PixelRange, + matrix_coefficients: MatrixCoefficients, + bit_depth: u8, + ) -> Result { + let color_description = Some(ColorDescription { + transfer_characteristics: TransferCharacteristics::SRGB, + color_primaries: ColorPrimaries::BT709, // sRGB-compatible + matrix_coefficients, + }); + + let threads = self.threads.map(|threads| { + if threads > 0 { + threads + } else { + rayon::current_num_threads() + } + }); - let encode_color = move || encode_to_av1::

(&Av1EncodeConfig { - width, - height, - bit_depth: bit_depth.into(), - quantizer: self.quantizer.into(), - speed: SpeedTweaks::from_my_preset(self.speed, self.quantizer), - threads, - pixel_range: color_pixel_range, - chroma_sampling: ChromaSampling::Cs444, - color_description, - }, move |frame| init_frame_3(width, height, planes, frame)); - let encode_alpha = move || alpha.map(|alpha| encode_to_av1::

(&Av1EncodeConfig { - width, - height, - bit_depth: bit_depth.into(), - quantizer: self.alpha_quantizer.into(), - speed: SpeedTweaks::from_my_preset(self.speed, self.alpha_quantizer), - threads, - pixel_range: PixelRange::Full, - chroma_sampling: ChromaSampling::Cs400, - color_description: None, - }, |frame| init_frame_1(width, height, alpha, frame))); - #[cfg(all(target_arch="wasm32", not(target_feature = "atomics")))] - let (color, alpha) = (encode_color(), encode_alpha()); - #[cfg(not(all(target_arch="wasm32", not(target_feature = "atomics"))))] - let (color, alpha) = rayon::join(encode_color, encode_alpha); - let (color, alpha) = (color?, alpha.transpose()?); - - let avif_file = avif_serialize::Aviffy::new() - .matrix_coefficients(match matrix_coefficients { - MatrixCoefficients::Identity => avif_serialize::constants::MatrixCoefficients::Rgb, - MatrixCoefficients::BT709 => avif_serialize::constants::MatrixCoefficients::Bt709, - MatrixCoefficients::Unspecified => avif_serialize::constants::MatrixCoefficients::Unspecified, - MatrixCoefficients::BT601 => avif_serialize::constants::MatrixCoefficients::Bt601, - MatrixCoefficients::YCgCo => avif_serialize::constants::MatrixCoefficients::Ycgco, - MatrixCoefficients::BT2020NCL => avif_serialize::constants::MatrixCoefficients::Bt2020Ncl, - MatrixCoefficients::BT2020CL => avif_serialize::constants::MatrixCoefficients::Bt2020Cl, - _ => return Err(Error::Unsupported("matrix coefficients")), + let encode_color = move || { + encode_to_av1::

( + &Av1EncodeConfig { + width, + height, + bit_depth: bit_depth.into(), + quantizer: self.quantizer.into(), + speed: SpeedTweaks::from_my_preset(self.speed, self.quantizer), + threads, + pixel_range: color_pixel_range, + chroma_sampling: ChromaSampling::Cs444, + color_description, + }, + move |frame| init_frame_3(width, height, planes, frame), + ) + }; + let encode_alpha = move || { + alpha.map(|alpha| { + encode_to_av1::

( + &Av1EncodeConfig { + width, + height, + bit_depth: bit_depth.into(), + quantizer: self.alpha_quantizer.into(), + speed: SpeedTweaks::from_my_preset(self.speed, self.alpha_quantizer), + threads, + pixel_range: PixelRange::Full, + chroma_sampling: ChromaSampling::Cs400, + color_description: None, + }, + |frame| init_frame_1(width, height, alpha, frame), + ) + }) + }; + #[cfg(all(target_arch = "wasm32", not(target_feature = "atomics")))] + let (color, alpha) = (encode_color(), encode_alpha()); + #[cfg(not(all(target_arch = "wasm32", not(target_feature = "atomics"))))] + let (color, alpha) = rayon::join(encode_color, encode_alpha); + let (color, alpha) = (color?, alpha.transpose()?); + + let avif_file = avif_serialize::Aviffy::new() + .matrix_coefficients(match matrix_coefficients { + MatrixCoefficients::Identity => avif_serialize::constants::MatrixCoefficients::Rgb, + MatrixCoefficients::BT709 => avif_serialize::constants::MatrixCoefficients::Bt709, + MatrixCoefficients::Unspecified => { + avif_serialize::constants::MatrixCoefficients::Unspecified + } + MatrixCoefficients::BT601 => avif_serialize::constants::MatrixCoefficients::Bt601, + MatrixCoefficients::YCgCo => avif_serialize::constants::MatrixCoefficients::Ycgco, + MatrixCoefficients::BT2020NCL => { + avif_serialize::constants::MatrixCoefficients::Bt2020Ncl + } + MatrixCoefficients::BT2020CL => { + avif_serialize::constants::MatrixCoefficients::Bt2020Cl + } + _ => return Err(Error::Unsupported("matrix coefficients")), + }) + .premultiplied_alpha(self.premultiplied_alpha) + .to_vec( + &color, + alpha.as_deref(), + width as u32, + height as u32, + bit_depth, + ); + let color_byte_size = color.len(); + let alpha_byte_size = alpha.as_ref().map_or(0, |a| a.len()); + + Ok(EncodedImage { + avif_file, + color_byte_size, + alpha_byte_size, }) - .premultiplied_alpha(self.premultiplied_alpha) - .to_vec(&color, alpha.as_deref(), width as u32, height as u32, bit_depth); - let color_byte_size = color.len(); - let alpha_byte_size = alpha.as_ref().map_or(0, |a| a.len()); - - Ok(EncodedImage { - avif_file, color_byte_size, alpha_byte_size, - }) -} + } } #[inline(always)] @@ -459,12 +547,14 @@ const BT601: [f32; 3] = [0.2990, 0.5870, 0.1140]; #[inline(always)] fn rgb_to_ycbcr(px: rgb::RGB, depth: u8, matrix: [f32; 3]) -> (f32, f32, f32) { - let max_value = ((1<, matrix: [f32; 3]) -> (u8, u8, u8) { fn quality_to_quantizer(quality: f32) -> u8 { let q = quality / 100.; - let x = if q >= 0.85 { (1. - q) * 3. } else if q > 0.25 { 1. - 0.125 - q * 0.5 } else { 1. - q }; + let x = if q >= 0.85 { + (1. - q) * 3. + } else if q > 0.25 { + 1. - 0.125 - q * 0.5 + } else { + 1. - q + }; (x * 255.).round() as u8 } @@ -538,7 +634,6 @@ impl SpeedTweaks { reduced_tx_set: Some(speed == 4 || speed >= 9), // It interacts with tx_domain_distortion too? // 4px blocks disabled at 5 - fine_directional_intra: Some(speed <= 6), fast_deblock: Some(speed >= 7 && !high_quality), // mixed bag? @@ -570,19 +665,53 @@ impl SpeedTweaks { speed_settings.scene_detection_mode = SceneDetectionSpeed::None; speed_settings.motion.include_near_mvs = false; - if let Some(v) = self.fast_deblock { speed_settings.fast_deblock = v; } - if let Some(v) = self.reduced_tx_set { speed_settings.transform.reduced_tx_set = v; } - if let Some(v) = self.tx_domain_distortion { speed_settings.transform.tx_domain_distortion = v; } - if let Some(v) = self.tx_domain_rate { speed_settings.transform.tx_domain_rate = v; } - if let Some(v) = self.encode_bottomup { speed_settings.partition.encode_bottomup = v; } - if let Some(v) = self.rdo_tx_decision { speed_settings.transform.rdo_tx_decision = v; } - if let Some(v) = self.cdef { speed_settings.cdef = v; } - if let Some(v) = self.lrf { speed_settings.lrf = v; } - if let Some(v) = self.inter_tx_split { speed_settings.transform.enable_inter_tx_split = v; } - if let Some(v) = self.sgr_complexity_full { speed_settings.sgr_complexity = if v { SGRComplexityLevel::Full } else { SGRComplexityLevel::Reduced } }; - if let Some(v) = self.use_satd_subpel { speed_settings.motion.use_satd_subpel = v; } - if let Some(v) = self.fine_directional_intra { speed_settings.prediction.fine_directional_intra = v; } - if let Some(v) = self.complex_prediction_modes { speed_settings.prediction.prediction_modes = if v { PredictionModesSetting::ComplexAll } else { PredictionModesSetting::Simple} }; + if let Some(v) = self.fast_deblock { + speed_settings.fast_deblock = v; + } + if let Some(v) = self.reduced_tx_set { + speed_settings.transform.reduced_tx_set = v; + } + if let Some(v) = self.tx_domain_distortion { + speed_settings.transform.tx_domain_distortion = v; + } + if let Some(v) = self.tx_domain_rate { + speed_settings.transform.tx_domain_rate = v; + } + if let Some(v) = self.encode_bottomup { + speed_settings.partition.encode_bottomup = v; + } + if let Some(v) = self.rdo_tx_decision { + speed_settings.transform.rdo_tx_decision = v; + } + if let Some(v) = self.cdef { + speed_settings.cdef = v; + } + if let Some(v) = self.lrf { + speed_settings.lrf = v; + } + if let Some(v) = self.inter_tx_split { + speed_settings.transform.enable_inter_tx_split = v; + } + if let Some(v) = self.sgr_complexity_full { + speed_settings.sgr_complexity = if v { + SGRComplexityLevel::Full + } else { + SGRComplexityLevel::Reduced + } + }; + if let Some(v) = self.use_satd_subpel { + speed_settings.motion.use_satd_subpel = v; + } + if let Some(v) = self.fine_directional_intra { + speed_settings.prediction.fine_directional_intra = v; + } + if let Some(v) = self.complex_prediction_modes { + speed_settings.prediction.prediction_modes = if v { + PredictionModesSetting::ComplexAll + } else { + PredictionModesSetting::Simple + } + }; if let Some((min, max)) = self.partition_range { debug_assert!(min <= max); fn sz(s: u8) -> BlockSize { @@ -624,8 +753,7 @@ fn rav1e_config(p: &Av1EncodeConfig) -> Config { threads.min((p.width * p.height) / (p.speed.min_tile_size as usize).pow(2)) }; let speed_settings = p.speed.speed_settings(); - let cfg = Config::new() - .with_encoder_config(EncoderConfig { + let cfg = Config::new().with_encoder_config(EncoderConfig { width: p.width, height: p.height, time_base: Rational::new(1, 1), @@ -664,7 +792,12 @@ fn rav1e_config(p: &Av1EncodeConfig) -> Config { } } -fn init_frame_3(width: usize, height: usize, planes: impl IntoIterator + Send, frame: &mut Frame

) -> Result<(), Error> { +fn init_frame_3( + width: usize, + height: usize, + planes: impl IntoIterator + Send, + frame: &mut Frame

, +) -> Result<(), Error> { let mut f = frame.planes.iter_mut(); let mut planes = planes.into_iter(); @@ -673,7 +806,12 @@ fn init_frame_3(width: usize, height: usize, planes: let mut u = f.next().unwrap().mut_slice(Default::default()); let mut v = f.next().unwrap().mut_slice(Default::default()); - for ((y, u), v) in y.rows_iter_mut().zip(u.rows_iter_mut()).zip(v.rows_iter_mut()).take(height) { + for ((y, u), v) in y + .rows_iter_mut() + .zip(u.rows_iter_mut()) + .zip(v.rows_iter_mut()) + .take(height) + { let y = &mut y[..width]; let u = &mut u[..width]; let v = &mut v[..width]; @@ -687,7 +825,12 @@ fn init_frame_3(width: usize, height: usize, planes: Ok(()) } -fn init_frame_1(width: usize, height: usize, planes: impl IntoIterator + Send, frame: &mut Frame

) -> Result<(), Error> { +fn init_frame_1( + width: usize, + height: usize, + planes: impl IntoIterator + Send, + frame: &mut Frame

, +) -> Result<(), Error> { let mut y = frame.planes[0].mut_slice(Default::default()); let mut planes = planes.into_iter(); @@ -701,7 +844,10 @@ fn init_frame_1(width: usize, height: usize, planes: } #[inline(never)] -fn encode_to_av1(p: &Av1EncodeConfig, init: impl FnOnce(&mut Frame

) -> Result<(), Error>) -> Result, Error> { +fn encode_to_av1( + p: &Av1EncodeConfig, + init: impl FnOnce(&mut Frame

) -> Result<(), Error>, +) -> Result, Error> { let mut ctx: Context

= rav1e_config(p).new_context()?; let mut frame = ctx.new_frame(); @@ -718,11 +864,9 @@ fn encode_to_av1(p: &Av1EncodeConfig, init: impl FnOnce(&mut Fr } _ => continue, }, - Err(EncoderStatus::Encoded) | - Err(EncoderStatus::LimitReached) => break, + Err(EncoderStatus::Encoded) | Err(EncoderStatus::LimitReached) => break, Err(err) => Err(err)?, } } Ok(out) } - diff --git a/ravif/src/dirtyalpha.rs b/ravif/src/dirtyalpha.rs index 14c41cb..5b03f1e 100644 --- a/ravif/src/dirtyalpha.rs +++ b/ravif/src/dirtyalpha.rs @@ -7,13 +7,17 @@ use rgb::RGBA8; #[inline] fn weighed_pixel(px: RGBA8) -> (u16, RGB) { if px.a == 0 { - return (0, RGB::new(0,0,0)) + return (0, RGB::new(0, 0, 0)); } let weight = 256 - u16::from(px.a); - (weight, RGB::new( - u32::from(px.r) * u32::from(weight), - u32::from(px.g) * u32::from(weight), - u32::from(px.b) * u32::from(weight))) + ( + weight, + RGB::new( + u32::from(px.r) * u32::from(weight), + u32::from(px.g) * u32::from(weight), + u32::from(px.b) * u32::from(weight), + ), + ) } /// Clear/change RGB components of fully-transparent RGBA pixels to make them cheaper to encode with AV1 @@ -38,7 +42,12 @@ pub(crate) fn blurred_dirty_alpha(img: ImgRef) -> Option>> return None; // opaque image } - let neutral_alpha = RGBA8::new((sum.r / weights) as u8, (sum.g / weights) as u8, (sum.b / weights) as u8, 0); + let neutral_alpha = RGBA8::new( + (sum.r / weights) as u8, + (sum.g / weights) as u8, + (sum.b / weights) as u8, + 0, + ); let img2 = bleed_opaque_color(img, neutral_alpha); Some(blur_transparent_pixels(img2.as_ref())) } @@ -51,13 +60,14 @@ fn bleed_opaque_color(img: ImgRef, bg: RGBA8) -> Img> { out.push(if mid.curr.a == 255 { mid.curr } else { - let (weights, sum) = chain(&top, &mid, &bot) - .map(|c| weighed_pixel(*c)) - .fold((0u32, RGB::new(0,0,0)), |mut sum, item| { + let (weights, sum) = chain(&top, &mid, &bot).map(|c| weighed_pixel(*c)).fold( + (0u32, RGB::new(0, 0, 0)), + |mut sum, item| { sum.0 += u32::from(item.0); sum.1 += item.1; sum - }); + }, + ); if weights == 0 { bg } else { @@ -85,8 +95,9 @@ fn blur_transparent_pixels(img: ImgRef) -> Img> { out.push(if mid.curr.a == 255 { mid.curr } else { - let sum: RGB = - chain(&top, &mid, &bot).map(|px| px.rgb().map(u16::from)).sum(); + let sum: RGB = chain(&top, &mid, &bot) + .map(|px| px.rgb().map(u16::from)) + .sum(); let mut avg = sum.map(|c| (c / 9) as u8); if mid.curr.a == 0 { avg.with_alpha(0) @@ -104,7 +115,11 @@ fn blur_transparent_pixels(img: ImgRef) -> Img> { } #[inline(always)] -fn chain<'a, T>(top: &'a loop9::Triple, mid: &'a loop9::Triple, bot: &'a loop9::Triple) -> impl Iterator + 'a { +fn chain<'a, T>( + top: &'a loop9::Triple, + mid: &'a loop9::Triple, + bot: &'a loop9::Triple, +) -> impl Iterator + 'a { top.iter().chain(mid.iter()).chain(bot.iter()) } @@ -129,11 +144,11 @@ fn premultiplied_minmax(px: u8, alpha: u8) -> (u8, u8) { #[test] fn preminmax() { - assert_eq!((100,100), premultiplied_minmax(100, 255)); - assert_eq!((78,100), premultiplied_minmax(100, 10)); - assert_eq!(100*10/255, 78*10/255); - assert_eq!(100*10/255, 100*10/255); - assert_eq!((8,119), premultiplied_minmax(100, 2)); - assert_eq!((16,239), premultiplied_minmax(100, 1)); - assert_eq!((15,255), premultiplied_minmax(255, 1)); + assert_eq!((100, 100), premultiplied_minmax(100, 255)); + assert_eq!((78, 100), premultiplied_minmax(100, 10)); + assert_eq!(100 * 10 / 255, 78 * 10 / 255); + assert_eq!(100 * 10 / 255, 100 * 10 / 255); + assert_eq!((8, 119), premultiplied_minmax(100, 2)); + assert_eq!((16, 239), premultiplied_minmax(100, 1)); + assert_eq!((15, 255), premultiplied_minmax(255, 1)); } diff --git a/ravif/src/lib.rs b/ravif/src/lib.rs index 5e4c0e8..2dd38ac 100644 --- a/ravif/src/lib.rs +++ b/ravif/src/lib.rs @@ -11,8 +11,8 @@ mod av1encoder; mod error; -pub use error::Error; pub use av1encoder::ColorModel; +pub use error::Error; #[doc(hidden)] #[deprecated = "Renamed to `ColorModel`"] @@ -32,7 +32,9 @@ pub use rgb::{RGB8, RGBA8}; #[cfg(not(feature = "threading"))] mod rayoff { pub fn current_num_threads() -> usize { - std::thread::available_parallelism().map(|v| v.get()).unwrap_or(1) + std::thread::available_parallelism() + .map(|v| v.get()) + .unwrap_or(1) } pub fn join(a: impl FnOnce() -> A, b: impl FnOnce() -> B) -> (A, B) { diff --git a/src/main.rs b/src/main.rs index 99d8cb7..5f13d7b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ -use clap::ArgAction; use clap::value_parser; +use clap::ArgAction; use clap::{Arg, Command}; use imgref::ImgVec; use ravif::{AlphaColorMode, BitDepth, ColorModel, EncodedImage, Encoder, RGBA8}; @@ -112,14 +112,12 @@ fn run() -> Result<(), BoxError> { .help("One or more JPEG or PNG files to convert. \"-\" is interpreted as stdin/stdout.")) .get_matches(); - let output = args.get_one::("output").map(|s| { - match s { - s if s.as_os_str() == "-" => MaybePath::Stdio, - s => MaybePath::Path(PathBuf::from(s)), - } + let output = args.get_one::("output").map(|s| match s { + s if s.as_os_str() == "-" => MaybePath::Stdio, + s => MaybePath::Path(PathBuf::from(s)), }); let quality = *args.get_one::("quality").expect("default"); - let alpha_quality = ((quality + 100.)/2.).min(quality + quality/4. + 2.); + let alpha_quality = ((quality + 100.) / 2.).min(quality + quality / 4. + 2.); let speed: u8 = *args.get_one::("speed").expect("default"); let overwrite = args.get_flag("overwrite"); let quiet = args.get_flag("quiet"); @@ -138,7 +136,9 @@ fn run() -> Result<(), BoxError> { _ => BitDepth::Auto, }; - let files = args.get_many::("IMAGES").ok_or("Please specify image paths to convert")?; + let files = args + .get_many::("IMAGES") + .ok_or("Please specify image paths to convert")?; let files: Vec<_> = files .filter(|pathstr| { let path = Path::new(&pathstr); @@ -178,7 +178,7 @@ fn run() -> Result<(), BoxError> { let _ = fs::create_dir_all(path); } files.len() > 1 || path.is_dir() - }, + } _ => false, }; @@ -194,15 +194,14 @@ fn run() -> Result<(), BoxError> { output.clone() } }), - (None, MaybePath::Stdio) | - (Some(MaybePath::Stdio), _) => MaybePath::Stdio, + (None, MaybePath::Stdio) | (Some(MaybePath::Stdio), _) => MaybePath::Stdio, (Some(MaybePath::Path(output)), MaybePath::Stdio) => MaybePath::Path(output.clone()), }; match out_path { MaybePath::Path(ref p) if !overwrite && p.exists() => { return Err(format!("{} already exists; skipping", p.display()).into()); - }, - _ => {}, + } + _ => {} } let enc = Encoder::new() .with_quality(quality) @@ -210,43 +209,58 @@ fn run() -> Result<(), BoxError> { .with_speed(speed) .with_alpha_quality(alpha_quality) .with_internal_color_model(color_model) - .with_alpha_color_mode(if dirty_alpha { AlphaColorMode::UnassociatedDirty } else { AlphaColorMode::UnassociatedClean }) + .with_alpha_color_mode(if dirty_alpha { + AlphaColorMode::UnassociatedDirty + } else { + AlphaColorMode::UnassociatedClean + }) .with_num_threads(threads.filter(|&n| n > 0).map(usize::from)); - let EncodedImage { avif_file, color_byte_size, alpha_byte_size , .. } = enc.encode_rgba(img.as_ref())?; + let EncodedImage { + avif_file, + color_byte_size, + alpha_byte_size, + .. + } = enc.encode_rgba(img.as_ref())?; match out_path { MaybePath::Path(ref p) => { if !quiet { - println!("{}: {}KB ({color_byte_size}B color, {alpha_byte_size}B alpha, {}B HEIF)", p.display(), (avif_file.len()+999)/1000, avif_file.len() - color_byte_size - alpha_byte_size); + println!( + "{}: {}KB ({color_byte_size}B color, {alpha_byte_size}B alpha, {}B HEIF)", + p.display(), + (avif_file.len() + 999) / 1000, + avif_file.len() - color_byte_size - alpha_byte_size + ); } fs::write(p, avif_file) - }, - MaybePath::Stdio => { - std::io::stdout().write_all(&avif_file) - }, - }.map_err(|e| format!("Unable to write output image: {e}"))?; + } + MaybePath::Stdio => std::io::stdout().write_all(&avif_file), + } + .map_err(|e| format!("Unable to write output image: {e}"))?; Ok(()) }; - let failures = files.into_par_iter().map(|path| { - let tmp; - let (data, path_str): (_, &dyn std::fmt::Display) = match path { - MaybePath::Stdio => { - let mut data = Vec::new(); - std::io::stdin().read_to_end(&mut data)?; - (data, &"stdin") - }, - MaybePath::Path(ref path) => { - let data = fs::read(path) - .map_err(|e| format!("Unable to read input image {}: {e}", path.display()))?; - tmp = path.display(); - (data, &tmp) - }, - }; - process(data, &path) - .map_err(|e| BoxError::from(format!("{path_str}: error: {e}"))) - }) - .filter_map(|res| res.err()) - .collect::>(); + let failures = files + .into_par_iter() + .map(|path| { + let tmp; + let (data, path_str): (_, &dyn std::fmt::Display) = match path { + MaybePath::Stdio => { + let mut data = Vec::new(); + std::io::stdin().read_to_end(&mut data)?; + (data, &"stdin") + } + MaybePath::Path(ref path) => { + let data = fs::read(path).map_err(|e| { + format!("Unable to read input image {}: {e}", path.display()) + })?; + tmp = path.display(); + (data, &tmp) + } + }; + process(data, &path).map_err(|e| BoxError::from(format!("{path_str}: error: {e}"))) + }) + .filter_map(|res| res.err()) + .collect::>(); if !failures.is_empty() { if !quiet { @@ -265,14 +279,52 @@ fn load_rgba(data: &[u8], premultiplied_alpha: bool) -> Result, Bo let img = load_image::load_data(data)?.into_imgvec(); let mut img = match img { - load_image::export::imgref::ImgVecKind::RGB8(img) => img.map_buf(|buf| buf.into_iter().map(|px| px.with_alpha(255)).collect()), + load_image::export::imgref::ImgVecKind::RGB8(img) => { + img.map_buf(|buf| buf.into_iter().map(|px| px.with_alpha(255)).collect()) + } load_image::export::imgref::ImgVecKind::RGBA8(img) => img, - load_image::export::imgref::ImgVecKind::RGB16(img) => img.map_buf(|buf| buf.into_iter().map(|px| px.map(|c| (c >> 8) as u8).with_alpha(255)).collect()), - load_image::export::imgref::ImgVecKind::RGBA16(img) => img.map_buf(|buf| buf.into_iter().map(|px| px.map(|c| (c >> 8) as u8)).collect()), - load_image::export::imgref::ImgVecKind::GRAY8(img) => img.map_buf(|buf| buf.into_iter().map(|g| { let c = g.0; RGBA8::new(c,c,c,255) }).collect()), - load_image::export::imgref::ImgVecKind::GRAY16(img) => img.map_buf(|buf| buf.into_iter().map(|g| { let c = (g.0>>8) as u8; RGBA8::new(c,c,c,255) }).collect()), - load_image::export::imgref::ImgVecKind::GRAYA8(img) => img.map_buf(|buf| buf.into_iter().map(|g| { let c = g.0; RGBA8::new(c,c,c,g.1) }).collect()), - load_image::export::imgref::ImgVecKind::GRAYA16(img) => img.map_buf(|buf| buf.into_iter().map(|g| { let c = (g.0>>8) as u8; RGBA8::new(c,c,c,(g.1>>8) as u8) }).collect()), + load_image::export::imgref::ImgVecKind::RGB16(img) => img.map_buf(|buf| { + buf.into_iter() + .map(|px| px.map(|c| (c >> 8) as u8).with_alpha(255)) + .collect() + }), + load_image::export::imgref::ImgVecKind::RGBA16(img) => img.map_buf(|buf| { + buf.into_iter() + .map(|px| px.map(|c| (c >> 8) as u8)) + .collect() + }), + load_image::export::imgref::ImgVecKind::GRAY8(img) => img.map_buf(|buf| { + buf.into_iter() + .map(|g| { + let c = g.0; + RGBA8::new(c, c, c, 255) + }) + .collect() + }), + load_image::export::imgref::ImgVecKind::GRAY16(img) => img.map_buf(|buf| { + buf.into_iter() + .map(|g| { + let c = (g.0 >> 8) as u8; + RGBA8::new(c, c, c, 255) + }) + .collect() + }), + load_image::export::imgref::ImgVecKind::GRAYA8(img) => img.map_buf(|buf| { + buf.into_iter() + .map(|g| { + let c = g.0; + RGBA8::new(c, c, c, g.1) + }) + .collect() + }), + load_image::export::imgref::ImgVecKind::GRAYA16(img) => img.map_buf(|buf| { + buf.into_iter() + .map(|g| { + let c = (g.0 >> 8) as u8; + RGBA8::new(c, c, c, (g.1 >> 8) as u8) + }) + .collect() + }), }; if premultiplied_alpha { diff --git a/tests/stdio.rs b/tests/stdio.rs index d83e327..777eb99 100644 --- a/tests/stdio.rs +++ b/tests/stdio.rs @@ -21,7 +21,7 @@ fn stdio() -> Result<(), std::io::Error> { let mut data = Vec::new(); cmd.stdout.take().unwrap().read_to_end(&mut data)?; assert!(cmd.wait()?.success()); - assert_eq!(&data[4..4+8], b"ftypavif"); + assert_eq!(&data[4..4 + 8], b"ftypavif"); Ok(()) }