Skip to main content

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}