1use crate::binary::{be_f64_at, be_u32_at};
27use crate::error::{Result, bail};
28use crate::image::RgbaImage;
29
30#[derive(Debug, Clone, PartialEq, serde::Serialize)]
32pub struct Placement {
33 pub width: u32,
35 pub height: u32,
37 pub scale: (f64, f64),
39 pub center: (f64, f64),
41 pub corners: [(f64, f64); 4],
43}
44
45const CORNERS_AT: usize = 120;
46
47impl Placement {
48 pub fn parse(data: &[u8]) -> Result<Self> {
50 if data.len() < CORNERS_AT + 64 {
51 bail!("ResizableImageInfo of {} bytes, expected at least 184", data.len());
52 }
53 let f = |at: usize| be_f64_at(data, at);
54 let corners = [0, 1, 2, 3].map(|i| (f(CORNERS_AT + 16 * i), f(CORNERS_AT + 16 * i + 8)));
55 let placement = Placement {
56 width: be_u32_at(data, 32),
57 height: be_u32_at(data, 36),
58 scale: (f(40), f(48)),
59 center: (f(64), f(72)),
60 corners,
61 };
62 if placement.width == 0
63 || placement.height == 0
64 || corners.iter().any(|(x, y)| !x.is_finite() || !y.is_finite())
65 {
66 bail!("ResizableImageInfo without a usable size or corners");
67 }
68 Ok(placement)
69 }
70
71 fn inverse(&self, offset: (f64, f64)) -> Option<[f64; 9]> {
75 let (w, h) = (self.width as f64, self.height as f64);
76 let source = [(0.0, 0.0), (w, 0.0), (0.0, h), (w, h)];
77 let target = self.corners.map(|(x, y)| (x + offset.0, y + offset.1));
78 homography(target, source)
79 }
80
81 pub fn draw(
85 &self,
86 original: &RgbaImage,
87 level_scale: f64,
88 offset: (f64, f64),
89 width: u32,
90 height: u32,
91 scale: f64,
92 ) -> Option<RgbaImage> {
93 self.draw_region(original, level_scale, offset, (0, 0, width, height), scale)
94 }
95
96 pub fn bounds(&self, offset: (f64, f64), scale: f64) -> (i64, i64, i64, i64) {
99 let xs = self.corners.map(|c| (c.0 + offset.0) * scale);
100 let ys = self.corners.map(|c| (c.1 + offset.1) * scale);
101 let min = |v: [f64; 4]| v.iter().copied().fold(f64::INFINITY, f64::min).floor() as i64;
102 let max = |v: [f64; 4]| v.iter().copied().fold(f64::NEG_INFINITY, f64::max).ceil() as i64;
103 (min(xs), min(ys), max(xs), max(ys))
104 }
105
106 pub fn draw_region(
110 &self,
111 original: &RgbaImage,
112 level_scale: f64,
113 offset: (f64, f64),
114 region: (u32, u32, u32, u32),
115 scale: f64,
116 ) -> Option<RgbaImage> {
117 let (left, top, width, height) = region;
118 let m = self.inverse(offset)?;
119 let premultiplied: Vec<[f32; 4]> = original
120 .pixels
121 .as_chunks::<4>()
122 .0
123 .iter()
124 .map(|p| {
125 let a = p[3] as f32 / 255.0;
126 [p[0] as f32 * a, p[1] as f32 * a, p[2] as f32 * a, p[3] as f32]
127 })
128 .collect();
129 let (ow, oh) = (original.width as i64, original.height as i64);
130 let texel = |x: i64, y: i64| premultiplied[(y.clamp(0, oh - 1) * ow + x.clamp(0, ow - 1)) as usize];
132 let (bx0, by0, bx1, by1) = self.bounds(offset, scale);
134 let clip = |value: i64, start: u32, size: u32| (value - start as i64).clamp(0, size as i64) as u32;
135 let (x0, x1) = (clip(bx0, left, width), clip(bx1, left, width));
136 let (y0, y1) = (clip(by0, top, height), clip(by1, top, height));
137 let mut out = RgbaImage::new(width, height);
138 for y in y0..y1 {
139 for x in x0..x1 {
140 let cx = ((x + left) as f64 + 0.5) / scale;
141 let cy = ((y + top) as f64 + 0.5) / scale;
142 let d = m[6] * cx + m[7] * cy + m[8];
143 if d.abs() < 1e-12 {
144 continue;
145 }
146 let sx = (m[0] * cx + m[1] * cy + m[2]) / d * level_scale - 0.5;
148 let sy = (m[3] * cx + m[4] * cy + m[5]) / d * level_scale - 0.5;
149 if sx < -0.5 || sy < -0.5 || sx >= ow as f64 - 0.5 || sy >= oh as f64 - 0.5 {
150 continue;
151 }
152 let (fx, fy) = (sx.floor(), sy.floor());
153 let (tx, ty) = ((sx - fx) as f32, (sy - fy) as f32);
154 let (ix, iy) = (fx as i64, fy as i64);
155 let (a, b, c, e) = (texel(ix, iy), texel(ix + 1, iy), texel(ix, iy + 1), texel(ix + 1, iy + 1));
156 let mut p = [0f32; 4];
157 for k in 0..4 {
158 let top = a[k] + (b[k] - a[k]) * tx;
159 let bottom = c[k] + (e[k] - c[k]) * tx;
160 p[k] = top + (bottom - top) * ty;
161 }
162 if p[3] <= 0.0 {
163 continue;
164 }
165 let alpha = p[3] / 255.0;
166 let at = (y as usize * width as usize + x as usize) * 4;
167 let straight = [p[0] / alpha, p[1] / alpha, p[2] / alpha, p[3]];
168 for (target, value) in out.pixels[at..at + 4].iter_mut().zip(straight) {
169 *target = value.round().clamp(0.0, 255.0) as u8;
170 }
171 }
172 }
173 Some(out)
174 }
175
176 pub fn magnification(&self) -> f64 {
178 let length = |a: (f64, f64), b: (f64, f64)| ((a.0 - b.0).powi(2) + (a.1 - b.1).powi(2)).sqrt();
179 let x = length(self.corners[0], self.corners[1]) / self.width as f64;
180 let y = length(self.corners[0], self.corners[2]) / self.height as f64;
181 x.max(y)
182 }
183}
184
185fn homography(from: [(f64, f64); 4], to: [(f64, f64); 4]) -> Option<[f64; 9]> {
188 let mut a = [[0f64; 9]; 8];
189 for (i, ((x, y), (u, v))) in from.iter().zip(to.iter()).enumerate() {
190 a[2 * i] = [*x, *y, 1.0, 0.0, 0.0, 0.0, -u * x, -u * y, *u];
191 a[2 * i + 1] = [0.0, 0.0, 0.0, *x, *y, 1.0, -v * x, -v * y, *v];
192 }
193 for col in 0..8 {
195 let pivot = (col..8).max_by(|&r, &s| a[r][col].abs().total_cmp(&a[s][col].abs()))?;
196 if a[pivot][col].abs() < 1e-12 {
197 return None;
198 }
199 a.swap(col, pivot);
200 let pivot_row = a[col];
201 for (index, row) in a.iter_mut().enumerate() {
202 if index != col {
203 let factor = row[col] / pivot_row[col];
204 for (value, pivot_value) in row.iter_mut().zip(pivot_row).skip(col) {
205 *value -= factor * pivot_value;
206 }
207 }
208 }
209 }
210 let mut h = [0f64; 9];
211 for (i, (value, row)) in h.iter_mut().zip(&a).enumerate() {
212 *value = row[8] / row[i];
213 }
214 h[8] = 1.0;
215 Some(h)
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221
222 fn blob(width: u32, height: u32, corners: [(f64, f64); 4]) -> Vec<u8> {
223 let mut data = vec![0u8; 184];
224 data[32..36].copy_from_slice(&width.to_be_bytes());
225 data[36..40].copy_from_slice(&height.to_be_bytes());
226 for (i, (x, y)) in corners.iter().enumerate() {
227 data[120 + 16 * i..128 + 16 * i].copy_from_slice(&x.to_be_bytes());
228 data[128 + 16 * i..136 + 16 * i].copy_from_slice(&y.to_be_bytes());
229 }
230 data
231 }
232
233 #[test]
234 fn scaled_and_moved_image_lands_on_its_corners() {
235 let mut original = RgbaImage::new(4, 2);
236 original.fill([200, 10, 20, 255]);
237 let placement = Placement::parse(&blob(4, 2, [(10.0, 6.0), (18.0, 6.0), (10.0, 10.0), (18.0, 10.0)])).unwrap();
239 assert!((placement.magnification() - 2.0).abs() < 1e-9);
240 let image = placement.draw(&original, 1.0, (2.0, 1.0), 32, 16, 1.0).unwrap();
241 assert_eq!(image.pixel(13, 9), [200, 10, 20, 255]);
242 assert_eq!(image.pixel(11, 6)[3], 0);
243 assert_eq!(image.pixel(20, 9)[3], 0);
244 assert_eq!(image.pixel(19, 10), [200, 10, 20, 255]);
245 }
246
247 #[test]
248 fn flipped_corners_mirror_the_image() {
249 let mut original = RgbaImage::new(2, 1);
250 original.set_pixel(0, 0, [255, 0, 0, 255]);
251 original.set_pixel(1, 0, [0, 0, 255, 255]);
252 let placement = Placement::parse(&blob(2, 1, [(20.0, 0.0), (0.0, 0.0), (20.0, 10.0), (0.0, 10.0)])).unwrap();
253 let image = placement.draw(&original, 1.0, (0.0, 0.0), 20, 10, 1.0).unwrap();
254 assert_eq!(image.pixel(2, 5), [0, 0, 255, 255]);
255 assert_eq!(image.pixel(17, 5), [255, 0, 0, 255]);
256 }
257
258 #[test]
259 fn a_region_is_the_same_part_of_the_whole() {
260 let mut original = RgbaImage::new(3, 3);
261 for (i, pixel) in original.pixels.chunks_mut(4).enumerate() {
262 pixel.copy_from_slice(&[i as u8 * 20, 100, 200 - i as u8 * 10, 255]);
263 }
264 let placement = Placement::parse(&blob(3, 3, [(3.5, 2.0), (15.0, 4.0), (2.0, 13.0), (14.0, 16.5)])).unwrap();
265 let whole = placement.draw(&original, 1.0, (1.0, 1.0), 24, 20, 1.0).unwrap();
266 let part = placement.draw_region(&original, 1.0, (1.0, 1.0), (5, 4, 8, 9), 1.0).unwrap();
267 for y in 0..9 {
268 for x in 0..8 {
269 assert_eq!(part.pixel(x, y), whole.pixel(x + 5, y + 4));
270 }
271 }
272 assert_eq!(placement.bounds((1.0, 1.0), 1.0), (3, 3, 16, 18));
273 }
274}