use core::fmt;
use j2k_types::J2kEncodeStageError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum DecodeError {
Format(FormatError),
Marker(MarkerError),
Tile(TileError),
Validation(ValidationError),
Decoding(DecodingError),
Color(ColorError),
AllocationTooLarge {
what: &'static str,
requested: usize,
cap: usize,
},
HostAllocationFailed {
what: &'static str,
bytes: usize,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum DecodeErrorClass {
InputTooShort {
need: usize,
have: usize,
},
InputTruncatedAt {
offset: usize,
segment: &'static str,
},
Unsupported {
what: &'static str,
},
Backend,
}
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum EncodeError {
InvalidInput {
what: &'static str,
},
Unsupported {
what: &'static str,
},
ArithmeticOverflow {
what: &'static str,
},
AllocationTooLarge {
what: &'static str,
requested: usize,
cap: usize,
},
HostAllocationFailed {
what: &'static str,
bytes: usize,
},
Accelerator {
operation: &'static str,
source: J2kEncodeStageError,
},
CodestreamValidation {
detail: &'static str,
},
InternalInvariant {
what: &'static str,
},
}
impl DecodeError {
#[must_use]
pub const fn classify(&self) -> DecodeErrorClass {
match *self {
Self::Format(FormatError::TooShort { need, have }) => {
DecodeErrorClass::InputTooShort { need, have }
}
Self::Format(FormatError::TruncatedAt { offset, segment }) => {
DecodeErrorClass::InputTruncatedAt { offset, segment }
}
Self::Format(FormatError::Unsupported) => DecodeErrorClass::Unsupported {
what: "JP2 image format",
},
Self::Marker(MarkerError::Unsupported) => DecodeErrorClass::Unsupported {
what: "JPEG 2000 marker",
},
Self::Decoding(DecodingError::DirectPlanUnsupported(reason)) => {
DecodeErrorClass::Unsupported {
what: direct_plan_unsupported_what(reason),
}
}
Self::Decoding(DecodingError::UnsupportedFeature(what)) => {
DecodeErrorClass::Unsupported { what }
}
Self::Decoding(DecodingError::UnexpectedEof) => DecodeErrorClass::InputTruncatedAt {
offset: 0,
segment: "JPEG 2000 entropy data",
},
_ => DecodeErrorClass::Backend,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum FormatError {
TooShort {
need: usize,
have: usize,
},
TruncatedAt {
offset: usize,
segment: &'static str,
},
InvalidSignature,
InvalidFileType,
InvalidBox,
MissingRequiredBox(&'static str),
MissingCodestream,
Unsupported,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum MarkerError {
Invalid,
Unsupported,
Expected(&'static str),
Missing(&'static str),
ParseFailure(&'static str),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum TileError {
Invalid,
InvalidIndex,
InvalidOffsets,
PpmPptConflict,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ValidationError {
InvalidDimensions,
ImageTooLarge,
TooManyChannels,
TooManyTiles,
InvalidComponentMetadata,
InvalidChannelDefinition,
InvalidProgressionOrder,
InvalidTransformation,
InvalidQuantizationStyle,
MissingPrecinctExponents,
InsufficientExponents,
MissingStepSize,
InvalidExponents,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum DecodingError {
CodeBlockDecodeFailure,
CodeBlockDecodeFailureWithContext(&'static str),
DirectPlanUnsupported(DirectPlanUnsupportedReason),
UnsupportedFeature(&'static str),
TooManyBitplanes,
TooManyCodingPasses,
InvalidBitplaneCount,
InvalidPrecinct,
InvalidProgressionIterator,
UnexpectedEof,
OutputBufferTooSmall,
HostAllocationFailed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ColorError {
Mct,
PaletteResolutionFailed,
SyccConversionFailed,
LabConversionFailed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum DirectPlanUnsupportedReason {
GrayscaleImageWithoutAlpha,
GrayscaleSingleTileCodestream,
GrayscaleSingleComponentCodestream,
ColorRgbImageWithoutAlpha,
ColorSingleTileCodestream,
ColorThreeComponentRgbCodestream,
ComponentIndexOutOfRange,
ComponentUnitSampled,
ComponentDecompositionIndexOutOfRange,
}
impl fmt::Display for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Format(e) => write!(f, "{e}"),
Self::Marker(e) => write!(f, "{e}"),
Self::Tile(e) => write!(f, "{e}"),
Self::Validation(e) => write!(f, "{e}"),
Self::Decoding(e) => write!(f, "{e}"),
Self::Color(e) => write!(f, "{e}"),
Self::AllocationTooLarge {
what,
requested,
cap,
} => write!(
f,
"{what} requires {requested} live host bytes, exceeding the {cap}-byte cap"
),
Self::HostAllocationFailed { what, bytes } => {
write!(
f,
"host allocation failed for {bytes} bytes while allocating {what}"
)
}
}
}
}
impl fmt::Display for EncodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidInput { what } => write!(f, "invalid encode input: {what}"),
Self::Unsupported { what } => write!(f, "unsupported encode request: {what}"),
Self::ArithmeticOverflow { what } => {
write!(f, "encode size overflow while planning {what}")
}
Self::AllocationTooLarge {
what,
requested,
cap,
} => write!(
f,
"{what} requires {requested} live host bytes, exceeding the {cap}-byte cap"
),
Self::HostAllocationFailed { what, bytes } => {
write!(
f,
"host allocation failed for {bytes} bytes while allocating {what}"
)
}
Self::Accelerator { operation, source } => {
write!(f, "encode accelerator failed during {operation}: {source}")
}
Self::CodestreamValidation { detail } => {
write!(f, "generated codestream validation failed: {detail}")
}
Self::InternalInvariant { what } => {
write!(f, "native encode invariant failed: {what}")
}
}
}
}
impl fmt::Display for FormatError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::TooShort { need, have } => {
write!(f, "input too short: need {need} bytes, have {have}")
}
Self::TruncatedAt { offset, segment } => {
write!(
f,
"input truncated at offset {offset} while reading {segment}"
)
}
Self::InvalidSignature => write!(f, "invalid JP2 signature"),
Self::InvalidFileType => write!(f, "invalid JP2 file type"),
Self::InvalidBox => write!(f, "invalid JP2 box"),
Self::MissingRequiredBox(box_type) => write!(f, "missing required JP2 box {box_type}"),
Self::MissingCodestream => write!(f, "missing codestream data"),
Self::Unsupported => write!(f, "unsupported JP2 image"),
}
}
}
impl fmt::Display for MarkerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Invalid => write!(f, "invalid marker"),
Self::Unsupported => write!(f, "unsupported marker"),
Self::Expected(marker) => write!(f, "expected {marker} marker"),
Self::Missing(marker) => write!(f, "missing {marker} marker"),
Self::ParseFailure(marker) => write!(f, "failed to parse {marker} marker"),
}
}
}
impl fmt::Display for TileError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Invalid => write!(f, "image contains no tiles"),
Self::InvalidIndex => write!(f, "invalid tile index in tile-part header"),
Self::InvalidOffsets => write!(f, "invalid tile offsets"),
Self::PpmPptConflict => {
write!(
f,
"PPT marker present when PPM marker exists in main header"
)
}
}
}
}
impl fmt::Display for ValidationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidDimensions => write!(f, "invalid image dimensions"),
Self::ImageTooLarge => write!(f, "image is too large"),
Self::TooManyChannels => write!(f, "image has too many channels"),
Self::TooManyTiles => write!(f, "image has too many tiles"),
Self::InvalidComponentMetadata => write!(f, "invalid component metadata"),
Self::InvalidChannelDefinition => write!(f, "invalid channel definition"),
Self::InvalidProgressionOrder => write!(f, "invalid progression order"),
Self::InvalidTransformation => write!(f, "invalid transformation type"),
Self::InvalidQuantizationStyle => write!(f, "invalid quantization style"),
Self::MissingPrecinctExponents => {
write!(f, "missing exponents for precinct sizes")
}
Self::InsufficientExponents => {
write!(f, "not enough exponents provided in header")
}
Self::MissingStepSize => write!(f, "missing exponent step size"),
Self::InvalidExponents => write!(f, "invalid quantization exponents"),
}
}
}
impl fmt::Display for DecodingError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::CodeBlockDecodeFailure => write!(f, "failed to decode code-block"),
Self::CodeBlockDecodeFailureWithContext(context) => {
write!(f, "failed to decode code-block: {context}")
}
Self::DirectPlanUnsupported(reason) => {
write!(f, "unsupported decoding feature: {reason}")
}
Self::UnsupportedFeature(feature) => {
write!(f, "unsupported decoding feature: {feature}")
}
Self::TooManyBitplanes => write!(f, "number of bitplanes is too large"),
Self::TooManyCodingPasses => {
write!(f, "code-block contains too many coding passes")
}
Self::InvalidBitplaneCount => write!(f, "invalid number of bitplanes"),
Self::InvalidPrecinct => write!(f, "a precinct was invalid"),
Self::InvalidProgressionIterator => {
write!(f, "a progression iterator was invalid")
}
Self::UnexpectedEof => write!(f, "unexpected end of data"),
Self::OutputBufferTooSmall => write!(f, "output buffer is too small"),
Self::HostAllocationFailed => write!(f, "host decode workspace allocation failed"),
}
}
}
impl fmt::Display for DirectPlanUnsupportedReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(direct_plan_unsupported_what(*self))
}
}
const fn direct_plan_unsupported_what(reason: DirectPlanUnsupportedReason) -> &'static str {
match reason {
DirectPlanUnsupportedReason::GrayscaleImageWithoutAlpha => {
"direct grayscale plan only supports grayscale images without alpha"
}
DirectPlanUnsupportedReason::GrayscaleSingleTileCodestream => {
"direct grayscale plan only supports single-tile codestreams"
}
DirectPlanUnsupportedReason::GrayscaleSingleComponentCodestream => {
"direct grayscale plan only supports single-component codestreams"
}
DirectPlanUnsupportedReason::ColorRgbImageWithoutAlpha => {
"direct color plan only supports RGB images without alpha"
}
DirectPlanUnsupportedReason::ColorSingleTileCodestream => {
"direct color plan only supports single-tile codestreams"
}
DirectPlanUnsupportedReason::ColorThreeComponentRgbCodestream => {
"direct color plan only supports three-component RGB codestreams"
}
DirectPlanUnsupportedReason::ComponentIndexOutOfRange => {
"direct component plan index is out of range"
}
DirectPlanUnsupportedReason::ComponentUnitSampled => {
"direct component plan only supports unit-sampled components"
}
DirectPlanUnsupportedReason::ComponentDecompositionIndexOutOfRange => {
"direct component decomposition index is out of range"
}
}
}
impl fmt::Display for ColorError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Mct => write!(f, "multi-component transform failed"),
Self::PaletteResolutionFailed => write!(f, "failed to resolve palette indices"),
Self::SyccConversionFailed => write!(f, "failed to convert from sYCC to RGB"),
Self::LabConversionFailed => write!(f, "failed to convert from LAB to RGB"),
}
}
}
impl core::error::Error for DecodeError {}
impl core::error::Error for EncodeError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
Self::Accelerator { source, .. } => Some(source),
_ => None,
}
}
}
impl core::error::Error for FormatError {}
impl core::error::Error for MarkerError {}
impl core::error::Error for TileError {}
impl core::error::Error for ValidationError {}
impl core::error::Error for DecodingError {}
impl core::error::Error for DirectPlanUnsupportedReason {}
impl core::error::Error for ColorError {}
impl From<FormatError> for DecodeError {
fn from(e: FormatError) -> Self {
Self::Format(e)
}
}
impl From<MarkerError> for DecodeError {
fn from(e: MarkerError) -> Self {
Self::Marker(e)
}
}
impl From<TileError> for DecodeError {
fn from(e: TileError) -> Self {
Self::Tile(e)
}
}
impl From<ValidationError> for DecodeError {
fn from(e: ValidationError) -> Self {
Self::Validation(e)
}
}
impl From<DecodingError> for DecodeError {
fn from(e: DecodingError) -> Self {
Self::Decoding(e)
}
}
impl From<ColorError> for DecodeError {
fn from(e: ColorError) -> Self {
Self::Color(e)
}
}
pub type Result<T> = core::result::Result<T, DecodeError>;
pub type EncodeResult<T> = core::result::Result<T, EncodeError>;
macro_rules! bail {
($err:expr) => {
return Err($err.into())
};
}
macro_rules! err {
($err:expr) => {
Err($err.into())
};
}
pub(crate) use bail;
pub(crate) use err;
#[cfg(test)]
mod classification_tests {
use alloc::string::ToString;
use super::{
DecodeError, DecodeErrorClass, DecodingError, DirectPlanUnsupportedReason, EncodeError,
FormatError, MarkerError, ValidationError,
};
#[test]
fn facade_classification_preserves_structured_input_and_support_details() {
let cases = [
(
DecodeError::Format(FormatError::TooShort { need: 9, have: 3 }),
DecodeErrorClass::InputTooShort { need: 9, have: 3 },
),
(
DecodeError::Format(FormatError::TruncatedAt {
offset: 17,
segment: "SIZ",
}),
DecodeErrorClass::InputTruncatedAt {
offset: 17,
segment: "SIZ",
},
),
(
DecodeError::Format(FormatError::Unsupported),
DecodeErrorClass::Unsupported {
what: "JP2 image format",
},
),
(
DecodeError::Marker(MarkerError::Unsupported),
DecodeErrorClass::Unsupported {
what: "JPEG 2000 marker",
},
),
(
DecodeError::Decoding(DecodingError::UnsupportedFeature("packet marker")),
DecodeErrorClass::Unsupported {
what: "packet marker",
},
),
(
DecodeError::Decoding(DecodingError::UnexpectedEof),
DecodeErrorClass::InputTruncatedAt {
offset: 0,
segment: "JPEG 2000 entropy data",
},
),
(
DecodeError::Validation(ValidationError::InvalidDimensions),
DecodeErrorClass::Backend,
),
];
for (error, expected) in cases {
assert_eq!(error.classify(), expected, "{error}");
}
}
#[test]
fn direct_plan_classification_and_display_share_the_same_label() {
let reason = DirectPlanUnsupportedReason::ColorThreeComponentRgbCodestream;
let error = DecodeError::Decoding(DecodingError::DirectPlanUnsupported(reason));
let DecodeErrorClass::Unsupported { what } = error.classify() else {
panic!("direct-plan errors must classify as unsupported");
};
assert_eq!(what, reason.to_string());
}
#[test]
fn encode_resource_errors_keep_cap_and_allocator_failures_distinct() {
let cap_error = EncodeError::AllocationTooLarge {
what: "Tier-2 packet assembly",
requested: 513,
cap: 512,
};
let allocation_error = EncodeError::HostAllocationFailed {
what: "Tier-2 packet body",
bytes: 511,
};
assert!(cap_error.to_string().contains("513"));
assert!(cap_error.to_string().contains("512-byte cap"));
assert!(allocation_error.to_string().contains("511"));
assert_ne!(cap_error, allocation_error);
}
}