1use crate::kernels::geometry::{js_ceil, js_floor, Rect};
6use crate::kernels::region::Region;
7
8#[derive(Clone, Copy)]
11pub struct Layout {
12 pub width: f64,
13 pub height: f64,
14 pub pane: Rect,
15 pub content: Rect,
16 pub dx: f64,
17 pub dy: f64,
18 pub seam_x: f64,
19}
20
21pub fn frame_layout(
22 source_width: f64,
23 source_height: f64,
24 pane: Rect,
25 bounds_width: f64,
26 bounds_height: f64,
27) -> Layout {
28 let content = Rect {
29 x: pane.x,
30 y: pane.y,
31 width: pane.width.max(js_ceil(bounds_width) as f64),
32 height: pane.height.max(js_ceil(bounds_height) as f64),
33 };
34 Layout {
35 width: source_width + content.width - pane.width,
36 height: source_height + content.height - pane.height,
37 pane,
38 content,
39 dx: content.width - pane.width,
40 dy: content.height - pane.height,
41 seam_x: (pane.x + pane.width / 2.0).floor(),
42 }
43}
44
45pub enum Coordinate {
48 Content,
49 Extension,
50 Source(f64, f64),
51}
52
53pub fn frame_coordinate(layout: &Layout, x: f64, y: f64) -> Coordinate {
54 let (p, c, dx, dy, seam_x) = (
55 layout.pane,
56 layout.content,
57 layout.dx,
58 layout.dy,
59 layout.seam_x,
60 );
61 if x >= c.x && x < c.x + c.width && y >= c.y && y < c.y + c.height {
62 return Coordinate::Content;
63 }
64 if y < p.y || y >= c.y + c.height {
65 let sy = if y < p.y { y } else { y - dy };
66 if x >= seam_x && x < seam_x + dx {
67 return Coordinate::Extension;
68 }
69 return Coordinate::Source(if x < seam_x { x } else { x - dx }, sy);
70 }
71 if y >= p.y + p.height {
72 return Coordinate::Extension;
73 }
74 Coordinate::Source(if x < p.x { x } else { x - dx }, y)
75}
76
77fn mode(values: &[u32]) -> u32 {
80 let mut counts: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
81 let mut best = values.first().copied().unwrap_or(0);
82 let mut count = 0u32;
83 for &v in values {
84 let n = counts.entry(v).or_insert(0);
85 *n += 1;
86 if *n > count {
87 count = *n;
88 best = v;
89 }
90 }
91 best
92}
93
94#[inline]
96fn px32(rgba: &[u8], i: usize) -> u32 {
97 u32::from_le_bytes([
98 rgba[i * 4],
99 rgba[i * 4 + 1],
100 rgba[i * 4 + 2],
101 rgba[i * 4 + 3],
102 ])
103}
104
105pub fn backgrounds(
108 rgba: &[u8],
109 image_width: usize,
110 image_height: usize,
111 pane: Rect,
112) -> (Vec<u32>, Vec<u32>) {
113 let xs = (1usize).max((pane.width / 128.0).floor() as usize);
114 let ys = (1usize).max((pane.height / 128.0).floor() as usize);
115 let mut rows = vec![0u32; image_height];
116 let mut columns = vec![0u32; image_width];
117 let mut samples = Vec::new();
118 for (y, row) in rows.iter_mut().enumerate() {
119 samples.clear();
120 let mut x = pane.x as i64;
121 while (x as f64) < pane.x + pane.width {
122 samples.push(px32(rgba, y * image_width + x as usize));
123 x += xs as i64;
124 }
125 *row = mode(&samples);
126 }
127 for (x, column) in columns.iter_mut().enumerate() {
128 samples.clear();
129 let mut y = pane.y as i64;
130 while (y as f64) < pane.y + pane.height {
131 samples.push(px32(rgba, y as usize * image_width + x));
132 y += ys as i64;
133 }
134 *column = mode(&samples);
135 }
136 (rows, columns)
137}
138
139pub struct OutputTile<'a> {
141 pub size: usize,
142 pub pixels: &'a mut [u8],
143 pub coverage: &'a mut [u8],
144 pub provisional: &'a mut [u8],
145 pub quality: &'a mut [u8],
146 pub score: &'a mut [f32],
147 pub owner: &'a mut [u32],
148 pub conflicts: &'a mut [u8],
149 pub frozen: &'a mut [u8],
150}
151
152pub struct SourceTile<'a> {
154 pub sx: i64,
155 pub sy: i64,
156 pub pixels: &'a [u8],
157 pub coverage: &'a [u8],
158 pub provisional: &'a [u8],
159 pub quality: &'a [u8],
160 pub score: &'a [f32],
161 pub owner: &'a [u32],
162 pub conflicts: &'a [u8],
163 pub frozen: &'a [u8],
164}
165
166#[inline]
167fn bit(bits: &[u8], p: usize) -> bool {
168 bits[p >> 3] & (1 << (p & 7)) != 0
169}
170#[inline]
171fn set_bit(bits: &mut [u8], p: usize) {
172 bits[p >> 3] |= 1 << (p & 7);
173}
174
175fn intersect(a: Rect, b: Rect) -> Rect {
176 let x = a.x.max(b.x);
177 let y = a.y.max(b.y);
178 Rect {
179 x,
180 y,
181 width: 0f64.max((a.x + a.width).min(b.x + b.width) - x),
182 height: 0f64.max((a.y + a.height).min(b.y + b.height) - y),
183 }
184}
185
186#[allow(clippy::too_many_arguments)]
190pub fn paint_tile(
191 out: &mut OutputTile<'_>,
192 layout: &Layout,
193 bounds: Rect,
194 source: &[u8],
195 source_width: usize,
196 source_height: usize,
197 bg_rows: &[u32],
198 bg_columns: &[u32],
199 ignore: &[Region],
200) {
201 let size = out.size;
202 let bw = bounds.width as i64;
203 let bh = bounds.height as i64;
204 for y in 0..bh {
205 for x in 0..bw {
206 let (ox, oy) = (bounds.x + x as f64, bounds.y + y as f64);
207 let at = (y as usize) * size + x as usize;
208 match frame_coordinate(layout, ox, oy) {
209 Coordinate::Content => continue,
210 Coordinate::Source(sx, sy) => {
211 if ignore
212 .iter()
213 .any(|r| r.contains(sx, sy, source_width as f64, source_height as f64))
214 {
215 continue;
216 }
217 let src = sy as usize * source_width + sx as usize;
218 out.pixels[at * 4..at * 4 + 4].copy_from_slice(&source[src * 4..src * 4 + 4]);
219 if out.pixels[at * 4 + 3] != 0 {
220 set_bit(out.coverage, at);
221 }
222 }
223 Coordinate::Extension => {
224 let v = if oy < layout.pane.y || oy >= layout.content.y + layout.content.height
225 {
226 bg_row(
227 bg_rows,
228 if oy < layout.pane.y {
229 oy
230 } else {
231 oy - layout.dy
232 },
233 )
234 } else {
235 bg_column(
236 bg_columns,
237 if ox < layout.pane.x {
238 ox
239 } else {
240 ox - layout.dx
241 },
242 )
243 };
244 out.pixels[at * 4..at * 4 + 4].copy_from_slice(&v.to_le_bytes());
245 }
246 }
247 }
248 }
249}
250
251#[inline]
255fn bg_row(rows: &[u32], y: f64) -> u32 {
256 if y < 0.0 {
257 0
258 } else {
259 rows.get(y as usize).copied().unwrap_or(0)
260 }
261}
262#[inline]
263fn bg_column(columns: &[u32], x: f64) -> u32 {
264 if x < 0.0 {
265 0
266 } else {
267 columns.get(x as usize).copied().unwrap_or(0)
268 }
269}
270
271struct BlockAgg {
272 quality: u8,
273 score: f32,
274 score_set: bool,
275 owner_single: Option<u32>,
276 owner_conflict: bool,
277 conflicts: bool,
278 frozen: bool,
279}
280
281#[allow(clippy::too_many_arguments)]
285pub fn fold_evidence(
286 out: &mut OutputTile<'_>,
287 layout: &Layout,
288 source_bounds_x: f64,
289 source_bounds_y: f64,
290 bounds: Rect,
291 sources: &[SourceTile<'_>],
292) {
293 const QUALITY_BLOCK: i64 = 16;
294 let size = out.size as i64;
295 let overlap = intersect(bounds, layout.content);
296 if overlap.width <= 0.0 || overlap.height <= 0.0 {
297 return;
298 }
299 let wx = js_floor(source_bounds_x + overlap.x - layout.content.x) as i64;
300 let wy = js_floor(source_bounds_y + overlap.y - layout.content.y) as i64;
301 let mut evidence: std::collections::HashMap<i64, BlockAgg> = std::collections::HashMap::new();
302 let blocks_per_row = size / QUALITY_BLOCK;
303 for t in sources {
304 let part = intersect(
305 Rect {
306 x: wx as f64,
307 y: wy as f64,
308 width: overlap.width,
309 height: overlap.height,
310 },
311 Rect {
312 x: (t.sx * size) as f64,
313 y: (t.sy * size) as f64,
314 width: size as f64,
315 height: size as f64,
316 },
317 );
318 if part.width <= 0.0 || part.height <= 0.0 {
319 continue;
320 }
321 let (py, ph, px, pw) = (
322 part.y as i64,
323 part.height as i64,
324 part.x as i64,
325 part.width as i64,
326 );
327 for y in py..py + ph {
328 let dy = (overlap.y - bounds.y) as i64 + y - wy;
329 let dx = (overlap.x - bounds.x) as i64 + px - wx;
330 let from = (y - t.sy * size) * size + px - t.sx * size;
331 for x in 0..pw {
332 let source_pixel = (from + x) as usize;
333 let destination_pixel = (dy * size + dx + x) as usize;
334 out.pixels[destination_pixel * 4..destination_pixel * 4 + 4]
335 .copy_from_slice(&t.pixels[source_pixel * 4..source_pixel * 4 + 4]);
336 if bit(t.coverage, source_pixel) {
337 set_bit(out.coverage, destination_pixel);
338 let sbx = (source_pixel as i64 % size) / QUALITY_BLOCK;
339 let sby = (source_pixel as i64 / size) / QUALITY_BLOCK;
340 let source_block = sby * blocks_per_row + sbx;
341 let dbx = (destination_pixel as i64 % size) / QUALITY_BLOCK;
342 let dby = (destination_pixel as i64 / size) / QUALITY_BLOCK;
343 let destination_block = dby * blocks_per_row + dbx;
344 let agg = evidence.entry(destination_block).or_insert(BlockAgg {
345 quality: 255,
346 score: 0.0,
347 score_set: false,
348 owner_single: None,
349 owner_conflict: false,
350 conflicts: false,
351 frozen: false,
352 });
353 agg.quality = agg.quality.min(t.quality[source_block as usize]);
354 let sscore = t.score[source_block as usize];
355 agg.score = if agg.score_set {
356 agg.score.min(sscore)
357 } else {
358 sscore
359 };
360 agg.score_set = true;
361 let sowner = t.owner[source_block as usize];
362 match agg.owner_single {
363 None if !agg.owner_conflict => agg.owner_single = Some(sowner),
364 Some(existing) if existing != sowner => {
365 agg.owner_conflict = true;
366 agg.owner_single = None;
367 }
368 _ => {}
369 }
370 agg.conflicts |= t.conflicts[source_block as usize] != 0;
371 agg.frozen |= t.frozen[source_block as usize] != 0;
372 }
373 if bit(t.provisional, source_pixel) {
374 set_bit(out.provisional, destination_pixel);
375 }
376 }
377 }
378 }
379 for (q, agg) in evidence {
380 out.quality[q as usize] = agg.quality;
381 out.score[q as usize] = if agg.score_set { agg.score } else { 0.0 };
382 out.conflicts[q as usize] = agg.conflicts as u8;
383 out.frozen[q as usize] = agg.frozen as u8;
384 out.owner[q as usize] = match agg.owner_single {
387 Some(o) if o != 0 && !agg.owner_conflict => o,
388 _ => 0,
389 };
390 }
391}
392
393#[cfg(test)]
394mod tests {
395 use super::*;
396
397 fn rect(x: f64, y: f64, w: f64, h: f64) -> Rect {
398 Rect {
399 x,
400 y,
401 width: w,
402 height: h,
403 }
404 }
405
406 #[test]
407 fn layout_matches_the_frozen_ts_geometry() {
408 let layout = frame_layout(12.0, 10.0, rect(2.0, 2.0, 8.0, 6.0), 14.0, 12.0);
409 assert_eq!((layout.width, layout.height), (18.0, 16.0));
410 }
411
412 #[test]
413 fn coordinate_classifies_content_extension_and_source() {
414 let layout = frame_layout(12.0, 10.0, rect(2.0, 2.0, 8.0, 6.0), 14.0, 12.0);
415 assert!(matches!(
416 frame_coordinate(&layout, 5.0, 5.0),
417 Coordinate::Content
418 ));
419 match frame_coordinate(&layout, 0.0, 0.0) {
420 Coordinate::Source(x, y) => assert_eq!((x, y), (0.0, 0.0)),
421 _ => panic!("expected source"),
422 }
423 }
424
425 #[test]
426 fn mode_breaks_ties_by_first_arrival() {
427 assert_eq!(mode(&[3, 1, 1, 3]), 1);
430 assert_eq!(mode(&[]), 0);
431 }
432}