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