1use crate::box_reader::{BoxReader, BoxType};
6use crate::error::{Jpeg2000Error, Result};
7use byteorder::{BigEndian, ReadBytesExt};
8use std::io::{Read, Seek};
9
10#[derive(Debug, Clone)]
12pub struct FileType {
13 pub brand: [u8; 4],
15 pub minor_version: u32,
17 pub compatibility: Vec<[u8; 4]>,
19}
20
21impl FileType {
22 pub fn parse<R: Read>(reader: &mut R, length: u64) -> Result<Self> {
24 let mut brand = [0u8; 4];
25 reader.read_exact(&mut brand)?;
26
27 let minor_version = reader.read_u32::<BigEndian>()?;
28
29 let mut compatibility = Vec::new();
30 let remaining = (length - 8) as usize;
31 let num_compat = remaining / 4;
32
33 for _ in 0..num_compat {
34 let mut compat = [0u8; 4];
35 reader.read_exact(&mut compat)?;
36 compatibility.push(compat);
37 }
38
39 Ok(Self {
40 brand,
41 minor_version,
42 compatibility,
43 })
44 }
45
46 pub fn is_jp2(&self) -> bool {
48 &self.brand == b"jp2 "
49 }
50}
51
52#[derive(Debug, Clone)]
54pub struct ImageHeader {
55 pub height: u32,
57 pub width: u32,
59 pub num_components: u16,
61 pub bits_per_component: u8,
63 pub compression_type: u8,
65 pub colorspace_unknown: bool,
67 pub has_ipr: bool,
69}
70
71impl ImageHeader {
72 pub fn parse<R: Read>(reader: &mut R) -> Result<Self> {
74 let height = reader.read_u32::<BigEndian>()?;
75 let width = reader.read_u32::<BigEndian>()?;
76 let num_components = reader.read_u16::<BigEndian>()?;
77
78 let bpc = reader.read_u8()?;
79 let bits_per_component = (bpc & 0x7F) + 1;
80
81 let compression_type = reader.read_u8()?;
82 if compression_type != 7 {
83 tracing::warn!(
84 "Non-standard compression type: {} (expected 7)",
85 compression_type
86 );
87 }
88
89 let colorspace_unknown = reader.read_u8()? != 0;
90 let has_ipr = reader.read_u8()? != 0;
91
92 Ok(Self {
93 height,
94 width,
95 num_components,
96 bits_per_component,
97 compression_type,
98 colorspace_unknown,
99 has_ipr,
100 })
101 }
102}
103
104#[derive(Debug, Clone)]
106pub struct ColorSpecification {
107 pub method: u8,
109 pub precedence: i8,
111 pub approximation: u8,
113 pub enum_cs: Option<EnumeratedColorSpace>,
115 pub icc_profile: Option<Vec<u8>>,
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121#[repr(u32)]
122pub enum EnumeratedColorSpace {
123 Srgb = 16,
125 Grayscale = 17,
127 Sycc = 18,
129 Custom(u32),
131}
132
133impl EnumeratedColorSpace {
134 pub fn from_u32(value: u32) -> Self {
136 match value {
137 16 => Self::Srgb,
138 17 => Self::Grayscale,
139 18 => Self::Sycc,
140 v => Self::Custom(v),
141 }
142 }
143
144 pub fn to_u32(&self) -> u32 {
146 match self {
147 Self::Srgb => 16,
148 Self::Grayscale => 17,
149 Self::Sycc => 18,
150 Self::Custom(v) => *v,
151 }
152 }
153}
154
155impl ColorSpecification {
156 pub fn parse<R: Read>(reader: &mut R, length: u64) -> Result<Self> {
158 let method = reader.read_u8()?;
159 let precedence = reader.read_i8()?;
160 let approximation = reader.read_u8()?;
161
162 let (enum_cs, icc_profile) = if method == 1 {
163 let cs_value = reader.read_u32::<BigEndian>()?;
165 (Some(EnumeratedColorSpace::from_u32(cs_value)), None)
166 } else if method == 2 {
167 let remaining = (length - 3) as usize;
169 let mut profile = vec![0u8; remaining];
170 reader.read_exact(&mut profile)?;
171 (None, Some(profile))
172 } else {
173 return Err(Jpeg2000Error::InvalidMetadata(format!(
174 "Invalid color specification method: {}",
175 method
176 )));
177 };
178
179 Ok(Self {
180 method,
181 precedence,
182 approximation,
183 enum_cs,
184 icc_profile,
185 })
186 }
187}
188
189#[derive(Debug, Clone)]
191pub struct Resolution {
192 pub vertical: f64,
194 pub horizontal: f64,
196}
197
198impl Resolution {
199 pub fn parse<R: Read>(reader: &mut R) -> Result<Self> {
201 let vr_num = reader.read_u16::<BigEndian>()?;
202 let vr_den = reader.read_u16::<BigEndian>()?;
203 let hr_num = reader.read_u16::<BigEndian>()?;
204 let hr_den = reader.read_u16::<BigEndian>()?;
205
206 let vr_exp = reader.read_i8()?;
207 let hr_exp = reader.read_i8()?;
208
209 let vertical = f64::from(vr_num) / f64::from(vr_den) * 10f64.powi(i32::from(vr_exp));
210 let horizontal = f64::from(hr_num) / f64::from(hr_den) * 10f64.powi(i32::from(hr_exp));
211
212 Ok(Self {
213 vertical,
214 horizontal,
215 })
216 }
217
218 pub fn to_dpi(&self) -> (f64, f64) {
220 const INCH_PER_METER: f64 = 39.3701;
221 (
222 self.horizontal / INCH_PER_METER,
223 self.vertical / INCH_PER_METER,
224 )
225 }
226}
227
228#[derive(Debug, Clone)]
230pub struct XmlMetadata {
231 pub content: String,
233}
234
235impl XmlMetadata {
236 pub fn parse<R: Read>(reader: &mut R, length: u64) -> Result<Self> {
238 let mut buffer = vec![0u8; length as usize];
239 reader.read_exact(&mut buffer)?;
240
241 let content = String::from_utf8(buffer).map_err(|e| {
242 Jpeg2000Error::InvalidMetadata(format!("Invalid UTF-8 in XML box: {}", e))
243 })?;
244
245 Ok(Self { content })
246 }
247}
248
249#[derive(Debug, Clone)]
251pub struct UuidBox {
252 pub uuid: [u8; 16],
254 pub data: Vec<u8>,
256}
257
258impl UuidBox {
259 pub fn parse<R: Read>(reader: &mut R, length: u64) -> Result<Self> {
261 let mut uuid = [0u8; 16];
262 reader.read_exact(&mut uuid)?;
263
264 let data_len = (length - 16) as usize;
265 let mut data = vec![0u8; data_len];
266 reader.read_exact(&mut data)?;
267
268 Ok(Self { uuid, data })
269 }
270
271 pub fn uuid_string(&self) -> String {
273 format!(
274 "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
275 self.uuid[0],
276 self.uuid[1],
277 self.uuid[2],
278 self.uuid[3],
279 self.uuid[4],
280 self.uuid[5],
281 self.uuid[6],
282 self.uuid[7],
283 self.uuid[8],
284 self.uuid[9],
285 self.uuid[10],
286 self.uuid[11],
287 self.uuid[12],
288 self.uuid[13],
289 self.uuid[14],
290 self.uuid[15]
291 )
292 }
293}
294
295#[derive(Debug, Clone, Default)]
297pub struct Jp2Metadata {
298 pub file_type: Option<FileType>,
300 pub image_header: Option<ImageHeader>,
302 pub color_spec: Option<ColorSpecification>,
304 pub capture_resolution: Option<Resolution>,
306 pub display_resolution: Option<Resolution>,
308 pub xml_boxes: Vec<XmlMetadata>,
310 pub uuid_boxes: Vec<UuidBox>,
312}
313
314impl Jp2Metadata {
315 pub fn new() -> Self {
317 Self::default()
318 }
319
320 pub fn parse<R: Read + Seek>(reader: &mut R) -> Result<Self> {
322 let mut box_reader = BoxReader::new(reader)?;
323 let mut metadata = Self::new();
324
325 if let Some(ftyp_header) = box_reader.find_box(BoxType::FileType)? {
327 let data = box_reader.read_box_data(&ftyp_header)?;
328 let mut cursor = std::io::Cursor::new(&data);
329 metadata.file_type = Some(FileType::parse(&mut cursor, ftyp_header.data_size())?);
330 }
331
332 box_reader.reset()?;
334 if let Some(jp2h_header) = box_reader.find_box(BoxType::Jp2Header)? {
335 let data = box_reader.read_box_data(&jp2h_header)?;
337 let mut cursor = std::io::Cursor::new(&data);
338
339 let mut sub_reader = BoxReader::new(&mut cursor)?;
341 if let Some(ihdr_header) = sub_reader.find_box(BoxType::ImageHeader)? {
342 let ihdr_data = sub_reader.read_box_data(&ihdr_header)?;
343 let mut ihdr_cursor = std::io::Cursor::new(&ihdr_data);
344 metadata.image_header = Some(ImageHeader::parse(&mut ihdr_cursor)?);
345 }
346
347 sub_reader.reset()?;
349 if let Some(colr_header) = sub_reader.find_box(BoxType::ColorSpecification)? {
350 let colr_data = sub_reader.read_box_data(&colr_header)?;
351 let mut colr_cursor = std::io::Cursor::new(&colr_data);
352 metadata.color_spec = Some(ColorSpecification::parse(
353 &mut colr_cursor,
354 colr_header.data_size(),
355 )?);
356 }
357 }
358
359 Ok(metadata)
360 }
361}
362
363#[cfg(test)]
364mod tests {
365 use super::*;
366
367 #[test]
368 fn test_enumerated_colorspace() {
369 let cs = EnumeratedColorSpace::from_u32(16);
370 assert_eq!(cs, EnumeratedColorSpace::Srgb);
371 assert_eq!(cs.to_u32(), 16);
372 }
373
374 #[test]
375 fn test_resolution_to_dpi() {
376 let res = Resolution {
377 horizontal: 11811.0, vertical: 11811.0,
379 };
380
381 let (h_dpi, v_dpi) = res.to_dpi();
382 assert!((h_dpi - 300.0).abs() < 1.0);
383 assert!((v_dpi - 300.0).abs() < 1.0);
384 }
385
386 #[test]
387 fn test_uuid_string() {
388 let uuid_box = UuidBox {
389 uuid: [
390 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
391 0xee, 0xff,
392 ],
393 data: Vec::new(),
394 };
395
396 let uuid_str = uuid_box.uuid_string();
397 assert_eq!(uuid_str, "00112233-4455-6677-8899-aabbccddeeff");
398 }
399}