use crate::error::{Error, Result};
use crate::types::{EncodePixel, PixelLayout};
use alloc::vec::Vec;
use enough::Stop;
use whereat::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
#[repr(i32)]
pub enum Preset {
#[default]
Default = 0,
Picture = 1,
Photo = 2,
Drawing = 3,
Icon = 4,
Text = 5,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
#[repr(u32)]
pub enum ImageHint {
#[default]
Default = 0,
Picture = 1,
Photo = 2,
Graph = 3,
}
impl ImageHint {
pub(crate) fn to_libwebp(self) -> libwebp_sys::WebPImageHint {
match self {
ImageHint::Default => libwebp_sys::WebPImageHint::WEBP_HINT_DEFAULT,
ImageHint::Picture => libwebp_sys::WebPImageHint::WEBP_HINT_PICTURE,
ImageHint::Photo => libwebp_sys::WebPImageHint::WEBP_HINT_PHOTO,
ImageHint::Graph => libwebp_sys::WebPImageHint::WEBP_HINT_GRAPH,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
#[repr(i32)]
pub enum AlphaFilter {
None = 0,
#[default]
Fast = 1,
Best = 2,
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct EncodeStats {
pub coded_size: u32,
pub psnr: [f32; 5],
pub block_count: [u32; 3],
pub header_bytes: [u32; 2],
pub segment_size: [u32; 4],
pub segment_quant: [u32; 4],
pub segment_level: [u32; 4],
pub alpha_data_size: u32,
pub histogram_bits: u32,
pub transform_bits: u32,
pub cache_bits: u32,
pub palette_size: u32,
pub lossless_size: u32,
pub lossless_hdr_size: u32,
pub lossless_data_size: u32,
}
impl EncodeStats {
pub(crate) fn from_libwebp(stats: &libwebp_sys::WebPAuxStats) -> Self {
Self {
coded_size: stats.coded_size as u32,
psnr: stats.PSNR,
block_count: [
stats.block_count[0] as u32,
stats.block_count[1] as u32,
stats.block_count[2] as u32,
],
header_bytes: [stats.header_bytes[0] as u32, stats.header_bytes[1] as u32],
segment_size: [
stats.segment_size[0] as u32,
stats.segment_size[1] as u32,
stats.segment_size[2] as u32,
stats.segment_size[3] as u32,
],
segment_quant: [
stats.segment_quant[0] as u32,
stats.segment_quant[1] as u32,
stats.segment_quant[2] as u32,
stats.segment_quant[3] as u32,
],
segment_level: [
stats.segment_level[0] as u32,
stats.segment_level[1] as u32,
stats.segment_level[2] as u32,
stats.segment_level[3] as u32,
],
alpha_data_size: stats.alpha_data_size as u32,
histogram_bits: stats.histogram_bits as u32,
transform_bits: stats.transform_bits as u32,
cache_bits: stats.cache_bits as u32,
palette_size: stats.palette_size as u32,
lossless_size: stats.lossless_size as u32,
lossless_hdr_size: stats.lossless_hdr_size as u32,
lossless_data_size: stats.lossless_data_size as u32,
}
}
}
impl Preset {
pub(crate) fn to_libwebp(self) -> libwebp_sys::WebPPreset {
match self {
Preset::Default => libwebp_sys::WebPPreset::WEBP_PRESET_DEFAULT,
Preset::Picture => libwebp_sys::WebPPreset::WEBP_PRESET_PICTURE,
Preset::Photo => libwebp_sys::WebPPreset::WEBP_PRESET_PHOTO,
Preset::Drawing => libwebp_sys::WebPPreset::WEBP_PRESET_DRAWING,
Preset::Icon => libwebp_sys::WebPPreset::WEBP_PRESET_ICON,
Preset::Text => libwebp_sys::WebPPreset::WEBP_PRESET_TEXT,
}
}
}
#[derive(Debug, Clone)]
pub struct EncoderConfig {
pub(crate) quality: f32,
pub(crate) preset: Preset,
pub(crate) lossless: bool,
pub(crate) method: u8,
pub(crate) near_lossless: u8,
pub(crate) alpha_quality: u8,
pub(crate) alpha_compression: bool,
pub(crate) alpha_filter: AlphaFilter,
pub(crate) exact: bool,
pub(crate) target_size: u32,
pub(crate) target_psnr: f32,
pub(crate) sns_strength: Option<u8>,
pub(crate) filter_strength: Option<u8>,
pub(crate) filter_sharpness: Option<u8>,
pub(crate) filter_type: u8,
pub(crate) autofilter: bool,
pub(crate) pass: u8,
pub(crate) segments: u8,
pub(crate) use_sharp_yuv: bool,
pub(crate) thread_level: u8,
pub(crate) low_memory: bool,
pub(crate) hint: ImageHint,
pub(crate) preprocessing: u8,
pub(crate) partitions: u8,
pub(crate) partition_limit: u8,
pub(crate) delta_palette: bool,
pub(crate) qmin: u8,
pub(crate) qmax: u8,
#[cfg(feature = "icc")]
pub(crate) icc_profile: Option<Vec<u8>>,
#[cfg(feature = "icc")]
pub(crate) exif_data: Option<Vec<u8>>,
#[cfg(feature = "icc")]
pub(crate) xmp_data: Option<Vec<u8>>,
}
impl Default for EncoderConfig {
fn default() -> Self {
Self {
quality: 75.0,
preset: Preset::Default,
lossless: false,
method: 4,
near_lossless: 100,
alpha_quality: 100,
alpha_compression: true,
alpha_filter: AlphaFilter::Fast,
exact: false,
target_size: 0,
target_psnr: 0.0,
sns_strength: None,
filter_strength: None,
filter_sharpness: None,
filter_type: 1,
autofilter: false,
pass: 1,
segments: 4,
use_sharp_yuv: false,
thread_level: 0,
low_memory: false,
hint: ImageHint::Default,
preprocessing: 0,
partitions: 0,
partition_limit: 0,
delta_palette: false,
qmin: 0,
qmax: 100,
#[cfg(feature = "icc")]
icc_profile: None,
#[cfg(feature = "icc")]
exif_data: None,
#[cfg(feature = "icc")]
xmp_data: None,
}
}
}
impl EncoderConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn new_lossless() -> Self {
Self {
lossless: true,
quality: 75.0,
alpha_compression: false,
..Self::default()
}
}
#[must_use]
pub fn new_lossless_level(level: u8) -> Self {
let level = level.min(9);
let method = match level {
0 => 0,
1 => 1,
2 => 2,
3 => 3,
4 => 3,
5 => 4,
6 => 4,
7 => 4,
8 => 5,
_ => 6,
};
let quality = match level {
0..=4 => 25.0 + (level as f32) * 15.0,
_ => 80.0 + ((level - 5) as f32) * 4.0,
};
Self {
lossless: true,
method,
quality,
alpha_compression: false,
..Self::default()
}
}
#[must_use]
pub fn with_preset(preset: Preset, quality: f32) -> Self {
Self {
preset,
quality,
..Self::default()
}
}
#[must_use]
pub fn max_compression() -> Self {
Self {
quality: 90.0,
method: 6,
pass: 10,
segments: 4,
sns_strength: Some(100),
autofilter: true,
use_sharp_yuv: true,
partition_limit: 100,
..Self::default()
}
}
#[must_use]
pub fn max_compression_lossless() -> Self {
Self {
lossless: true,
quality: 100.0,
method: 6,
near_lossless: 100,
delta_palette: true,
alpha_compression: false,
..Self::default()
}
}
#[must_use]
pub fn quality(mut self, quality: f32) -> Self {
self.quality = quality.clamp(0.0, 100.0);
self
}
#[must_use]
pub fn preset(mut self, preset: Preset) -> Self {
self.preset = preset;
self
}
#[must_use]
pub fn lossless(mut self, lossless: bool) -> Self {
self.lossless = lossless;
if lossless {
self.alpha_compression = false;
}
self
}
#[must_use]
pub fn method(mut self, method: u8) -> Self {
self.method = method.min(6);
self
}
#[must_use]
pub fn near_lossless(mut self, value: u8) -> Self {
self.near_lossless = value.min(100);
self
}
#[must_use]
pub fn alpha_quality(mut self, quality: u8) -> Self {
self.alpha_quality = quality.min(100);
self
}
#[must_use]
pub fn alpha_compression(mut self, enable: bool) -> Self {
self.alpha_compression = enable;
self
}
#[must_use]
pub fn alpha_filter(mut self, filter: AlphaFilter) -> Self {
self.alpha_filter = filter;
self
}
#[must_use]
pub fn exact(mut self, exact: bool) -> Self {
self.exact = exact;
self
}
#[must_use]
pub fn target_size(mut self, size: u32) -> Self {
self.target_size = size;
self
}
#[must_use]
pub fn target_psnr(mut self, psnr: f32) -> Self {
self.target_psnr = psnr;
self
}
#[must_use]
pub fn sns_strength(mut self, strength: u8) -> Self {
self.sns_strength = Some(strength.min(100));
self
}
#[must_use]
pub fn filter_strength(mut self, strength: u8) -> Self {
self.filter_strength = Some(strength.min(100));
self
}
#[must_use]
pub fn filter_sharpness(mut self, sharpness: u8) -> Self {
self.filter_sharpness = Some(sharpness.min(7));
self
}
#[must_use]
pub fn filter_type(mut self, filter_type: u8) -> Self {
self.filter_type = filter_type.min(1);
self
}
#[must_use]
pub fn autofilter(mut self, enable: bool) -> Self {
self.autofilter = enable;
self
}
#[must_use]
pub fn pass(mut self, passes: u8) -> Self {
self.pass = passes.clamp(1, 10);
self
}
#[must_use]
pub fn segments(mut self, segments: u8) -> Self {
self.segments = segments.clamp(1, 4);
self
}
#[must_use]
pub fn sharp_yuv(mut self, enable: bool) -> Self {
self.use_sharp_yuv = enable;
self
}
#[must_use]
pub fn thread_level(mut self, level: u8) -> Self {
self.thread_level = level;
self
}
#[must_use]
pub fn low_memory(mut self, enable: bool) -> Self {
self.low_memory = enable;
self
}
#[must_use]
pub fn hint(mut self, hint: ImageHint) -> Self {
self.hint = hint;
self
}
#[must_use]
pub fn preprocessing(mut self, level: u8) -> Self {
self.preprocessing = level.min(7);
self
}
#[must_use]
pub fn partitions(mut self, log2_partitions: u8) -> Self {
self.partitions = log2_partitions.min(3);
self
}
#[must_use]
pub fn partition_limit(mut self, limit: u8) -> Self {
self.partition_limit = limit.min(100);
self
}
#[must_use]
pub fn delta_palette(mut self, enable: bool) -> Self {
self.delta_palette = enable;
self
}
#[must_use]
pub fn quality_range(mut self, min: u8, max: u8) -> Self {
self.qmin = min.min(100);
self.qmax = max.min(100).max(self.qmin);
self
}
#[cfg(feature = "icc")]
#[must_use]
pub fn icc_profile(mut self, profile: impl Into<Vec<u8>>) -> Self {
self.icc_profile = Some(profile.into());
self
}
#[cfg(feature = "icc")]
#[must_use]
pub fn exif(mut self, data: impl Into<Vec<u8>>) -> Self {
self.exif_data = Some(data.into());
self
}
#[cfg(feature = "icc")]
#[must_use]
pub fn xmp(mut self, data: impl Into<Vec<u8>>) -> Self {
self.xmp_data = Some(data.into());
self
}
pub fn encode<P: EncodePixel>(
&self,
pixels: &[P],
width: u32,
height: u32,
stop: impl Stop,
) -> Result<Vec<u8>> {
let bpp = P::LAYOUT.bytes_per_pixel();
let data = unsafe {
core::slice::from_raw_parts(pixels.as_ptr() as *const u8, pixels.len() * bpp)
};
self.encode_internal(data, width, height, P::LAYOUT, stop)
}
pub fn encode_img<P: EncodePixel>(
&self,
img: imgref::ImgRef<'_, P>,
stop: impl Stop,
) -> Result<Vec<u8>> {
crate::Encoder::from_img(img)
.config(self.clone())
.encode(stop)
}
pub fn encode_rgba(
&self,
data: &[u8],
width: u32,
height: u32,
stop: impl Stop,
) -> Result<Vec<u8>> {
self.encode_internal(data, width, height, PixelLayout::Rgba, stop)
}
pub fn encode_rgb(
&self,
data: &[u8],
width: u32,
height: u32,
stop: impl Stop,
) -> Result<Vec<u8>> {
self.encode_internal(data, width, height, PixelLayout::Rgb, stop)
}
pub fn encode_bgra(
&self,
data: &[u8],
width: u32,
height: u32,
stop: impl Stop,
) -> Result<Vec<u8>> {
self.encode_internal(data, width, height, PixelLayout::Bgra, stop)
}
pub fn encode_bgr(
&self,
data: &[u8],
width: u32,
height: u32,
stop: impl Stop,
) -> Result<Vec<u8>> {
self.encode_internal(data, width, height, PixelLayout::Bgr, stop)
}
pub fn encode_rgba_with_stats(
&self,
data: &[u8],
width: u32,
height: u32,
) -> Result<(Vec<u8>, EncodeStats)> {
crate::encode::encode_with_config_stats(data, width, height, 4, self)
}
pub fn encode_rgb_with_stats(
&self,
data: &[u8],
width: u32,
height: u32,
) -> Result<(Vec<u8>, EncodeStats)> {
crate::encode::encode_with_config_stats(data, width, height, 3, self)
}
fn encode_internal(
&self,
data: &[u8],
width: u32,
height: u32,
layout: PixelLayout,
stop: impl Stop,
) -> Result<Vec<u8>> {
match layout {
PixelLayout::Rgba => {
crate::encode::encode_with_config_stoppable(data, width, height, 4, self, &stop)
}
PixelLayout::Rgb => {
crate::encode::encode_with_config_stoppable(data, width, height, 3, self, &stop)
}
PixelLayout::Bgra => crate::Encoder::new_bgra(data, width, height)
.config(self.clone())
.encode(stop),
PixelLayout::Bgr => crate::Encoder::new_bgr(data, width, height)
.config(self.clone())
.encode(stop),
}
}
pub fn validate(&self) -> Result<()> {
let _ = self.to_libwebp()?;
Ok(())
}
pub(crate) fn to_libwebp(&self) -> Result<libwebp_sys::WebPConfig> {
let mut config =
libwebp_sys::WebPConfig::new_with_preset(self.preset.to_libwebp(), self.quality)
.map_err(|_| at!(Error::InvalidConfig("failed to initialize config".into())))?;
config.lossless = self.lossless as i32;
config.method = self.method as i32;
config.near_lossless = self.near_lossless as i32;
config.alpha_quality = self.alpha_quality as i32;
config.alpha_compression = self.alpha_compression as i32;
config.alpha_filtering = self.alpha_filter as i32;
config.exact = self.exact as i32;
config.target_size = self.target_size as i32;
config.target_PSNR = self.target_psnr;
if let Some(v) = self.sns_strength {
config.sns_strength = v as i32;
}
if let Some(v) = self.filter_strength {
config.filter_strength = v as i32;
}
if let Some(v) = self.filter_sharpness {
config.filter_sharpness = v as i32;
}
config.filter_type = self.filter_type as i32;
config.autofilter = self.autofilter as i32;
config.pass = self.pass as i32;
config.segments = self.segments as i32;
config.use_sharp_yuv = self.use_sharp_yuv as i32;
config.thread_level = self.thread_level as i32;
config.low_memory = self.low_memory as i32;
config.image_hint = self.hint.to_libwebp();
config.preprocessing = self.preprocessing as i32;
config.partitions = self.partitions as i32;
config.partition_limit = self.partition_limit as i32;
config.use_delta_palette = self.delta_palette as i32;
config.qmin = self.qmin as i32;
config.qmax = self.qmax as i32;
if unsafe { libwebp_sys::WebPValidateConfig(&config) } == 0 {
return Err(at!(Error::InvalidConfig("config validation failed".into())));
}
Ok(config)
}
#[must_use]
pub fn get_quality(&self) -> f32 {
self.quality
}
#[must_use]
pub fn get_preset(&self) -> Preset {
self.preset
}
#[must_use]
pub fn is_lossless(&self) -> bool {
self.lossless
}
#[must_use]
pub fn get_method(&self) -> u8 {
self.method
}
}
#[derive(Debug, Clone, Default)]
pub struct DecoderConfig {
pub(crate) bypass_filtering: bool,
pub(crate) no_fancy_upsampling: bool,
pub(crate) use_cropping: bool,
pub(crate) crop_left: u32,
pub(crate) crop_top: u32,
pub(crate) crop_width: u32,
pub(crate) crop_height: u32,
pub(crate) use_scaling: bool,
pub(crate) scaled_width: u32,
pub(crate) scaled_height: u32,
pub(crate) use_threads: bool,
pub(crate) flip: bool,
pub(crate) alpha_dithering: u8,
}
impl DecoderConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn bypass_filtering(mut self, enable: bool) -> Self {
self.bypass_filtering = enable;
self
}
#[must_use]
pub fn no_fancy_upsampling(mut self, enable: bool) -> Self {
self.no_fancy_upsampling = enable;
self
}
#[must_use]
pub fn crop(mut self, left: u32, top: u32, width: u32, height: u32) -> Self {
self.use_cropping = true;
self.crop_left = left;
self.crop_top = top;
self.crop_width = width;
self.crop_height = height;
self
}
#[must_use]
pub fn scale(mut self, width: u32, height: u32) -> Self {
self.use_scaling = true;
self.scaled_width = width;
self.scaled_height = height;
self
}
#[must_use]
pub fn use_threads(mut self, enable: bool) -> Self {
self.use_threads = enable;
self
}
#[must_use]
pub fn flip(mut self, enable: bool) -> Self {
self.flip = enable;
self
}
#[must_use]
pub fn alpha_dithering(mut self, strength: u8) -> Self {
self.alpha_dithering = strength.min(100);
self
}
pub(crate) fn needs_advanced_api(&self) -> bool {
self.use_cropping
|| self.use_scaling
|| self.use_threads
|| self.bypass_filtering
|| self.no_fancy_upsampling
|| self.flip
|| self.alpha_dithering > 0
}
}