1use core::fmt;
4use j2k_types::J2kEncodeStageError;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8#[non_exhaustive]
9pub enum DecodeError {
10 Format(FormatError),
12 Marker(MarkerError),
14 Tile(TileError),
16 Validation(ValidationError),
18 Decoding(DecodingError),
20 Color(ColorError),
22 AllocationTooLarge {
24 what: &'static str,
26 requested: usize,
28 cap: usize,
30 },
31 HostAllocationFailed {
33 what: &'static str,
35 bytes: usize,
37 },
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46#[non_exhaustive]
47pub enum DecodeErrorClass {
48 InputTooShort {
50 need: usize,
52 have: usize,
54 },
55 InputTruncatedAt {
57 offset: usize,
59 segment: &'static str,
61 },
62 Unsupported {
64 what: &'static str,
66 },
67 Backend,
69}
70
71#[derive(Debug, PartialEq, Eq)]
78#[non_exhaustive]
79pub enum EncodeError {
80 InvalidInput {
82 what: &'static str,
84 },
85 Unsupported {
87 what: &'static str,
89 },
90 ArithmeticOverflow {
92 what: &'static str,
94 },
95 AllocationTooLarge {
97 what: &'static str,
99 requested: usize,
101 cap: usize,
103 },
104 HostAllocationFailed {
106 what: &'static str,
108 bytes: usize,
110 },
111 Accelerator {
113 operation: &'static str,
115 source: J2kEncodeStageError,
117 },
118 CodestreamValidation {
120 detail: &'static str,
122 },
123 InternalInvariant {
125 what: &'static str,
127 },
128}
129
130impl DecodeError {
131 #[must_use]
133 pub const fn classify(&self) -> DecodeErrorClass {
134 match *self {
135 Self::Format(FormatError::TooShort { need, have }) => {
136 DecodeErrorClass::InputTooShort { need, have }
137 }
138 Self::Format(FormatError::TruncatedAt { offset, segment }) => {
139 DecodeErrorClass::InputTruncatedAt { offset, segment }
140 }
141 Self::Format(FormatError::Unsupported) => DecodeErrorClass::Unsupported {
142 what: "JP2 image format",
143 },
144 Self::Marker(MarkerError::Unsupported) => DecodeErrorClass::Unsupported {
145 what: "JPEG 2000 marker",
146 },
147 Self::Decoding(DecodingError::DirectPlanUnsupported(reason)) => {
148 DecodeErrorClass::Unsupported {
149 what: direct_plan_unsupported_what(reason),
150 }
151 }
152 Self::Decoding(DecodingError::UnsupportedFeature(what)) => {
153 DecodeErrorClass::Unsupported { what }
154 }
155 Self::Decoding(DecodingError::UnexpectedEof) => DecodeErrorClass::InputTruncatedAt {
156 offset: 0,
157 segment: "JPEG 2000 entropy data",
158 },
159 _ => DecodeErrorClass::Backend,
160 }
161 }
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq)]
166#[non_exhaustive]
167pub enum FormatError {
168 TooShort {
170 need: usize,
172 have: usize,
174 },
175 TruncatedAt {
177 offset: usize,
179 segment: &'static str,
181 },
182 InvalidSignature,
184 InvalidFileType,
186 InvalidBox,
188 MissingRequiredBox(&'static str),
190 MissingCodestream,
192 Unsupported,
194}
195
196#[derive(Debug, Clone, Copy, PartialEq, Eq)]
198#[non_exhaustive]
199pub enum MarkerError {
200 Invalid,
202 Unsupported,
204 Expected(&'static str),
206 Missing(&'static str),
208 ParseFailure(&'static str),
210}
211
212#[derive(Debug, Clone, Copy, PartialEq, Eq)]
214#[non_exhaustive]
215pub enum TileError {
216 Invalid,
218 InvalidIndex,
220 InvalidOffsets,
222 PpmPptConflict,
224}
225
226#[derive(Debug, Clone, Copy, PartialEq, Eq)]
228#[non_exhaustive]
229pub enum ValidationError {
230 InvalidDimensions,
232 ImageTooLarge,
234 TooManyChannels,
236 TooManyTiles,
238 InvalidComponentMetadata,
240 InvalidChannelDefinition,
242 InvalidProgressionOrder,
244 InvalidTransformation,
246 InvalidQuantizationStyle,
248 MissingPrecinctExponents,
250 InsufficientExponents,
252 MissingStepSize,
254 InvalidExponents,
256}
257
258#[derive(Debug, Clone, Copy, PartialEq, Eq)]
260#[non_exhaustive]
261pub enum DecodingError {
262 CodeBlockDecodeFailure,
264 CodeBlockDecodeFailureWithContext(&'static str),
266 DirectPlanUnsupported(DirectPlanUnsupportedReason),
268 UnsupportedFeature(&'static str),
270 TooManyBitplanes,
272 TooManyCodingPasses,
274 InvalidBitplaneCount,
276 InvalidPrecinct,
278 InvalidProgressionIterator,
280 UnexpectedEof,
282 OutputBufferTooSmall,
284 HostAllocationFailed,
286}
287
288#[derive(Debug, Clone, Copy, PartialEq, Eq)]
290#[non_exhaustive]
291pub enum ColorError {
292 Mct,
294 PaletteResolutionFailed,
296 SyccConversionFailed,
298 LabConversionFailed,
300}
301
302#[derive(Debug, Clone, Copy, PartialEq, Eq)]
304#[non_exhaustive]
305pub enum DirectPlanUnsupportedReason {
306 GrayscaleImageWithoutAlpha,
308 GrayscaleSingleTileCodestream,
310 GrayscaleSingleComponentCodestream,
312 ColorRgbImageWithoutAlpha,
314 ColorSingleTileCodestream,
316 ColorThreeComponentRgbCodestream,
318 RgbaRgbImageWithAlpha,
320 RgbaFourComponentRgbCodestream,
322 ComponentIndexOutOfRange,
324 ComponentUnitSampled,
326 ComponentDecompositionIndexOutOfRange,
328}
329
330impl fmt::Display for DecodeError {
331 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
332 match self {
333 Self::Format(e) => write!(f, "{e}"),
334 Self::Marker(e) => write!(f, "{e}"),
335 Self::Tile(e) => write!(f, "{e}"),
336 Self::Validation(e) => write!(f, "{e}"),
337 Self::Decoding(e) => write!(f, "{e}"),
338 Self::Color(e) => write!(f, "{e}"),
339 Self::AllocationTooLarge {
340 what,
341 requested,
342 cap,
343 } => write!(
344 f,
345 "{what} requires {requested} live host bytes, exceeding the {cap}-byte cap"
346 ),
347 Self::HostAllocationFailed { what, bytes } => {
348 write!(
349 f,
350 "host allocation failed for {bytes} bytes while allocating {what}"
351 )
352 }
353 }
354 }
355}
356
357impl fmt::Display for EncodeError {
358 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
359 match self {
360 Self::InvalidInput { what } => write!(f, "invalid encode input: {what}"),
361 Self::Unsupported { what } => write!(f, "unsupported encode request: {what}"),
362 Self::ArithmeticOverflow { what } => {
363 write!(f, "encode size overflow while planning {what}")
364 }
365 Self::AllocationTooLarge {
366 what,
367 requested,
368 cap,
369 } => write!(
370 f,
371 "{what} requires {requested} live host bytes, exceeding the {cap}-byte cap"
372 ),
373 Self::HostAllocationFailed { what, bytes } => {
374 write!(
375 f,
376 "host allocation failed for {bytes} bytes while allocating {what}"
377 )
378 }
379 Self::Accelerator { operation, source } => {
380 write!(f, "encode accelerator failed during {operation}: {source}")
381 }
382 Self::CodestreamValidation { detail } => {
383 write!(f, "generated codestream validation failed: {detail}")
384 }
385 Self::InternalInvariant { what } => {
386 write!(f, "native encode invariant failed: {what}")
387 }
388 }
389 }
390}
391
392impl fmt::Display for FormatError {
393 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
394 match self {
395 Self::TooShort { need, have } => {
396 write!(f, "input too short: need {need} bytes, have {have}")
397 }
398 Self::TruncatedAt { offset, segment } => {
399 write!(
400 f,
401 "input truncated at offset {offset} while reading {segment}"
402 )
403 }
404 Self::InvalidSignature => write!(f, "invalid JP2 signature"),
405 Self::InvalidFileType => write!(f, "invalid JP2 file type"),
406 Self::InvalidBox => write!(f, "invalid JP2 box"),
407 Self::MissingRequiredBox(box_type) => write!(f, "missing required JP2 box {box_type}"),
408 Self::MissingCodestream => write!(f, "missing codestream data"),
409 Self::Unsupported => write!(f, "unsupported JP2 image"),
410 }
411 }
412}
413
414impl fmt::Display for MarkerError {
415 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
416 match self {
417 Self::Invalid => write!(f, "invalid marker"),
418 Self::Unsupported => write!(f, "unsupported marker"),
419 Self::Expected(marker) => write!(f, "expected {marker} marker"),
420 Self::Missing(marker) => write!(f, "missing {marker} marker"),
421 Self::ParseFailure(marker) => write!(f, "failed to parse {marker} marker"),
422 }
423 }
424}
425
426impl fmt::Display for TileError {
427 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
428 match self {
429 Self::Invalid => write!(f, "image contains no tiles"),
430 Self::InvalidIndex => write!(f, "invalid tile index in tile-part header"),
431 Self::InvalidOffsets => write!(f, "invalid tile offsets"),
432 Self::PpmPptConflict => {
433 write!(
434 f,
435 "PPT marker present when PPM marker exists in main header"
436 )
437 }
438 }
439 }
440}
441
442impl fmt::Display for ValidationError {
443 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
444 match self {
445 Self::InvalidDimensions => write!(f, "invalid image dimensions"),
446 Self::ImageTooLarge => write!(f, "image is too large"),
447 Self::TooManyChannels => write!(f, "image has too many channels"),
448 Self::TooManyTiles => write!(f, "image has too many tiles"),
449 Self::InvalidComponentMetadata => write!(f, "invalid component metadata"),
450 Self::InvalidChannelDefinition => write!(f, "invalid channel definition"),
451 Self::InvalidProgressionOrder => write!(f, "invalid progression order"),
452 Self::InvalidTransformation => write!(f, "invalid transformation type"),
453 Self::InvalidQuantizationStyle => write!(f, "invalid quantization style"),
454 Self::MissingPrecinctExponents => {
455 write!(f, "missing exponents for precinct sizes")
456 }
457 Self::InsufficientExponents => {
458 write!(f, "not enough exponents provided in header")
459 }
460 Self::MissingStepSize => write!(f, "missing exponent step size"),
461 Self::InvalidExponents => write!(f, "invalid quantization exponents"),
462 }
463 }
464}
465
466impl fmt::Display for DecodingError {
467 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
468 match self {
469 Self::CodeBlockDecodeFailure => write!(f, "failed to decode code-block"),
470 Self::CodeBlockDecodeFailureWithContext(context) => {
471 write!(f, "failed to decode code-block: {context}")
472 }
473 Self::DirectPlanUnsupported(reason) => {
474 write!(f, "unsupported decoding feature: {reason}")
475 }
476 Self::UnsupportedFeature(feature) => {
477 write!(f, "unsupported decoding feature: {feature}")
478 }
479 Self::TooManyBitplanes => write!(f, "number of bitplanes is too large"),
480 Self::TooManyCodingPasses => {
481 write!(f, "code-block contains too many coding passes")
482 }
483 Self::InvalidBitplaneCount => write!(f, "invalid number of bitplanes"),
484 Self::InvalidPrecinct => write!(f, "a precinct was invalid"),
485 Self::InvalidProgressionIterator => {
486 write!(f, "a progression iterator was invalid")
487 }
488 Self::UnexpectedEof => write!(f, "unexpected end of data"),
489 Self::OutputBufferTooSmall => write!(f, "output buffer is too small"),
490 Self::HostAllocationFailed => write!(f, "host decode workspace allocation failed"),
491 }
492 }
493}
494
495impl fmt::Display for DirectPlanUnsupportedReason {
496 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
497 f.write_str(direct_plan_unsupported_what(*self))
498 }
499}
500
501const fn direct_plan_unsupported_what(reason: DirectPlanUnsupportedReason) -> &'static str {
502 match reason {
503 DirectPlanUnsupportedReason::GrayscaleImageWithoutAlpha => {
504 "direct grayscale plan only supports grayscale images without alpha"
505 }
506 DirectPlanUnsupportedReason::GrayscaleSingleTileCodestream => {
507 "direct grayscale plan only supports single-tile codestreams"
508 }
509 DirectPlanUnsupportedReason::GrayscaleSingleComponentCodestream => {
510 "direct grayscale plan only supports single-component codestreams"
511 }
512 DirectPlanUnsupportedReason::ColorRgbImageWithoutAlpha => {
513 "direct color plan only supports RGB images without alpha"
514 }
515 DirectPlanUnsupportedReason::ColorSingleTileCodestream => {
516 "direct color plan only supports single-tile codestreams"
517 }
518 DirectPlanUnsupportedReason::ColorThreeComponentRgbCodestream => {
519 "direct color plan only supports three-component RGB codestreams"
520 }
521 DirectPlanUnsupportedReason::RgbaRgbImageWithAlpha => {
522 "direct RGBA plan only supports RGB images with explicit alpha"
523 }
524 DirectPlanUnsupportedReason::RgbaFourComponentRgbCodestream => {
525 "direct RGBA plan only supports four-component RGB-alpha codestreams"
526 }
527 DirectPlanUnsupportedReason::ComponentIndexOutOfRange => {
528 "direct component plan index is out of range"
529 }
530 DirectPlanUnsupportedReason::ComponentUnitSampled => {
531 "direct component plan only supports unit-sampled components"
532 }
533 DirectPlanUnsupportedReason::ComponentDecompositionIndexOutOfRange => {
534 "direct component decomposition index is out of range"
535 }
536 }
537}
538
539impl fmt::Display for ColorError {
540 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
541 match self {
542 Self::Mct => write!(f, "multi-component transform failed"),
543 Self::PaletteResolutionFailed => write!(f, "failed to resolve palette indices"),
544 Self::SyccConversionFailed => write!(f, "failed to convert from sYCC to RGB"),
545 Self::LabConversionFailed => write!(f, "failed to convert from LAB to RGB"),
546 }
547 }
548}
549
550impl core::error::Error for DecodeError {}
551impl core::error::Error for EncodeError {
552 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
553 match self {
554 Self::Accelerator { source, .. } => Some(source),
555 _ => None,
556 }
557 }
558}
559impl core::error::Error for FormatError {}
560impl core::error::Error for MarkerError {}
561impl core::error::Error for TileError {}
562impl core::error::Error for ValidationError {}
563impl core::error::Error for DecodingError {}
564impl core::error::Error for DirectPlanUnsupportedReason {}
565impl core::error::Error for ColorError {}
566
567impl From<FormatError> for DecodeError {
568 fn from(e: FormatError) -> Self {
569 Self::Format(e)
570 }
571}
572
573impl From<MarkerError> for DecodeError {
574 fn from(e: MarkerError) -> Self {
575 Self::Marker(e)
576 }
577}
578
579impl From<TileError> for DecodeError {
580 fn from(e: TileError) -> Self {
581 Self::Tile(e)
582 }
583}
584
585impl From<ValidationError> for DecodeError {
586 fn from(e: ValidationError) -> Self {
587 Self::Validation(e)
588 }
589}
590
591impl From<DecodingError> for DecodeError {
592 fn from(e: DecodingError) -> Self {
593 Self::Decoding(e)
594 }
595}
596
597impl From<ColorError> for DecodeError {
598 fn from(e: ColorError) -> Self {
599 Self::Color(e)
600 }
601}
602
603pub type Result<T> = core::result::Result<T, DecodeError>;
605
606pub type EncodeResult<T> = core::result::Result<T, EncodeError>;
608
609macro_rules! bail {
610 ($err:expr) => {
611 return Err($err.into())
612 };
613}
614
615macro_rules! err {
616 ($err:expr) => {
617 Err($err.into())
618 };
619}
620
621pub(crate) use bail;
622pub(crate) use err;
623
624#[cfg(test)]
625mod classification_tests {
626 use alloc::string::ToString;
627
628 use super::{
629 DecodeError, DecodeErrorClass, DecodingError, DirectPlanUnsupportedReason, EncodeError,
630 FormatError, MarkerError, ValidationError,
631 };
632
633 #[test]
634 fn facade_classification_preserves_structured_input_and_support_details() {
635 let cases = [
636 (
637 DecodeError::Format(FormatError::TooShort { need: 9, have: 3 }),
638 DecodeErrorClass::InputTooShort { need: 9, have: 3 },
639 ),
640 (
641 DecodeError::Format(FormatError::TruncatedAt {
642 offset: 17,
643 segment: "SIZ",
644 }),
645 DecodeErrorClass::InputTruncatedAt {
646 offset: 17,
647 segment: "SIZ",
648 },
649 ),
650 (
651 DecodeError::Format(FormatError::Unsupported),
652 DecodeErrorClass::Unsupported {
653 what: "JP2 image format",
654 },
655 ),
656 (
657 DecodeError::Marker(MarkerError::Unsupported),
658 DecodeErrorClass::Unsupported {
659 what: "JPEG 2000 marker",
660 },
661 ),
662 (
663 DecodeError::Decoding(DecodingError::UnsupportedFeature("packet marker")),
664 DecodeErrorClass::Unsupported {
665 what: "packet marker",
666 },
667 ),
668 (
669 DecodeError::Decoding(DecodingError::UnexpectedEof),
670 DecodeErrorClass::InputTruncatedAt {
671 offset: 0,
672 segment: "JPEG 2000 entropy data",
673 },
674 ),
675 (
676 DecodeError::Validation(ValidationError::InvalidDimensions),
677 DecodeErrorClass::Backend,
678 ),
679 ];
680
681 for (error, expected) in cases {
682 assert_eq!(error.classify(), expected, "{error}");
683 }
684 }
685
686 #[test]
687 fn direct_plan_classification_and_display_share_the_same_label() {
688 let reason = DirectPlanUnsupportedReason::ColorThreeComponentRgbCodestream;
689 let error = DecodeError::Decoding(DecodingError::DirectPlanUnsupported(reason));
690 let DecodeErrorClass::Unsupported { what } = error.classify() else {
691 panic!("direct-plan errors must classify as unsupported");
692 };
693
694 assert_eq!(what, reason.to_string());
695 }
696
697 #[test]
698 fn encode_resource_errors_keep_cap_and_allocator_failures_distinct() {
699 let cap_error = EncodeError::AllocationTooLarge {
700 what: "Tier-2 packet assembly",
701 requested: 513,
702 cap: 512,
703 };
704 let allocation_error = EncodeError::HostAllocationFailed {
705 what: "Tier-2 packet body",
706 bytes: 511,
707 };
708
709 assert!(cap_error.to_string().contains("513"));
710 assert!(cap_error.to_string().contains("512-byte cap"));
711 assert!(allocation_error.to_string().contains("511"));
712 assert_ne!(cap_error, allocation_error);
713 }
714}