1use std::collections::{BTreeMap, HashMap};
2
3use crate::error::{CziError, Result};
4
5#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
6pub enum Dimension {
7 Z,
8 C,
9 T,
10 R,
11 S,
12 I,
13 H,
14 V,
15 B,
16 X,
17 Y,
18 M,
19}
20
21impl Dimension {
22 pub const FRAME_ORDER: [Dimension; 9] = [
23 Dimension::S,
24 Dimension::T,
25 Dimension::C,
26 Dimension::Z,
27 Dimension::R,
28 Dimension::I,
29 Dimension::H,
30 Dimension::V,
31 Dimension::B,
32 ];
33
34 pub fn as_str(self) -> &'static str {
35 match self {
36 Self::Z => "Z",
37 Self::C => "C",
38 Self::T => "T",
39 Self::R => "R",
40 Self::S => "S",
41 Self::I => "I",
42 Self::H => "H",
43 Self::V => "V",
44 Self::B => "B",
45 Self::X => "X",
46 Self::Y => "Y",
47 Self::M => "M",
48 }
49 }
50
51 pub fn from_code(code: &str) -> Option<Self> {
52 match code.trim().to_ascii_uppercase().as_str() {
53 "Z" => Some(Self::Z),
54 "C" => Some(Self::C),
55 "T" => Some(Self::T),
56 "R" => Some(Self::R),
57 "S" => Some(Self::S),
58 "I" => Some(Self::I),
59 "H" => Some(Self::H),
60 "V" => Some(Self::V),
61 "B" => Some(Self::B),
62 "X" => Some(Self::X),
63 "Y" => Some(Self::Y),
64 "M" => Some(Self::M),
65 _ => None,
66 }
67 }
68
69 pub fn is_frame_dimension(self) -> bool {
70 matches!(
71 self,
72 Self::Z | Self::C | Self::T | Self::R | Self::S | Self::I | Self::H | Self::V | Self::B
73 )
74 }
75}
76
77impl std::fmt::Display for Dimension {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 f.write_str(self.as_str())
80 }
81}
82
83#[derive(Copy, Clone, Debug, PartialEq, Eq)]
84pub struct Interval {
85 pub start: i32,
86 pub size: usize,
87}
88
89impl Interval {
90 pub fn end_exclusive(self) -> i32 {
91 self.start + self.size as i32
92 }
93}
94
95#[derive(Clone, Debug, Default, PartialEq, Eq)]
96pub struct DimBounds {
97 intervals: BTreeMap<Dimension, Interval>,
98}
99
100impl DimBounds {
101 pub fn new() -> Self {
102 Self::default()
103 }
104
105 pub fn get(&self, dimension: Dimension) -> Option<Interval> {
106 self.intervals.get(&dimension).copied()
107 }
108
109 pub fn set(&mut self, dimension: Dimension, interval: Interval) {
110 self.intervals.insert(dimension, interval);
111 }
112
113 pub fn update_value(&mut self, dimension: Dimension, value: i32) {
114 match self.intervals.get_mut(&dimension) {
115 Some(existing) => {
116 let current_end = existing.end_exclusive();
117 if value < existing.start {
118 existing.size = (current_end - value) as usize;
119 existing.start = value;
120 } else if value >= current_end {
121 existing.size = (value - existing.start + 1) as usize;
122 }
123 }
124 None => {
125 self.set(
126 dimension,
127 Interval {
128 start: value,
129 size: 1,
130 },
131 );
132 }
133 }
134 }
135
136 pub fn iter(&self) -> impl Iterator<Item = (Dimension, Interval)> + '_ {
137 self.intervals
138 .iter()
139 .map(|(dimension, interval)| (*dimension, *interval))
140 }
141
142 pub fn to_size_map(&self) -> HashMap<String, usize> {
143 self.iter()
144 .map(|(dimension, interval)| (dimension.as_str().to_owned(), interval.size))
145 .collect()
146 }
147}
148
149#[derive(Clone, Debug, Default, PartialEq, Eq)]
150pub struct Coordinate {
151 values: BTreeMap<Dimension, i32>,
152}
153
154impl Coordinate {
155 pub fn new() -> Self {
156 Self::default()
157 }
158
159 pub fn with(mut self, dimension: Dimension, value: i32) -> Self {
160 self.set(dimension, value);
161 self
162 }
163
164 pub fn set(&mut self, dimension: Dimension, value: i32) {
165 self.values.insert(dimension, value);
166 }
167
168 pub fn get(&self, dimension: Dimension) -> Option<i32> {
169 self.values.get(&dimension).copied()
170 }
171
172 pub fn contains(&self, dimension: Dimension) -> bool {
173 self.values.contains_key(&dimension)
174 }
175
176 pub fn iter(&self) -> impl Iterator<Item = (Dimension, i32)> + '_ {
177 self.values
178 .iter()
179 .map(|(dimension, value)| (*dimension, *value))
180 }
181}
182
183#[derive(Clone, Debug, Default, PartialEq, Eq)]
184pub struct PlaneIndex {
185 values: BTreeMap<Dimension, usize>,
186}
187
188impl PlaneIndex {
189 pub fn new() -> Self {
190 Self::default()
191 }
192
193 pub fn with(mut self, dimension: Dimension, value: usize) -> Self {
194 self.set(dimension, value);
195 self
196 }
197
198 pub fn set(&mut self, dimension: Dimension, value: usize) {
199 self.values.insert(dimension, value);
200 }
201
202 pub fn get(&self, dimension: Dimension) -> Option<usize> {
203 self.values.get(&dimension).copied()
204 }
205
206 pub fn iter(&self) -> impl Iterator<Item = (Dimension, usize)> + '_ {
207 self.values
208 .iter()
209 .map(|(dimension, value)| (*dimension, *value))
210 }
211}
212
213#[derive(Copy, Clone, Debug, PartialEq, Eq)]
214pub struct IntRect {
215 pub x: i32,
216 pub y: i32,
217 pub w: i32,
218 pub h: i32,
219}
220
221impl IntRect {
222 pub fn new(x: i32, y: i32, w: i32, h: i32) -> Self {
223 Self { x, y, w, h }
224 }
225
226 pub fn is_valid(self) -> bool {
227 self.w >= 0 && self.h >= 0
228 }
229
230 pub fn is_non_empty(self) -> bool {
231 self.w > 0 && self.h > 0
232 }
233
234 pub fn right(self) -> i32 {
235 self.x + self.w
236 }
237
238 pub fn bottom(self) -> i32 {
239 self.y + self.h
240 }
241
242 pub fn intersect(self, other: Self) -> Option<Self> {
243 let x1 = self.x.max(other.x);
244 let y1 = self.y.max(other.y);
245 let x2 = self.right().min(other.right());
246 let y2 = self.bottom().min(other.bottom());
247 if x2 <= x1 || y2 <= y1 {
248 return None;
249 }
250 Some(Self::new(x1, y1, x2 - x1, y2 - y1))
251 }
252
253 pub(crate) fn union_with(&mut self, other: Self) {
254 if !self.is_valid() {
255 *self = other;
256 return;
257 }
258
259 let x1 = self.x.min(other.x);
260 let y1 = self.y.min(other.y);
261 let x2 = self.right().max(other.right());
262 let y2 = self.bottom().max(other.bottom());
263 *self = Self::new(x1, y1, x2 - x1, y2 - y1);
264 }
265}
266
267#[derive(Copy, Clone, Debug, PartialEq, Eq)]
268pub struct IntSize {
269 pub w: u32,
270 pub h: u32,
271}
272
273#[derive(Copy, Clone, Debug, PartialEq, Eq)]
274pub enum PixelType {
275 Gray8,
276 Gray16,
277 Gray32Float,
278 Bgr24,
279 Bgr48,
280 Bgr96Float,
281 Bgra32,
282 Gray64ComplexFloat,
283 Bgr192ComplexFloat,
284 Gray32,
285 Gray64Float,
286}
287
288impl PixelType {
289 pub fn from_raw(value: i32) -> Option<Self> {
290 match value {
291 0 => Some(Self::Gray8),
292 1 => Some(Self::Gray16),
293 2 => Some(Self::Gray32Float),
294 3 => Some(Self::Bgr24),
295 4 => Some(Self::Bgr48),
296 8 => Some(Self::Bgr96Float),
297 9 => Some(Self::Bgra32),
298 10 => Some(Self::Gray64ComplexFloat),
299 11 => Some(Self::Bgr192ComplexFloat),
300 12 => Some(Self::Gray32),
301 13 => Some(Self::Gray64Float),
302 _ => None,
303 }
304 }
305
306 pub fn from_name(name: &str) -> Option<Self> {
307 match name.trim() {
308 "Gray8" => Some(Self::Gray8),
309 "Gray16" => Some(Self::Gray16),
310 "Gray32Float" => Some(Self::Gray32Float),
311 "Bgr24" => Some(Self::Bgr24),
312 "Bgr48" => Some(Self::Bgr48),
313 "Bgr96Float" => Some(Self::Bgr96Float),
314 "Bgra32" => Some(Self::Bgra32),
315 "Gray64ComplexFloat" => Some(Self::Gray64ComplexFloat),
316 "Bgr192ComplexFloat" => Some(Self::Bgr192ComplexFloat),
317 "Gray32" => Some(Self::Gray32),
318 "Gray64Float" => Some(Self::Gray64Float),
319 _ => None,
320 }
321 }
322
323 pub fn as_str(self) -> &'static str {
324 match self {
325 Self::Gray8 => "Gray8",
326 Self::Gray16 => "Gray16",
327 Self::Gray32Float => "Gray32Float",
328 Self::Bgr24 => "Bgr24",
329 Self::Bgr48 => "Bgr48",
330 Self::Bgr96Float => "Bgr96Float",
331 Self::Bgra32 => "Bgra32",
332 Self::Gray64ComplexFloat => "Gray64ComplexFloat",
333 Self::Bgr192ComplexFloat => "Bgr192ComplexFloat",
334 Self::Gray32 => "Gray32",
335 Self::Gray64Float => "Gray64Float",
336 }
337 }
338
339 pub fn bytes_per_pixel(self) -> usize {
340 match self {
341 Self::Gray8 => 1,
342 Self::Gray16 => 2,
343 Self::Gray32Float => 4,
344 Self::Bgr24 => 3,
345 Self::Bgr48 => 6,
346 Self::Bgr96Float => 12,
347 Self::Bgra32 => 4,
348 Self::Gray64ComplexFloat => 16,
349 Self::Bgr192ComplexFloat => 24,
350 Self::Gray32 => 4,
351 Self::Gray64Float => 8,
352 }
353 }
354}
355
356impl std::fmt::Display for PixelType {
357 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
358 f.write_str(self.as_str())
359 }
360}
361
362#[derive(Copy, Clone, Debug, PartialEq, Eq)]
363pub enum CompressionMode {
364 UnCompressed,
365 Jpg,
366 JpgXr,
367 Zstd0,
368 Zstd1,
369}
370
371impl CompressionMode {
372 pub fn from_raw(value: i32) -> Option<Self> {
373 match value {
374 0 => Some(Self::UnCompressed),
375 1 => Some(Self::Jpg),
376 4 => Some(Self::JpgXr),
377 5 => Some(Self::Zstd0),
378 6 => Some(Self::Zstd1),
379 _ => None,
380 }
381 }
382
383 pub fn as_str(self) -> &'static str {
384 match self {
385 Self::UnCompressed => "UnCompressed",
386 Self::Jpg => "Jpg",
387 Self::JpgXr => "JpgXr",
388 Self::Zstd0 => "Zstd0",
389 Self::Zstd1 => "Zstd1",
390 }
391 }
392}
393
394impl std::fmt::Display for CompressionMode {
395 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
396 f.write_str(self.as_str())
397 }
398}
399
400#[derive(Copy, Clone, Debug, PartialEq, Eq)]
401pub enum SubBlockPyramidType {
402 None,
403 SingleSubBlock,
404 MultiSubBlock,
405}
406
407impl SubBlockPyramidType {
408 pub fn from_raw(value: u8) -> Option<Self> {
409 match value {
410 0 => Some(Self::None),
411 1 => Some(Self::SingleSubBlock),
412 2 => Some(Self::MultiSubBlock),
413 _ => None,
414 }
415 }
416}
417
418#[derive(Clone, Debug, PartialEq, Eq)]
419pub struct FileHeaderInfo {
420 pub major: i32,
421 pub minor: i32,
422 pub primary_file_guid: String,
423 pub file_guid: String,
424 pub file_part: i32,
425 pub subblock_directory_position: u64,
426 pub metadata_position: u64,
427 pub attachment_directory_position: u64,
428 pub update_pending: bool,
429}
430
431#[derive(Clone, Debug, PartialEq, Eq)]
432pub struct DirectorySubBlockInfo {
433 pub index: usize,
434 pub file_position: u64,
435 pub file_part: i32,
436 pub pixel_type: PixelType,
437 pub compression: CompressionMode,
438 pub coordinate: Coordinate,
439 pub rect: IntRect,
440 pub stored_size: IntSize,
441 pub m_index: Option<i32>,
442 pub pyramid_type: Option<SubBlockPyramidType>,
443}
444
445impl DirectorySubBlockInfo {
446 pub fn is_layer0(&self) -> bool {
447 self.rect.w >= 0
448 && self.rect.h >= 0
449 && self.stored_size.w == self.rect.w as u32
450 && self.stored_size.h == self.rect.h as u32
451 }
452}
453
454#[derive(Clone, Debug, PartialEq, Eq)]
455pub struct AttachmentInfo {
456 pub index: usize,
457 pub file_position: u64,
458 pub file_part: i32,
459 pub content_guid: String,
460 pub content_file_type: String,
461 pub name: String,
462 pub data_size: u64,
463}
464
465#[derive(Clone, Debug, PartialEq, Eq)]
466pub struct AttachmentBlob {
467 pub info: AttachmentInfo,
468 pub data: Vec<u8>,
469}
470
471#[derive(Clone, Debug, PartialEq, Eq)]
472pub struct RawSubBlock {
473 pub info: DirectorySubBlockInfo,
474 pub metadata: Vec<u8>,
475 pub data: Vec<u8>,
476 pub attachment: Vec<u8>,
477}
478
479#[derive(Copy, Clone, Debug, PartialEq, Eq)]
480pub struct BoundingBoxes {
481 pub all: IntRect,
482 pub layer0: IntRect,
483}
484
485#[derive(Clone, Debug, PartialEq, Eq)]
486pub struct SubBlockStatistics {
487 pub subblock_count: usize,
488 pub dim_bounds: DimBounds,
489 pub bounding_box: Option<IntRect>,
490 pub bounding_box_layer0: Option<IntRect>,
491 pub scene_bounding_boxes: BTreeMap<i32, BoundingBoxes>,
492 pub min_m_index: Option<i32>,
493 pub max_m_index: Option<i32>,
494}
495
496impl Default for SubBlockStatistics {
497 fn default() -> Self {
498 Self {
499 subblock_count: 0,
500 dim_bounds: DimBounds::new(),
501 bounding_box: None,
502 bounding_box_layer0: None,
503 scene_bounding_boxes: BTreeMap::new(),
504 min_m_index: None,
505 max_m_index: None,
506 }
507 }
508}
509
510#[derive(Clone, Debug, Default, PartialEq)]
511pub struct ScalingInfo {
512 pub x: Option<f64>,
513 pub y: Option<f64>,
514 pub z: Option<f64>,
515 pub unit: Option<String>,
516}
517
518#[derive(Clone, Debug, Default, PartialEq, Eq)]
519pub struct ChannelInfo {
520 pub index: usize,
521 pub id: Option<String>,
522 pub name: Option<String>,
523 pub pixel_type: Option<PixelType>,
524 pub color: Option<String>,
525}
526
527#[derive(Clone, Debug, Default, PartialEq, Eq)]
528pub struct DocumentInfo {
529 pub name: Option<String>,
530 pub title: Option<String>,
531 pub comment: Option<String>,
532 pub author: Option<String>,
533 pub user_name: Option<String>,
534 pub creation_date: Option<String>,
535 pub description: Option<String>,
536 pub application_name: Option<String>,
537 pub application_version: Option<String>,
538}
539
540#[derive(Clone, Debug, Default, PartialEq, Eq)]
541pub struct ImageInfo {
542 pub pixel_type: Option<PixelType>,
543 pub sizes: BTreeMap<Dimension, usize>,
544}
545
546#[derive(Clone, Debug, Default, PartialEq)]
547pub struct MetadataSummary {
548 pub document: DocumentInfo,
549 pub image: ImageInfo,
550 pub scaling: ScalingInfo,
551 pub channels: Vec<ChannelInfo>,
552}
553
554#[derive(Clone, Debug, PartialEq, Eq)]
555pub struct Bitmap {
556 pub pixel_type: PixelType,
557 pub width: u32,
558 pub height: u32,
559 pub stride: usize,
560 pub data: Vec<u8>,
561}
562
563impl Bitmap {
564 pub fn new(pixel_type: PixelType, width: u32, height: u32, data: Vec<u8>) -> Result<Self> {
565 let stride = width as usize * pixel_type.bytes_per_pixel();
566 let expected = stride
567 .checked_mul(height as usize)
568 .ok_or_else(|| CziError::internal_overflow("bitmap size"))?;
569 if data.len() != expected {
570 return Err(CziError::file_invalid_format(format!(
571 "bitmap byte count mismatch: expected {expected}, got {}",
572 data.len()
573 )));
574 }
575
576 Ok(Self {
577 pixel_type,
578 width,
579 height,
580 stride,
581 data,
582 })
583 }
584
585 pub fn zeros(pixel_type: PixelType, width: u32, height: u32) -> Result<Self> {
586 let stride = width as usize * pixel_type.bytes_per_pixel();
587 let len = stride
588 .checked_mul(height as usize)
589 .ok_or_else(|| CziError::internal_overflow("bitmap allocation"))?;
590 Ok(Self {
591 pixel_type,
592 width,
593 height,
594 stride,
595 data: vec![0; len],
596 })
597 }
598
599 pub fn bytes_per_pixel(&self) -> usize {
600 self.pixel_type.bytes_per_pixel()
601 }
602
603 pub fn as_bytes(&self) -> &[u8] {
604 &self.data
605 }
606
607 pub fn into_bytes(self) -> Vec<u8> {
608 self.data
609 }
610
611 pub fn to_u16_vec(&self) -> Result<Vec<u16>> {
612 if self.bytes_per_pixel() % 2 != 0 {
613 return Err(CziError::unsupported_pixel_type(self.pixel_type.as_str()));
614 }
615
616 let mut values = Vec::with_capacity(self.data.len() / 2);
617 for chunk in self.data.chunks_exact(2) {
618 values.push(u16::from_le_bytes([chunk[0], chunk[1]]));
619 }
620 Ok(values)
621 }
622
623 pub fn to_f32_vec(&self) -> Result<Vec<f32>> {
624 if self.bytes_per_pixel() % 4 != 0 {
625 return Err(CziError::unsupported_pixel_type(self.pixel_type.as_str()));
626 }
627
628 let mut values = Vec::with_capacity(self.data.len() / 4);
629 for chunk in self.data.chunks_exact(4) {
630 values.push(f32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]));
631 }
632 Ok(values)
633 }
634}