use crate::ColorSpace;
use crate::encode_image::MAX_DIMENSION;
use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub enum EncodeError {
EmptyImage,
InputSizeMismatch {
expected: usize,
actual: usize,
},
AlphaSizeMismatch {
expected: usize,
actual: usize,
},
InvalidDistance(f32),
QualityIsNaN,
DimensionTooLarge {
width: usize,
height: usize,
},
UnsupportedAlphaBitDepth(u8),
IccProfileNotSupported,
Unsupported(&'static str),
BadChannelCount(usize),
BadBitDepth(u32),
IccNotSupported,
SizeOverflow,
InputLength {
expected: usize,
got: usize,
},
UnsupportedColorSpace(ColorSpace),
}
impl fmt::Display for EncodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::EmptyImage => write!(f, "image dimensions must be non-zero"),
Self::InputSizeMismatch { expected, actual } => write!(
f,
"input buffer size mismatch: expected {expected} bytes, got {actual}"
),
Self::AlphaSizeMismatch { expected, actual } => write!(
f,
"alpha plane size mismatch: expected {expected} pixels, got {actual}"
),
Self::InvalidDistance(d) => write!(
f,
"butteraugli distance must be a positive finite number, got {d}"
),
Self::QualityIsNaN => write!(f, "quality value must not be NaN"),
Self::DimensionTooLarge { width, height } => write!(
f,
"image dimensions {width}×{height} exceed the maximum ({})",
MAX_DIMENSION
),
Self::UnsupportedAlphaBitDepth(bits) => {
write!(
f,
"unsupported alpha bit depth: {bits} (must be 8, 10, or 12)"
)
}
Self::IccProfileNotSupported => {
write!(f, "ICC profile injection is not yet supported by jixel")
}
Self::Unsupported(msg) => write!(f, "unsupported: {msg}"),
Self::BadChannelCount(n) => write!(f, "channel count {} not in 1..=4", n),
Self::BadBitDepth(b) => write!(f, "bits_per_sample {} not in 1..=16", b),
Self::IccNotSupported => write!(
f,
"embedded ICC not yet supported; use an enumerated colour space"
),
Self::SizeOverflow => write!(f, "image size overflows usize"),
Self::InputLength { expected, got } => {
write!(f, "input length {} != expected {}", got, expected)
}
EncodeError::UnsupportedColorSpace(colorspace) => {
f.write_fmt(format_args!("unsupported color space: {:?}", colorspace))
}
}
}
}
impl std::error::Error for EncodeError {}
pub(crate) trait FastRound {
fn fast_round(self) -> Self;
}
impl FastRound for f32 {
fn fast_round(self) -> Self {
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "sse4.1"
))]
{
const MAGIC: f32 = ((1u32 << 23) + (1u32 << 22)) as f32;
(f32::from_bits(self.to_bits() + 1) + MAGIC) - MAGIC
}
#[cfg(target_arch = "aarch64")]
{
self.round()
}
#[cfg(not(any(
target_arch = "aarch64",
all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "sse4.1"
)
)))]
{
const MAGIC: f32 = ((1u32 << 23) + (1u32 << 22)) as f32;
(f32::from_bits(self.to_bits() + 1) + MAGIC) - MAGIC
}
}
}
pub(crate) fn f32_to_f16(value: f32) -> u16 {
let bits = value.to_bits();
let sign = ((bits >> 16) & 0x8000) as u16;
let exp = ((bits >> 23) & 0xFF) as i32 - 127; let mant = bits & 0x7F_FFFF; if value == 0.0 {
return sign;
}
if exp > 15 {
return sign | 0x7BFF; }
if exp < -24 {
return sign; }
if exp < -14 {
let shift = (14 - exp) as u32; let full = mant | 0x80_0000;
let half_mant = full >> (shift + 13);
let round = (full >> (shift + 12)) & 1;
return sign | ((half_mant as u16) + round as u16);
}
let e16 = (exp + 15) as u16;
let mant16 = (mant >> 13) as u16;
let remainder = mant & 0x1FFF; let half = 0x1000u32;
let mut out = (e16 << 10) | mant16;
if remainder > half || (remainder == half && (mant16 & 1) == 1) {
out += 1; }
if out >= 0x7C00 {
out = 0x7BFF; }
sign | out
}
#[cfg(test)]
mod f16_tests {
use super::f32_to_f16;
#[test]
fn known_hdr_luminances() {
assert_eq!(f32_to_f16(0.0), 0x0000);
assert_eq!(f32_to_f16(255.0), 0x5BF8);
assert_eq!(f32_to_f16(1000.0), 0x63D0);
assert_eq!(f32_to_f16(4000.0), 0x6BD0);
assert_eq!(f32_to_f16(10000.0), 0x70E2);
}
}