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 PacketParseFailure(&'static str),
280 InvalidProgressionIterator,
282 UnexpectedEof,
284 OutputBufferTooSmall,
286 HostAllocationFailed,
288}
289
290#[derive(Debug, Clone, Copy, PartialEq, Eq)]
292#[non_exhaustive]
293pub enum ColorError {
294 Mct,
296 PaletteResolutionFailed,
298 SyccConversionFailed,
300 LabConversionFailed,
302}
303
304#[derive(Debug, Clone, Copy, PartialEq, Eq)]
306#[non_exhaustive]
307pub enum DirectPlanUnsupportedReason {
308 GrayscaleImageWithoutAlpha,
310 GrayscaleSingleTileCodestream,
312 GrayscaleSingleComponentCodestream,
314 ColorRgbImageWithoutAlpha,
316 ColorSingleTileCodestream,
318 ColorThreeComponentRgbCodestream,
320 RgbaRgbImageWithAlpha,
322 RgbaFourComponentRgbCodestream,
324 ComponentIndexOutOfRange,
326 ComponentUnitSampled,
328 ComponentDecompositionIndexOutOfRange,
330 MixedCodeBlockCoding,
332 ComponentGridFullImage,
334}
335
336impl fmt::Display for DecodeError {
337 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
338 match self {
339 Self::Format(e) => write!(f, "{e}"),
340 Self::Marker(e) => write!(f, "{e}"),
341 Self::Tile(e) => write!(f, "{e}"),
342 Self::Validation(e) => write!(f, "{e}"),
343 Self::Decoding(e) => write!(f, "{e}"),
344 Self::Color(e) => write!(f, "{e}"),
345 Self::AllocationTooLarge {
346 what,
347 requested,
348 cap,
349 } => write!(
350 f,
351 "{what} requires {requested} live host bytes, exceeding the {cap}-byte cap"
352 ),
353 Self::HostAllocationFailed { what, bytes } => {
354 write!(
355 f,
356 "host allocation failed for {bytes} bytes while allocating {what}"
357 )
358 }
359 }
360 }
361}
362
363impl fmt::Display for EncodeError {
364 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
365 match self {
366 Self::InvalidInput { what } => write!(f, "invalid encode input: {what}"),
367 Self::Unsupported { what } => write!(f, "unsupported encode request: {what}"),
368 Self::ArithmeticOverflow { what } => {
369 write!(f, "encode size overflow while planning {what}")
370 }
371 Self::AllocationTooLarge {
372 what,
373 requested,
374 cap,
375 } => write!(
376 f,
377 "{what} requires {requested} live host bytes, exceeding the {cap}-byte cap"
378 ),
379 Self::HostAllocationFailed { what, bytes } => {
380 write!(
381 f,
382 "host allocation failed for {bytes} bytes while allocating {what}"
383 )
384 }
385 Self::Accelerator { operation, source } => {
386 write!(f, "encode accelerator failed during {operation}: {source}")
387 }
388 Self::CodestreamValidation { detail } => {
389 write!(f, "generated codestream validation failed: {detail}")
390 }
391 Self::InternalInvariant { what } => {
392 write!(f, "native encode invariant failed: {what}")
393 }
394 }
395 }
396}
397
398impl fmt::Display for FormatError {
399 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
400 match self {
401 Self::TooShort { need, have } => {
402 write!(f, "input too short: need {need} bytes, have {have}")
403 }
404 Self::TruncatedAt { offset, segment } => {
405 write!(
406 f,
407 "input truncated at offset {offset} while reading {segment}"
408 )
409 }
410 Self::InvalidSignature => write!(f, "invalid JP2 signature"),
411 Self::InvalidFileType => write!(f, "invalid JP2 file type"),
412 Self::InvalidBox => write!(f, "invalid JP2 box"),
413 Self::MissingRequiredBox(box_type) => write!(f, "missing required JP2 box {box_type}"),
414 Self::MissingCodestream => write!(f, "missing codestream data"),
415 Self::Unsupported => write!(f, "unsupported JP2 image"),
416 }
417 }
418}
419
420impl fmt::Display for MarkerError {
421 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
422 match self {
423 Self::Invalid => write!(f, "invalid marker"),
424 Self::Unsupported => write!(f, "unsupported marker"),
425 Self::Expected(marker) => write!(f, "expected {marker} marker"),
426 Self::Missing(marker) => write!(f, "missing {marker} marker"),
427 Self::ParseFailure(marker) => write!(f, "failed to parse {marker} marker"),
428 }
429 }
430}
431
432impl fmt::Display for TileError {
433 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
434 match self {
435 Self::Invalid => write!(f, "image contains no tiles"),
436 Self::InvalidIndex => write!(f, "invalid tile index in tile-part header"),
437 Self::InvalidOffsets => write!(f, "invalid tile offsets"),
438 Self::PpmPptConflict => {
439 write!(
440 f,
441 "PPT marker present when PPM marker exists in main header"
442 )
443 }
444 }
445 }
446}
447
448impl fmt::Display for ValidationError {
449 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
450 match self {
451 Self::InvalidDimensions => write!(f, "invalid image dimensions"),
452 Self::ImageTooLarge => write!(f, "image is too large"),
453 Self::TooManyChannels => write!(f, "image has too many channels"),
454 Self::TooManyTiles => write!(f, "image has too many tiles"),
455 Self::InvalidComponentMetadata => write!(f, "invalid component metadata"),
456 Self::InvalidChannelDefinition => write!(f, "invalid channel definition"),
457 Self::InvalidProgressionOrder => write!(f, "invalid progression order"),
458 Self::InvalidTransformation => write!(f, "invalid transformation type"),
459 Self::InvalidQuantizationStyle => write!(f, "invalid quantization style"),
460 Self::MissingPrecinctExponents => {
461 write!(f, "missing exponents for precinct sizes")
462 }
463 Self::InsufficientExponents => {
464 write!(f, "not enough exponents provided in header")
465 }
466 Self::MissingStepSize => write!(f, "missing exponent step size"),
467 Self::InvalidExponents => write!(f, "invalid quantization exponents"),
468 }
469 }
470}
471
472impl fmt::Display for DecodingError {
473 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
474 match self {
475 Self::CodeBlockDecodeFailure => write!(f, "failed to decode code-block"),
476 Self::CodeBlockDecodeFailureWithContext(context) => {
477 write!(f, "failed to decode code-block: {context}")
478 }
479 Self::DirectPlanUnsupported(reason) => {
480 write!(f, "unsupported decoding feature: {reason}")
481 }
482 Self::UnsupportedFeature(feature) => {
483 write!(f, "unsupported decoding feature: {feature}")
484 }
485 Self::TooManyBitplanes => write!(f, "number of bitplanes is too large"),
486 Self::TooManyCodingPasses => {
487 write!(f, "code-block contains too many coding passes")
488 }
489 Self::InvalidBitplaneCount => write!(f, "invalid number of bitplanes"),
490 Self::InvalidPrecinct => write!(f, "a precinct was invalid"),
491 Self::PacketParseFailure(context) => {
492 write!(f, "failed to parse JPEG 2000 packet data: {context}")
493 }
494 Self::InvalidProgressionIterator => {
495 write!(f, "a progression iterator was invalid")
496 }
497 Self::UnexpectedEof => write!(f, "unexpected end of data"),
498 Self::OutputBufferTooSmall => write!(f, "output buffer is too small"),
499 Self::HostAllocationFailed => write!(f, "host decode workspace allocation failed"),
500 }
501 }
502}
503
504impl fmt::Display for DirectPlanUnsupportedReason {
505 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
506 f.write_str(direct_plan_unsupported_what(*self))
507 }
508}
509
510const fn direct_plan_unsupported_what(reason: DirectPlanUnsupportedReason) -> &'static str {
511 match reason {
512 DirectPlanUnsupportedReason::GrayscaleImageWithoutAlpha => {
513 "direct grayscale plan only supports grayscale images without alpha"
514 }
515 DirectPlanUnsupportedReason::GrayscaleSingleTileCodestream => {
516 "direct grayscale plan only supports single-tile codestreams"
517 }
518 DirectPlanUnsupportedReason::GrayscaleSingleComponentCodestream => {
519 "direct grayscale plan only supports single-component codestreams"
520 }
521 DirectPlanUnsupportedReason::ColorRgbImageWithoutAlpha => {
522 "direct color plan only supports RGB images without alpha"
523 }
524 DirectPlanUnsupportedReason::ColorSingleTileCodestream => {
525 "direct color plan only supports single-tile codestreams"
526 }
527 DirectPlanUnsupportedReason::ColorThreeComponentRgbCodestream => {
528 "direct color plan only supports three-component RGB codestreams"
529 }
530 DirectPlanUnsupportedReason::RgbaRgbImageWithAlpha => {
531 "direct RGBA plan only supports RGB images with explicit alpha"
532 }
533 DirectPlanUnsupportedReason::RgbaFourComponentRgbCodestream => {
534 "direct RGBA plan only supports four-component RGB-alpha codestreams"
535 }
536 DirectPlanUnsupportedReason::ComponentIndexOutOfRange => {
537 "direct component plan index is out of range"
538 }
539 DirectPlanUnsupportedReason::ComponentGridFullImage => {
540 "component-grid plans require a full unsigned origin-zero image without MCT"
541 }
542 DirectPlanUnsupportedReason::ComponentUnitSampled => {
543 "direct component plan only supports unit-sampled components"
544 }
545 DirectPlanUnsupportedReason::ComponentDecompositionIndexOutOfRange => {
546 "direct component decomposition index is out of range"
547 }
548 DirectPlanUnsupportedReason::MixedCodeBlockCoding => {
549 "direct device plan does not support mixed classic and HT code-block coding in one sub-band"
550 }
551 }
552}
553
554impl fmt::Display for ColorError {
555 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
556 match self {
557 Self::Mct => write!(f, "multi-component transform failed"),
558 Self::PaletteResolutionFailed => write!(f, "failed to resolve palette indices"),
559 Self::SyccConversionFailed => write!(f, "failed to convert from sYCC to RGB"),
560 Self::LabConversionFailed => write!(f, "failed to convert from LAB to RGB"),
561 }
562 }
563}
564
565impl core::error::Error for DecodeError {}
566impl core::error::Error for EncodeError {
567 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
568 match self {
569 Self::Accelerator { source, .. } => Some(source),
570 _ => None,
571 }
572 }
573}
574impl core::error::Error for FormatError {}
575impl core::error::Error for MarkerError {}
576impl core::error::Error for TileError {}
577impl core::error::Error for ValidationError {}
578impl core::error::Error for DecodingError {}
579impl core::error::Error for DirectPlanUnsupportedReason {}
580impl core::error::Error for ColorError {}
581
582impl From<FormatError> for DecodeError {
583 fn from(e: FormatError) -> Self {
584 Self::Format(e)
585 }
586}
587
588impl From<MarkerError> for DecodeError {
589 fn from(e: MarkerError) -> Self {
590 Self::Marker(e)
591 }
592}
593
594impl From<TileError> for DecodeError {
595 fn from(e: TileError) -> Self {
596 Self::Tile(e)
597 }
598}
599
600impl From<ValidationError> for DecodeError {
601 fn from(e: ValidationError) -> Self {
602 Self::Validation(e)
603 }
604}
605
606impl From<j2k_types::DecodePlanAllocationError> for DecodeError {
607 fn from(_: j2k_types::DecodePlanAllocationError) -> Self {
608 Self::Validation(ValidationError::ImageTooLarge)
609 }
610}
611
612impl From<DecodingError> for DecodeError {
613 fn from(e: DecodingError) -> Self {
614 Self::Decoding(e)
615 }
616}
617
618impl From<ColorError> for DecodeError {
619 fn from(e: ColorError) -> Self {
620 Self::Color(e)
621 }
622}
623
624pub type Result<T> = core::result::Result<T, DecodeError>;
626
627pub type EncodeResult<T> = core::result::Result<T, EncodeError>;
629
630macro_rules! bail {
631 ($err:expr) => {
632 return Err($err.into())
633 };
634}
635
636macro_rules! err {
637 ($err:expr) => {
638 Err($err.into())
639 };
640}
641
642pub(crate) use bail;
643pub(crate) use err;
644
645#[cfg(test)]
646mod classification_tests {
647 use alloc::string::ToString;
648
649 use super::{
650 DecodeError, DecodeErrorClass, DecodingError, DirectPlanUnsupportedReason, EncodeError,
651 FormatError, MarkerError, ValidationError,
652 };
653
654 #[test]
655 fn facade_classification_preserves_structured_input_and_support_details() {
656 let cases = [
657 (
658 DecodeError::Format(FormatError::TooShort { need: 9, have: 3 }),
659 DecodeErrorClass::InputTooShort { need: 9, have: 3 },
660 ),
661 (
662 DecodeError::Format(FormatError::TruncatedAt {
663 offset: 17,
664 segment: "SIZ",
665 }),
666 DecodeErrorClass::InputTruncatedAt {
667 offset: 17,
668 segment: "SIZ",
669 },
670 ),
671 (
672 DecodeError::Format(FormatError::Unsupported),
673 DecodeErrorClass::Unsupported {
674 what: "JP2 image format",
675 },
676 ),
677 (
678 DecodeError::Marker(MarkerError::Unsupported),
679 DecodeErrorClass::Unsupported {
680 what: "JPEG 2000 marker",
681 },
682 ),
683 (
684 DecodeError::Decoding(DecodingError::UnsupportedFeature("packet marker")),
685 DecodeErrorClass::Unsupported {
686 what: "packet marker",
687 },
688 ),
689 (
690 DecodeError::Decoding(DecodingError::UnexpectedEof),
691 DecodeErrorClass::InputTruncatedAt {
692 offset: 0,
693 segment: "JPEG 2000 entropy data",
694 },
695 ),
696 (
697 DecodeError::Validation(ValidationError::InvalidDimensions),
698 DecodeErrorClass::Backend,
699 ),
700 ];
701
702 for (error, expected) in cases {
703 assert_eq!(error.classify(), expected, "{error}");
704 }
705 }
706
707 #[test]
708 fn direct_plan_classification_and_display_share_the_same_label() {
709 let reason = DirectPlanUnsupportedReason::ColorThreeComponentRgbCodestream;
710 let error = DecodeError::Decoding(DecodingError::DirectPlanUnsupported(reason));
711 let DecodeErrorClass::Unsupported { what } = error.classify() else {
712 panic!("direct-plan errors must classify as unsupported");
713 };
714
715 assert_eq!(what, reason.to_string());
716 }
717
718 #[test]
719 fn encode_resource_errors_keep_cap_and_allocator_failures_distinct() {
720 let cap_error = EncodeError::AllocationTooLarge {
721 what: "Tier-2 packet assembly",
722 requested: 513,
723 cap: 512,
724 };
725 let allocation_error = EncodeError::HostAllocationFailed {
726 what: "Tier-2 packet body",
727 bytes: 511,
728 };
729
730 assert!(cap_error.to_string().contains("513"));
731 assert!(cap_error.to_string().contains("512-byte cap"));
732 assert!(allocation_error.to_string().contains("511"));
733 assert_ne!(cap_error, allocation_error);
734 }
735}