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