ithmb_core/profile.rs
1//! Profile types — the Rust equivalent of `IthmbVariantProfile`.
2//!
3//! A profile describes how to decode a raw .ithmb frame identified by a 4-byte
4//! prefix (format ID). Each profile specifies dimensions, encoding, byte order,
5//! and optional post-processing (rotation, crop, padded slots).
6
7/// Known encoding variants for raw .ithmb frames.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9#[non_exhaustive]
10pub enum Encoding {
11 /// 16-bit RGB (5 bits red, 6 bits green, 5 bits blue).
12 Rgb565,
13 /// 15-bit RGB (5 bits per channel, 1 unused bit per pixel).
14 Rgb555,
15 /// Byte-swapped RGB555 variant used by some iPod models.
16 ReorderedRgb555,
17 /// UYVY 4:2:2 packed chroma subsampling.
18 Yuv422,
19 /// YCbCr 4:2:0 planar chroma subsampling.
20 Ycbcr420,
21 /// JPEG-compressed frame embedded within the raw file.
22 Jpeg,
23}
24
25/// Decoding profile for a raw .ithmb frame format.
26///
27/// Maps to `IthmbVariantProfile` in the C# implementation. Each field that
28/// defaults to `false`/`0`/`None` can be omitted — the decoder uses the
29/// standard behaviour unless overridden.
30#[derive(Debug, Clone)]
31#[allow(clippy::struct_excessive_bools)]
32pub struct Profile {
33 /// Big-endian 4-byte prefix identifying this format.
34 pub prefix: i32,
35
36 /// Frame width in pixels.
37 pub width: i32,
38
39 /// Frame height in pixels.
40 pub height: i32,
41
42 /// Pixel encoding.
43 pub encoding: Encoding,
44
45 /// Number of bytes in one complete frame (not counting padding).
46 pub frame_byte_length: i32,
47
48 // ---- Optional overrides ----
49 /// If true, width and height are swapped after decode.
50 pub swaps_dimensions: bool,
51
52 /// If false (default), pixel data is little-endian.
53 pub little_endian: bool,
54
55 /// If true, frame occupies a fixed-size slot with padding.
56 pub is_padded: bool,
57
58 /// If true, even/odd scanlines are stored as separate fields.
59 pub is_interlaced: bool,
60
61 /// If true, chroma uses CLCL shared-nibble layout.
62 pub clcl_chroma: bool,
63
64 /// If true, swap Cb/Cr plane order in YCbCr 4:2:0.
65 pub swap_chroma_planes: bool,
66
67 /// If true, chroma uses CL per-pixel nibble layout.
68 pub cl_chroma: bool,
69
70 /// If true, swap R and B channels (BGR15 iPhone compatibility).
71 pub swap_rgb_channels: bool,
72
73 /// Post-decode clockwise rotation in degrees (0, 90, 180, 270).
74 pub rotation: i32,
75
76 /// Visible-region X offset (applied after rotation).
77 pub crop_x: i32,
78
79 /// Visible-region Y offset (applied after rotation).
80 pub crop_y: i32,
81
82 /// Visible-region width (0 = full width).
83 pub crop_width: i32,
84
85 /// Visible-region height (0 = full height).
86 pub crop_height: i32,
87
88 /// Slot size in bytes for padded profiles.
89 pub slot_size: i32,
90
91 /// If true, use actual Width/Height from MHNI chunk instead of fixed values.
92 pub use_mhni_dimensions: bool,
93
94 /// Ordered list of fallback encodings to try if primary decode fails.
95 pub fallback_encodings: Option<Vec<Encoding>>,
96}
97
98impl Default for Profile {
99 fn default() -> Self {
100 Self {
101 prefix: 0,
102 width: 0,
103 height: 0,
104 encoding: Encoding::Rgb565,
105 frame_byte_length: 0,
106 swaps_dimensions: false,
107 little_endian: true,
108 is_padded: false,
109 is_interlaced: false,
110 clcl_chroma: false,
111 swap_chroma_planes: false,
112 cl_chroma: false,
113 swap_rgb_channels: false,
114 rotation: 0,
115 crop_x: 0,
116 crop_y: 0,
117 crop_width: 0,
118 crop_height: 0,
119 slot_size: 0,
120 use_mhni_dimensions: false,
121 fallback_encodings: None,
122 }
123 }
124}
125
126impl Profile {
127 /// The number of bytes per frame, accounting for slot padding.
128 ///
129 /// For padded profiles the frame stride is `slot_size`; for unpadded it
130 /// equals `frame_byte_length`.
131 #[must_use]
132 pub fn frame_size(&self) -> i32 {
133 if self.is_padded && self.slot_size > 0 {
134 self.slot_size
135 } else {
136 self.frame_byte_length
137 }
138 }
139
140 /// The number of pixel columns in the frame.
141 #[must_use]
142 pub fn display_width(&self) -> i32 {
143 if self.swaps_dimensions { self.height } else { self.width }
144 }
145
146 /// The number of pixel rows in the frame.
147 #[must_use]
148 pub fn display_height(&self) -> i32 {
149 if self.swaps_dimensions { self.width } else { self.height }
150 }
151}
152
153// ---------------------------------------------------------------------------
154// Profile database access
155// ---------------------------------------------------------------------------
156
157/// Returns the built-in set of known profiles.
158/// Returns a list of all known built-in profiles.
159///
160/// If the embedded profile database cannot be parsed (JSON corruption),
161/// an empty `Vec` is returned instead of panicking. Use [`ProfileDb::load_builtin`]
162/// directly if you need to distinguish "no profiles" from "parse failure".
163#[must_use]
164pub fn built_in_profiles() -> Vec<Profile> {
165 match crate::profile_db::ProfileDb::load_builtin() {
166 Ok(db) => db.all().values().cloned().collect(),
167 Err(_) => vec![],
168 }
169}