use std::{
io::Write,
num::{NonZeroU32, NonZeroU8},
};
use crate::{EncodingError, Format, ImageView, Progress, Size};
mod bc;
mod bc1;
mod bc4;
mod bc7;
mod bcn_util;
mod bi_planar;
mod encoder;
mod sub_sampled;
mod uncompressed;
mod write_util;
use bc::*;
use bi_planar::*;
pub(crate) use encoder::EncoderSet;
use sub_sampled::*;
use uncompressed::*;
pub(crate) const fn get_encoders(format: Format) -> Option<EncoderSet> {
Some(match format {
Format::R8G8B8_UNORM => R8G8B8_UNORM,
Format::B8G8R8_UNORM => B8G8R8_UNORM,
Format::R8G8B8A8_UNORM => R8G8B8A8_UNORM,
Format::R8G8B8A8_SNORM => R8G8B8A8_SNORM,
Format::B8G8R8A8_UNORM => B8G8R8A8_UNORM,
Format::B8G8R8X8_UNORM => B8G8R8X8_UNORM,
Format::B5G6R5_UNORM => B5G6R5_UNORM,
Format::B5G5R5A1_UNORM => B5G5R5A1_UNORM,
Format::B4G4R4A4_UNORM => B4G4R4A4_UNORM,
Format::A4B4G4R4_UNORM => A4B4G4R4_UNORM,
Format::R8_SNORM => R8_SNORM,
Format::R8_UNORM => R8_UNORM,
Format::R8G8_UNORM => R8G8_UNORM,
Format::R8G8_SNORM => R8G8_SNORM,
Format::A8_UNORM => A8_UNORM,
Format::R16_UNORM => R16_UNORM,
Format::R16_SNORM => R16_SNORM,
Format::R16G16_UNORM => R16G16_UNORM,
Format::R16G16_SNORM => R16G16_SNORM,
Format::R16G16B16A16_UNORM => R16G16B16A16_UNORM,
Format::R16G16B16A16_SNORM => R16G16B16A16_SNORM,
Format::R10G10B10A2_UNORM => R10G10B10A2_UNORM,
Format::R11G11B10_FLOAT => R11G11B10_FLOAT,
Format::R9G9B9E5_SHAREDEXP => R9G9B9E5_SHAREDEXP,
Format::R16_FLOAT => R16_FLOAT,
Format::R16G16_FLOAT => R16G16_FLOAT,
Format::R16G16B16A16_FLOAT => R16G16B16A16_FLOAT,
Format::R32_FLOAT => R32_FLOAT,
Format::R32G32_FLOAT => R32G32_FLOAT,
Format::R32G32B32_FLOAT => R32G32B32_FLOAT,
Format::R32G32B32A32_FLOAT => R32G32B32A32_FLOAT,
Format::R10G10B10_XR_BIAS_A2_UNORM => R10G10B10_XR_BIAS_A2_UNORM,
Format::AYUV => AYUV,
Format::Y410 => Y410,
Format::Y416 => Y416,
Format::R1_UNORM => R1_UNORM,
Format::R8G8_B8G8_UNORM => R8G8_B8G8_UNORM,
Format::G8R8_G8B8_UNORM => G8R8_G8B8_UNORM,
Format::UYVY => UYVY,
Format::YUY2 => YUY2,
Format::Y210 => Y210,
Format::Y216 => Y216,
Format::NV12 => NV12,
Format::P010 => P010,
Format::P016 => P016,
Format::BC1_UNORM => BC1_UNORM,
Format::BC2_UNORM => BC2_UNORM,
Format::BC2_UNORM_PREMULTIPLIED_ALPHA => BC2_UNORM_PREMULTIPLIED_ALPHA,
Format::BC3_UNORM => BC3_UNORM,
Format::BC3_UNORM_PREMULTIPLIED_ALPHA => BC3_UNORM_PREMULTIPLIED_ALPHA,
Format::BC4_UNORM => BC4_UNORM,
Format::BC4_SNORM => BC4_SNORM,
Format::BC5_UNORM => BC5_UNORM,
Format::BC5_SNORM => BC5_SNORM,
Format::BC7_UNORM => BC7_UNORM,
Format::ASTC_4X4_UNORM
| Format::ASTC_5X4_UNORM
| Format::ASTC_5X5_UNORM
| Format::ASTC_6X5_UNORM
| Format::ASTC_6X6_UNORM
| Format::ASTC_8X5_UNORM
| Format::ASTC_8X6_UNORM
| Format::ASTC_8X8_UNORM
| Format::ASTC_10X5_UNORM
| Format::ASTC_10X6_UNORM
| Format::ASTC_10X8_UNORM
| Format::ASTC_10X10_UNORM
| Format::ASTC_12X10_UNORM
| Format::ASTC_12X12_UNORM => return None,
Format::BC3_UNORM_RXGB => BC3_UNORM_RXGB,
Format::BC3_UNORM_NORMAL => BC3_UNORM_NORMAL,
Format::BC6H_UF16 | Format::BC6H_SF16 => return None,
})
}
pub fn encode(
writer: &mut dyn Write,
image: ImageView,
format: Format,
progress: Option<&mut Progress>,
options: &EncodeOptions,
) -> Result<(), EncodingError> {
let mut no_reporting = Progress::none();
let progress = progress.unwrap_or(&mut no_reporting);
progress.check_cancelled()?;
#[cfg(feature = "rayon")]
if options.parallel {
return encode_parallel(writer, image, format, progress, options);
}
let encoders = get_encoders(format).ok_or(EncodingError::UnsupportedFormat(format))?;
encoders.encode(writer, image, progress, options)?;
progress.check_cancelled()
}
#[cfg(feature = "rayon")]
fn encode_parallel(
writer: &mut dyn Write,
image: ImageView,
format: Format,
mut progress: &mut Progress,
options: &EncodeOptions,
) -> Result<(), EncodingError> {
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use crate::{ParallelProgress, PixelInfo, SplitView};
let mut options = options.clone();
options.parallel = false;
let split = SplitView::new(image, format, &options);
if let Some(single) = split.single() {
return encode(writer, single, format, Some(progress), &options);
}
let parallel_progress = ParallelProgress::new(&mut progress, image.height() as u64 + 1);
let pixel_info = PixelInfo::from(format);
let result: Result<Vec<Vec<u8>>, EncodingError> = (0..split.len())
.into_par_iter()
.map(|fragment_index| -> Result<Vec<u8>, EncodingError> {
let fragment = split.get(fragment_index).expect("invalid fragment index");
parallel_progress.check_cancelled()?;
let bytes: usize = pixel_info
.surface_bytes(fragment.size)
.unwrap_or(u64::MAX)
.try_into()
.expect("too many bytes");
let mut buffer: Vec<u8> = Vec::with_capacity(bytes);
encode(&mut buffer, fragment, format, None, &options)?;
parallel_progress.check_cancelled()?;
parallel_progress.submit(fragment.height() as u64);
debug_assert_eq!(buffer.len(), bytes);
Ok(buffer)
})
.collect();
let encoded_fragments = result?;
for fragment in encoded_fragments {
progress.check_cancelled()?;
writer.write_all(&fragment)?;
}
progress.checked_report(1.0)
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct EncodeOptions {
pub dithering: Dithering,
pub error_metric: ErrorMetric,
pub quality: CompressionQuality,
pub parallel: bool,
}
impl Default for EncodeOptions {
fn default() -> Self {
Self {
dithering: Dithering::None,
error_metric: ErrorMetric::Uniform,
quality: CompressionQuality::Normal,
parallel: true,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Dithering {
#[default]
None = 0b00,
ColorAndAlpha = 0b11,
Color = 0b01,
Alpha = 0b10,
}
impl Dithering {
pub const fn new(color: bool, alpha: bool) -> Self {
match (color, alpha) {
(true, true) => Dithering::ColorAndAlpha,
(true, false) => Dithering::Color,
(false, true) => Dithering::Alpha,
(false, false) => Dithering::None,
}
}
pub const fn color(self) -> bool {
matches!(self, Dithering::ColorAndAlpha | Dithering::Color)
}
pub const fn alpha(self) -> bool {
matches!(self, Dithering::ColorAndAlpha | Dithering::Alpha)
}
pub(crate) fn intersect(self, other: Self) -> Self {
match (self, other) {
(Dithering::None, _) | (_, Dithering::None) => Dithering::None,
(Dithering::ColorAndAlpha, other) | (other, Dithering::ColorAndAlpha) => other,
(Dithering::Color, Dithering::Alpha) | (Dithering::Alpha, Dithering::Color) => {
Dithering::None
}
(Dithering::Color, Dithering::Color) => Dithering::Color,
(Dithering::Alpha, Dithering::Alpha) => Dithering::Alpha,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum ErrorMetric {
#[default]
Uniform,
Perceptual,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
pub enum CompressionQuality {
Fast,
#[default]
Normal,
High,
Unreasonable,
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum PreferredFragmentSize {
EntireImage,
Fragment {
fast: u8,
high: u8,
unreasonable: u8,
},
}
impl PreferredFragmentSize {
pub const fn new(fast: u64, high: u64, unreasonable: u64) -> Self {
const fn log2(x: u64) -> u8 {
debug_assert!(x != 0);
debug_assert!(x.is_power_of_two());
64 - x.leading_zeros() as u8 - 1
}
Self::Fragment {
fast: log2(fast),
high: log2(high),
unreasonable: log2(unreasonable),
}
}
pub const fn combine(&self, other: Self) -> Self {
const fn u8_min(a: u8, b: u8) -> u8 {
if a > b {
b
} else {
a
}
}
match (*self, other) {
(PreferredFragmentSize::EntireImage, _) => other,
(_, PreferredFragmentSize::EntireImage) => *self,
(
PreferredFragmentSize::Fragment {
fast: a,
high: b,
unreasonable: c,
},
PreferredFragmentSize::Fragment {
fast: x,
high: y,
unreasonable: z,
},
) => PreferredFragmentSize::Fragment {
fast: u8_min(a, x),
high: u8_min(b, y),
unreasonable: u8_min(c, z),
},
}
}
pub fn get_preferred(&self, quality: CompressionQuality) -> u64 {
match *self {
PreferredFragmentSize::EntireImage => u64::MAX,
PreferredFragmentSize::Fragment {
fast,
high,
unreasonable,
} => {
let size_log2 = match quality {
CompressionQuality::Fast => fast,
CompressionQuality::Normal => ((fast as u16 + high as u16) / 2) as u8,
CompressionQuality::High => high,
CompressionQuality::Unreasonable => unreasonable,
};
1 << size_log2.min(63)
}
}
}
}
pub(crate) type SizeMultiple = (NonZeroU8, NonZeroU8);
const SIZE_MUL_2X2: SizeMultiple = {
if let Some(two) = NonZeroU8::new(2) {
(two, two)
} else {
unreachable!()
}
};
#[derive(Debug, Clone, Copy)]
pub struct EncodingSupport {
dithering: Dithering,
split_height: Option<NonZeroU8>,
local_dithering: bool,
size_multiple: Option<SizeMultiple>,
pub(crate) fragment_size: PreferredFragmentSize,
}
impl EncodingSupport {
pub const fn dithering(&self) -> Dithering {
self.dithering
}
pub const fn split_height(&self) -> Option<NonZeroU8> {
self.split_height
}
pub const fn local_dithering(&self) -> bool {
self.local_dithering
}
pub const fn size_multiple(&self) -> Option<(NonZeroU32, NonZeroU32)> {
if let Some((w, h)) = self.size_multiple {
if let (Some(w), Some(h)) = (
NonZeroU32::new(w.get() as u32),
NonZeroU32::new(h.get() as u32),
) {
Some((w, h))
} else {
unreachable!()
}
} else {
None
}
}
pub const fn supports_size(&self, size: Size) -> bool {
if let Some((w, h)) = self.size_multiple {
size.width % w.get() as u32 == 0 && size.height % h.get() as u32 == 0
} else {
true
}
}
}