1use crate::geometry::{BBox, Ctm, Point};
8
9#[derive(Debug, Clone, Default, PartialEq)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12pub struct ImageMetadata {
13 pub src_width: Option<u32>,
15 pub src_height: Option<u32>,
17 pub bits_per_component: Option<u32>,
19 pub color_space: Option<String>,
21}
22
23#[derive(Debug, Clone, PartialEq)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28pub struct Image {
29 pub x0: f64,
31 pub top: f64,
33 pub x1: f64,
35 pub bottom: f64,
37 pub width: f64,
39 pub height: f64,
41 pub name: String,
43 pub src_width: Option<u32>,
45 pub src_height: Option<u32>,
47 pub bits_per_component: Option<u32>,
49 pub color_space: Option<String>,
51}
52
53pub fn image_from_ctm(ctm: &Ctm, name: &str, page_height: f64, metadata: &ImageMetadata) -> Image {
63 let corners = [
65 ctm.transform_point(Point::new(0.0, 0.0)),
66 ctm.transform_point(Point::new(1.0, 0.0)),
67 ctm.transform_point(Point::new(0.0, 1.0)),
68 ctm.transform_point(Point::new(1.0, 1.0)),
69 ];
70
71 let pdf_x0 = corners.iter().map(|p| p.x).fold(f64::INFINITY, f64::min);
73 let pdf_x1 = corners
74 .iter()
75 .map(|p| p.x)
76 .fold(f64::NEG_INFINITY, f64::max);
77 let pdf_y0 = corners.iter().map(|p| p.y).fold(f64::INFINITY, f64::min);
78 let pdf_y1 = corners
79 .iter()
80 .map(|p| p.y)
81 .fold(f64::NEG_INFINITY, f64::max);
82
83 let top = page_height - pdf_y1;
85 let bottom = page_height - pdf_y0;
86
87 let width = pdf_x1 - pdf_x0;
88 let height = bottom - top;
89
90 Image {
91 x0: pdf_x0,
92 top,
93 x1: pdf_x1,
94 bottom,
95 width,
96 height,
97 name: name.to_string(),
98 src_width: metadata.src_width,
99 src_height: metadata.src_height,
100 bits_per_component: metadata.bits_per_component,
101 color_space: metadata.color_space.clone(),
102 }
103}
104
105impl Image {
106 pub fn bbox(&self) -> BBox {
108 BBox::new(self.x0, self.top, self.x1, self.bottom)
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 fn assert_approx(a: f64, b: f64) {
117 assert!(
118 (a - b).abs() < 1e-6,
119 "expected {b}, got {a}, diff={}",
120 (a - b).abs()
121 );
122 }
123
124 const PAGE_HEIGHT: f64 = 792.0;
125
126 #[test]
129 fn test_image_construction_and_field_access() {
130 let img = Image {
131 x0: 72.0,
132 top: 100.0,
133 x1: 272.0,
134 bottom: 250.0,
135 width: 200.0,
136 height: 150.0,
137 name: "Im0".to_string(),
138 src_width: Some(1920),
139 src_height: Some(1080),
140 bits_per_component: Some(8),
141 color_space: Some("DeviceRGB".to_string()),
142 };
143 assert_eq!(img.x0, 72.0);
144 assert_eq!(img.top, 100.0);
145 assert_eq!(img.x1, 272.0);
146 assert_eq!(img.bottom, 250.0);
147 assert_eq!(img.width, 200.0);
148 assert_eq!(img.height, 150.0);
149 assert_eq!(img.name, "Im0");
150 assert_eq!(img.src_width, Some(1920));
151 assert_eq!(img.src_height, Some(1080));
152 assert_eq!(img.bits_per_component, Some(8));
153 assert_eq!(img.color_space, Some("DeviceRGB".to_string()));
154
155 let bbox = img.bbox();
156 assert_approx(bbox.x0, 72.0);
157 assert_approx(bbox.top, 100.0);
158 assert_approx(bbox.x1, 272.0);
159 assert_approx(bbox.bottom, 250.0);
160 }
161
162 #[test]
163 fn test_image_bbox() {
164 let img = Image {
165 x0: 100.0,
166 top: 200.0,
167 x1: 300.0,
168 bottom: 400.0,
169 width: 200.0,
170 height: 200.0,
171 name: "Im0".to_string(),
172 src_width: Some(640),
173 src_height: Some(480),
174 bits_per_component: Some(8),
175 color_space: Some("DeviceRGB".to_string()),
176 };
177 let bbox = img.bbox();
178 assert_approx(bbox.x0, 100.0);
179 assert_approx(bbox.top, 200.0);
180 assert_approx(bbox.x1, 300.0);
181 assert_approx(bbox.bottom, 400.0);
182 }
183
184 #[test]
187 fn test_image_from_ctm_simple_placement() {
188 let ctm = Ctm::new(200.0, 0.0, 0.0, 150.0, 100.0, 500.0);
191 let meta = ImageMetadata {
192 src_width: Some(640),
193 src_height: Some(480),
194 bits_per_component: Some(8),
195 color_space: Some("DeviceRGB".to_string()),
196 };
197
198 let img = image_from_ctm(&ctm, "Im0", PAGE_HEIGHT, &meta);
199
200 assert_approx(img.x0, 100.0);
201 assert_approx(img.x1, 300.0);
202 assert_approx(img.top, 142.0);
204 assert_approx(img.bottom, 292.0);
205 assert_approx(img.width, 200.0);
206 assert_approx(img.height, 150.0);
207 assert_eq!(img.name, "Im0");
208 assert_eq!(img.src_width, Some(640));
209 assert_eq!(img.src_height, Some(480));
210 assert_eq!(img.bits_per_component, Some(8));
211 assert_eq!(img.color_space, Some("DeviceRGB".to_string()));
212 }
213
214 #[test]
215 fn test_image_from_ctm_identity() {
216 let ctm = Ctm::identity();
218 let meta = ImageMetadata::default();
219
220 let img = image_from_ctm(&ctm, "Im1", PAGE_HEIGHT, &meta);
221
222 assert_approx(img.x0, 0.0);
223 assert_approx(img.x1, 1.0);
224 assert_approx(img.top, 791.0);
226 assert_approx(img.bottom, 792.0);
227 assert_approx(img.width, 1.0);
228 assert_approx(img.height, 1.0);
229 }
230
231 #[test]
232 fn test_image_from_ctm_translation_only() {
233 let ctm = Ctm::new(1.0, 0.0, 0.0, 1.0, 300.0, 400.0);
235 let meta = ImageMetadata::default();
236
237 let img = image_from_ctm(&ctm, "Im2", PAGE_HEIGHT, &meta);
238
239 assert_approx(img.x0, 300.0);
240 assert_approx(img.x1, 301.0);
241 assert_approx(img.top, 391.0);
243 assert_approx(img.bottom, 392.0);
244 }
245
246 #[test]
247 fn test_image_from_ctm_scale_and_translate() {
248 let ctm = Ctm::new(400.0, 0.0, 0.0, 300.0, 50.0, 200.0);
250 let meta = ImageMetadata::default();
251
252 let img = image_from_ctm(&ctm, "Im3", PAGE_HEIGHT, &meta);
253
254 assert_approx(img.x0, 50.0);
255 assert_approx(img.x1, 450.0);
256 assert_approx(img.top, 292.0);
258 assert_approx(img.bottom, 592.0);
259 assert_approx(img.width, 400.0);
260 assert_approx(img.height, 300.0);
261 }
262
263 #[test]
264 fn test_image_from_ctm_no_metadata() {
265 let ctm = Ctm::new(100.0, 0.0, 0.0, 100.0, 200.0, 300.0);
266 let meta = ImageMetadata::default();
267
268 let img = image_from_ctm(&ctm, "ImX", PAGE_HEIGHT, &meta);
269
270 assert_eq!(img.name, "ImX");
271 assert_eq!(img.src_width, None);
272 assert_eq!(img.src_height, None);
273 assert_eq!(img.bits_per_component, None);
274 assert_eq!(img.color_space, None);
275 }
276
277 #[test]
278 fn test_image_from_ctm_different_page_height() {
279 let ctm = Ctm::new(100.0, 0.0, 0.0, 100.0, 0.0, 0.0);
281 let meta = ImageMetadata::default();
282
283 let img_letter = image_from_ctm(&ctm, "Im0", 792.0, &meta);
284 let img_a4 = image_from_ctm(&ctm, "Im0", 842.0, &meta);
285
286 assert_approx(img_letter.width, img_a4.width);
288 assert_approx(img_letter.top, 692.0); assert_approx(img_a4.top, 742.0); }
292
293 #[test]
294 fn test_image_metadata_default() {
295 let meta = ImageMetadata::default();
296 assert_eq!(meta.src_width, None);
297 assert_eq!(meta.src_height, None);
298 assert_eq!(meta.bits_per_component, None);
299 assert_eq!(meta.color_space, None);
300 }
301}