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