oxihuman_export/
tiff_export.rs1#![allow(dead_code)]
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum TiffCompression {
10 None,
11 Lzw,
12 Deflate,
13}
14
15impl TiffCompression {
16 pub fn tag_value(&self) -> u16 {
18 match self {
19 Self::None => 1,
20 Self::Lzw => 5,
21 Self::Deflate => 8,
22 }
23 }
24}
25
26#[derive(Debug, Clone)]
28pub struct TiffOptions {
29 pub width: u32,
30 pub height: u32,
31 pub bits_per_sample: u8,
32 pub samples_per_pixel: u8,
33 pub compression: TiffCompression,
34}
35
36impl Default for TiffOptions {
37 fn default() -> Self {
38 Self {
39 width: 512,
40 height: 512,
41 bits_per_sample: 8,
42 samples_per_pixel: 4,
43 compression: TiffCompression::None,
44 }
45 }
46}
47
48#[derive(Debug, Clone)]
50pub struct TiffExport {
51 pub options: TiffOptions,
52 pub pixels: Vec<u8>,
53}
54
55impl TiffExport {
56 pub fn new_solid(width: u32, height: u32, color: [u8; 4]) -> Self {
58 let n = (width * height) as usize;
59 let mut pixels = Vec::with_capacity(n * 4);
60 for _ in 0..n {
61 pixels.extend_from_slice(&color);
62 }
63 Self {
64 options: TiffOptions {
65 width,
66 height,
67 ..Default::default()
68 },
69 pixels,
70 }
71 }
72
73 pub fn pixel_count(&self) -> usize {
75 (self.options.width * self.options.height) as usize
76 }
77
78 pub fn raw_bytes(&self) -> usize {
80 self.pixels.len()
81 }
82}
83
84pub fn estimate_tiff_bytes(export: &TiffExport) -> usize {
86 let raw = export.raw_bytes();
87 match export.options.compression {
88 TiffCompression::None => raw + 512,
89 TiffCompression::Lzw => raw / 3 + 512,
90 TiffCompression::Deflate => raw / 4 + 512,
91 }
92}
93
94pub fn validate_tiff(export: &TiffExport) -> bool {
96 export.options.width > 0
97 && export.options.height > 0
98 && export.options.bits_per_sample > 0
99 && export.options.samples_per_pixel > 0
100}
101
102pub fn tiff_metadata_json(export: &TiffExport) -> String {
104 format!(
105 "{{\"width\":{},\"height\":{},\"bps\":{},\"spp\":{},\"compression\":{}}}",
106 export.options.width,
107 export.options.height,
108 export.options.bits_per_sample,
109 export.options.samples_per_pixel,
110 export.options.compression.tag_value()
111 )
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 fn sample() -> TiffExport {
119 TiffExport::new_solid(8, 8, [200, 150, 100, 255])
120 }
121
122 #[test]
123 fn test_pixel_count() {
124 assert_eq!(sample().pixel_count(), 64);
126 }
127
128 #[test]
129 fn test_raw_bytes() {
130 assert_eq!(sample().raw_bytes(), 256);
132 }
133
134 #[test]
135 fn test_validate_valid() {
136 assert!(validate_tiff(&sample()));
138 }
139
140 #[test]
141 fn test_compression_tag() {
142 assert_ne!(
144 TiffCompression::None.tag_value(),
145 TiffCompression::Lzw.tag_value()
146 );
147 }
148
149 #[test]
150 fn test_estimate_bytes_none_largest() {
151 let mut e = sample();
153 let none_size = estimate_tiff_bytes(&e);
154 e.options.compression = TiffCompression::Deflate;
155 let deflate_size = estimate_tiff_bytes(&e);
156 assert!(none_size > deflate_size);
157 }
158
159 #[test]
160 fn test_metadata_json_has_bps() {
161 assert!(tiff_metadata_json(&sample()).contains("bps"));
163 }
164
165 #[test]
166 fn test_validate_zero_dim() {
167 let mut e = sample();
169 e.options.width = 0;
170 assert!(!validate_tiff(&e));
171 }
172
173 #[test]
174 fn test_default_options() {
175 let opts = TiffOptions::default();
177 assert_eq!(opts.bits_per_sample, 8);
178 }
179}