1use std::borrow::Cow;
6use std::cell::{Cell, RefCell, RefMut};
7use std::collections::BTreeMap;
8use std::mem;
9use std::ops::{ControlFlow, Range};
10use std::slice::ChunksExactMut;
11
12use rayon::prelude::*;
13
14use crate::layout::{Flusher, Layout, Slice, TileFill};
15use crate::painter::layer_workbench::{OptimizerTileWriteOp, TileWriteOp};
16use crate::rasterizer::{search_last_by_key, PixelSegment};
17use crate::simd::{f32x4, f32x8, i16x16, i32x8, i8x16, u32x4, u32x8, u8x32, Simd};
18use crate::{PIXEL_DOUBLE_WIDTH, PIXEL_WIDTH, TILE_HEIGHT, TILE_WIDTH};
19
20mod layer_workbench;
21#[macro_use]
22mod style;
23
24use layer_workbench::{Context, LayerPainter, LayerWorkbench};
25
26pub use style::{
27 BlendMode, Fill, FillRule, Gradient, GradientBuilder, GradientType, Image, ImageId, Style,
28 Texture,
29};
30
31pub use self::style::{Channel, Color, BGR0, BGR1, BGRA, RGB0, RGB1, RGBA};
32
33const PIXEL_AREA: usize = PIXEL_WIDTH * PIXEL_WIDTH;
34const PIXEL_DOUBLE_AREA: usize = 2 * PIXEL_AREA;
35
36const C23: u32 = 0x4B00_0000;
38
39macro_rules! cols {
40 ( & $array:expr, $x0:expr, $x1:expr ) => {{
41 fn size_of_el<T: Simd>(_: impl AsRef<[T]>) -> usize {
42 T::LANES
43 }
44
45 let from = $x0 * crate::TILE_HEIGHT / size_of_el(&$array);
46 let to = $x1 * crate::TILE_HEIGHT / size_of_el(&$array);
47
48 &$array[from..to]
49 }};
50
51 ( & mut $array:expr, $x0:expr, $x1:expr ) => {{
52 fn size_of_el<T: Simd>(_: impl AsRef<[T]>) -> usize {
53 T::LANES
54 }
55
56 let from = $x0 * crate::TILE_HEIGHT / size_of_el(&$array);
57 let to = $x1 * crate::TILE_HEIGHT / size_of_el(&$array);
58
59 &mut $array[from..to]
60 }};
61}
62
63#[inline]
64fn doubled_area_to_coverage(doubled_area: i32x8, fill_rule: FillRule) -> f32x8 {
65 match fill_rule {
66 FillRule::NonZero => {
67 let doubled_area: f32x8 = doubled_area.into();
68 (doubled_area * f32x8::splat((PIXEL_DOUBLE_AREA as f32).recip()))
69 .abs()
70 .clamp(f32x8::splat(0.0), f32x8::splat(1.0))
71 }
72 FillRule::EvenOdd => {
73 let doubled_area: f32x8 = (i32x8::splat(PIXEL_DOUBLE_AREA as i32)
74 - ((doubled_area & i32x8::splat(2 * PIXEL_DOUBLE_AREA as i32 - 1))
75 - i32x8::splat(PIXEL_DOUBLE_AREA as i32))
76 .abs())
77 .into();
78 doubled_area * f32x8::splat((PIXEL_DOUBLE_AREA as f32).recip())
79 }
80 }
81}
82
83#[allow(clippy::many_single_char_names)]
84#[inline]
85fn linear_to_srgb_approx_simdx8(l: f32x8) -> f32x8 {
86 let a = f32x8::splat(0.201_017_72f32);
87 let b = f32x8::splat(-0.512_801_47f32);
88 let c = f32x8::splat(1.344_401f32);
89 let d = f32x8::splat(-0.030_656_587f32);
90
91 let s = l.sqrt();
92 let s2 = l;
93 let s3 = s2 * s;
94
95 let m = l * f32x8::splat(12.92);
96 let n = a.mul_add(s3, b.mul_add(s2, c.mul_add(s, d)));
97
98 m.select(n, l.le(f32x8::splat(0.003_130_8)))
99}
100
101#[allow(clippy::many_single_char_names)]
102#[inline]
103fn linear_to_srgb_approx_simdx4(l: f32x4) -> f32x4 {
104 let a = f32x4::splat(0.201_017_72f32);
105 let b = f32x4::splat(-0.512_801_47f32);
106 let c = f32x4::splat(1.344_401f32);
107 let d = f32x4::splat(-0.030_656_587f32);
108
109 let s = l.sqrt();
110 let s2 = l;
111 let s3 = s2 * s;
112
113 let m = l * f32x4::splat(12.92);
114 let n = a.mul_add(s3, b.mul_add(s2, c.mul_add(s, d)));
115
116 m.select(n, l.le(f32x4::splat(0.003_130_8)))
117}
118
119#[inline]
122fn to_u32x8(val: f32x8) -> u32x8 {
123 let max = f32x8::splat(f32::from(u8::MAX));
124 let c23 = u32x8::splat(C23);
125
126 let scaled = (val * max).clamp(f32x8::splat(0.0), max);
127 let val = scaled + f32x8::from_bits(c23);
128
129 val.to_bits()
130}
131
132#[inline]
133fn to_u32x4(val: f32x4) -> u32x4 {
134 let max = f32x4::splat(f32::from(u8::MAX));
135 let c23 = u32x4::splat(C23);
136
137 let scaled = (val * max).clamp(f32x4::splat(0.0), max);
138 let val = scaled + f32x4::from_bits(c23);
139
140 val.to_bits()
141}
142
143#[inline]
144fn to_srgb_bytes(color: [f32; 4]) -> [u8; 4] {
145 let linear = f32x4::new([color[0], color[1], color[2], 0.0]);
146 let srgb = to_u32x4(linear_to_srgb_approx_simdx4(linear).set::<3>(color[3]));
147
148 srgb.into()
149}
150
151#[derive(Clone, Debug, Eq, Hash, PartialEq)]
152pub struct Rect {
153 pub(crate) hor: Range<usize>,
154 pub(crate) vert: Range<usize>,
155}
156
157impl Rect {
158 pub fn new(horizontal: Range<usize>, vertical: Range<usize>) -> Self {
159 Self {
160 hor: horizontal.start / TILE_WIDTH..(horizontal.end + TILE_WIDTH - 1) / TILE_WIDTH,
161 vert: vertical.start / TILE_HEIGHT..(vertical.end + TILE_HEIGHT - 1) / TILE_HEIGHT,
162 }
163 }
164}
165
166#[derive(Clone, Debug, Eq, Hash, PartialEq)]
167pub enum Func {
168 Draw(Style),
169 Clip(usize),
173}
174
175impl Default for Func {
176 fn default() -> Self {
177 Self::Draw(Style::default())
178 }
179}
180
181#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
182pub struct Props {
183 pub fill_rule: FillRule,
184 pub func: Func,
185}
186
187pub trait LayerProps: Send + Sync {
188 fn get(&self, layer_id: u32) -> Cow<'_, Props>;
189 fn is_unchanged(&self, layer_id: u32) -> bool;
190}
191
192#[derive(Clone, Copy, Debug, Default)]
193pub(crate) struct Cover {
194 covers: [i8x16; TILE_HEIGHT / i8x16::LANES],
197}
198
199impl Cover {
200 pub fn as_slice_mut(&mut self) -> &mut [i8; TILE_HEIGHT] {
201 unsafe { mem::transmute(&mut self.covers) }
202 }
203
204 pub fn add_cover_to(&self, covers: &mut [i8x16]) {
205 for (i, &cover) in self.covers.iter().enumerate() {
206 covers[i] += cover;
207 }
208 }
209
210 pub fn is_empty(&self, fill_rule: FillRule) -> bool {
211 match fill_rule {
212 FillRule::NonZero => self.covers.iter().all(|&cover| cover.eq(i8x16::splat(0)).all()),
213 FillRule::EvenOdd => self
214 .covers
215 .iter()
216 .all(|&cover| (cover.abs() & i8x16::splat(31)).eq(i8x16::splat(0)).all()),
217 }
218 }
219
220 pub fn is_full(&self, fill_rule: FillRule) -> bool {
221 match fill_rule {
222 FillRule::NonZero => self
223 .covers
224 .iter()
225 .all(|&cover| cover.abs().eq(i8x16::splat(PIXEL_WIDTH as i8)).all()),
226 FillRule::EvenOdd => self.covers.iter().any(|&cover| {
227 (cover.abs() & i8x16::splat(0b1_1111)).eq(i8x16::splat(0b1_0000)).all()
228 }),
229 }
230 }
231}
232
233impl PartialEq for Cover {
234 fn eq(&self, other: &Self) -> bool {
235 self.covers.iter().zip(other.covers.iter()).all(|(t, o)| t.eq(*o).all())
236 }
237}
238
239#[derive(Clone, Copy, Debug)]
240pub struct CoverCarry {
241 cover: Cover,
242 layer_id: u32,
243}
244
245#[derive(Debug)]
246pub(crate) struct Painter {
247 doubled_areas: [i16x16; TILE_WIDTH * TILE_HEIGHT / i16x16::LANES],
248 covers: [i8x16; (TILE_WIDTH + 1) * TILE_HEIGHT / i8x16::LANES],
249 clip: Option<([f32x8; TILE_WIDTH * TILE_HEIGHT / f32x8::LANES], u32)>,
250 red: [f32x8; TILE_WIDTH * TILE_HEIGHT / f32x8::LANES],
251 green: [f32x8; TILE_WIDTH * TILE_HEIGHT / f32x8::LANES],
252 blue: [f32x8; TILE_WIDTH * TILE_HEIGHT / f32x8::LANES],
253 alpha: [f32x8; TILE_WIDTH * TILE_HEIGHT / f32x8::LANES],
254 srgb: [u8x32; TILE_WIDTH * TILE_HEIGHT * 4 / u8x32::LANES],
255}
256
257impl LayerPainter for Painter {
258 fn clear_cells(&mut self) {
259 self.doubled_areas.iter_mut().for_each(|doubled_area| *doubled_area = i16x16::splat(0));
260 self.covers.iter_mut().for_each(|cover| *cover = i8x16::splat(0));
261 }
262
263 fn acc_segment(&mut self, segment: PixelSegment<TILE_WIDTH, TILE_HEIGHT>) {
264 let x = segment.local_x() as usize;
265 let y = segment.local_y() as usize;
266
267 let doubled_areas: &mut [i16; TILE_WIDTH * TILE_HEIGHT] =
268 unsafe { mem::transmute(&mut self.doubled_areas) };
269 let covers: &mut [i8; (TILE_WIDTH + 1) * TILE_HEIGHT] =
270 unsafe { mem::transmute(&mut self.covers) };
271
272 doubled_areas[x * TILE_HEIGHT + y] += segment.double_area();
273 covers[(x + 1) * TILE_HEIGHT + y] += segment.cover();
274 }
275
276 fn acc_cover(&mut self, cover: Cover) {
277 cover.add_cover_to(&mut self.covers);
278 }
279
280 fn clear(&mut self, color: Color) {
281 self.red.iter_mut().for_each(|r| *r = f32x8::splat(color.r));
282 self.green.iter_mut().for_each(|g| *g = f32x8::splat(color.g));
283 self.blue.iter_mut().for_each(|b| *b = f32x8::splat(color.b));
284 self.alpha.iter_mut().for_each(|alpha| *alpha = f32x8::splat(color.a));
285 }
286
287 fn paint_layer(
288 &mut self,
289 tile_x: usize,
290 tile_y: usize,
291 layer_id: u32,
292 props: &Props,
293 apply_clip: bool,
294 ) -> Cover {
295 let mut doubled_areas = [i32x8::splat(0); TILE_HEIGHT / i32x8::LANES];
296 let mut covers = [i8x16::splat(0); TILE_HEIGHT / i8x16::LANES];
297 let mut coverages = [f32x8::splat(0.0); TILE_HEIGHT / f32x8::LANES];
298
299 if let Some((_, last_layer)) = self.clip {
300 if last_layer < layer_id {
301 self.clip = None;
302 }
303 }
304
305 for x in 0..=TILE_WIDTH {
306 if x != 0 {
307 self.compute_doubled_areas(x - 1, &covers, &mut doubled_areas);
308
309 for y in 0..coverages.len() {
310 coverages[y] = doubled_area_to_coverage(doubled_areas[y], props.fill_rule);
311
312 match &props.func {
313 Func::Draw(style) => {
314 if coverages[y].eq(f32x8::splat(0.0)).all() {
315 continue;
316 }
317
318 if apply_clip && self.clip.is_none() {
319 continue;
320 }
321
322 let fill = Self::fill_at(
323 x - 1 + tile_x * TILE_WIDTH,
324 y * f32x8::LANES + tile_y * TILE_HEIGHT,
325 style,
326 );
327
328 self.blend_at(x - 1, y, coverages, apply_clip, fill, style.blend_mode);
329 }
330 Func::Clip(layers) => {
331 self.clip_at(x - 1, y, coverages, layer_id + *layers as u32)
332 }
333 }
334 }
335 }
336
337 let column = cols!(&self.covers, x, x + 1);
338 for y in 0..column.len() {
339 covers[y] += column[y];
340 }
341 }
342
343 Cover { covers }
344 }
345}
346
347impl Painter {
348 pub fn new() -> Self {
349 Self {
350 doubled_areas: [i16x16::splat(0); TILE_WIDTH * TILE_HEIGHT / i16x16::LANES],
351 covers: [i8x16::splat(0); (TILE_WIDTH + 1) * TILE_HEIGHT / i8x16::LANES],
352 clip: None,
353 red: [f32x8::splat(0.0); TILE_WIDTH * TILE_HEIGHT / f32x8::LANES],
354 green: [f32x8::splat(0.0); TILE_WIDTH * TILE_HEIGHT / f32x8::LANES],
355 blue: [f32x8::splat(0.0); TILE_WIDTH * TILE_HEIGHT / f32x8::LANES],
356 alpha: [f32x8::splat(1.0); TILE_WIDTH * TILE_HEIGHT / f32x8::LANES],
357 srgb: [u8x32::splat(0); TILE_WIDTH * TILE_HEIGHT * 4 / u8x32::LANES],
358 }
359 }
360
361 #[inline]
362 fn fill_at(x: usize, y: usize, style: &Style) -> [f32x8; 4] {
363 match &style.fill {
364 Fill::Solid(color) => {
365 let Color { r, g, b, a } = *color;
366 [f32x8::splat(r), f32x8::splat(g), f32x8::splat(b), f32x8::splat(a)]
367 }
368 Fill::Gradient(gradient) => gradient.color_at(x as f32, y as f32),
369 Fill::Texture(texture) => texture.color_at(x as f32, y as f32),
370 }
371 }
372
373 fn compute_doubled_areas(
374 &self,
375 x: usize,
376 covers: &[i8x16; TILE_HEIGHT / i8x16::LANES],
377 doubled_areas: &mut [i32x8; TILE_HEIGHT / i32x8::LANES],
378 ) {
379 let column = cols!(&self.doubled_areas, x, x + 1);
380 for y in 0..covers.len() {
381 let covers: [i32x8; 2] = covers[y].into();
382 let column: [i32x8; 2] = column[y].into();
383
384 for yy in 0..2 {
385 doubled_areas[2 * y + yy] =
386 i32x8::splat(PIXEL_DOUBLE_WIDTH as i32) * covers[yy] + column[yy];
387 }
388 }
389 }
390
391 fn blend_at(
392 &mut self,
393 x: usize,
394 y: usize,
395 coverages: [f32x8; TILE_HEIGHT / f32x8::LANES],
396 is_clipped: bool,
397 fill: [f32x8; 4],
398 blend_mode: BlendMode,
399 ) {
400 let dst_r = &mut cols!(&mut self.red, x, x + 1)[y];
401 let dst_g = &mut cols!(&mut self.green, x, x + 1)[y];
402 let dst_b = &mut cols!(&mut self.blue, x, x + 1)[y];
403 let dst_a = &mut cols!(&mut self.alpha, x, x + 1)[y];
404
405 let src_r = fill[0];
406 let src_g = fill[1];
407 let src_b = fill[2];
408 let mut src_a = fill[3] * coverages[y];
409
410 if is_clipped {
411 if let Some((mask, _)) = self.clip {
412 src_a *= cols!(&mask, x, x + 1)[y];
413 }
414 }
415
416 let [blended_r, blended_g, blended_b] =
417 blend_function!(blend_mode, *dst_r, *dst_g, *dst_b, src_r, src_g, src_b);
418
419 let inv_dst_a = f32x8::splat(1.0) - *dst_a;
420 let inv_dst_a_src_a = inv_dst_a * src_a;
421 let inv_src_a = f32x8::splat(1.0) - src_a;
422 let dst_a_src_a = *dst_a * src_a;
423
424 let current_r = src_r.mul_add(inv_dst_a_src_a, blended_r * dst_a_src_a);
425 let current_g = src_g.mul_add(inv_dst_a_src_a, blended_g * dst_a_src_a);
426 let current_b = src_b.mul_add(inv_dst_a_src_a, blended_b * dst_a_src_a);
427
428 *dst_r = dst_r.mul_add(inv_src_a, current_r);
429 *dst_g = dst_g.mul_add(inv_src_a, current_g);
430 *dst_b = dst_b.mul_add(inv_src_a, current_b);
431 *dst_a = dst_a.mul_add(inv_src_a, src_a);
432 }
433
434 fn clip_at(
435 &mut self,
436 x: usize,
437 y: usize,
438 coverages: [f32x8; TILE_HEIGHT / f32x8::LANES],
439 last_layer_id: u32,
440 ) {
441 let clip = self.clip.get_or_insert_with(|| {
442 ([f32x8::splat(0.0); TILE_WIDTH * TILE_HEIGHT / f32x8::LANES], last_layer_id)
443 });
444 cols!(&mut clip.0, x, x + 1)[y] = coverages[y];
445 }
446
447 fn compute_srgb(&mut self, channels: [Channel; 4]) {
448 for ((((&red, &green), &blue), &alpha), srgb) in self
449 .red
450 .iter()
451 .zip(self.green.iter())
452 .zip(self.blue.iter())
453 .zip(self.alpha.iter())
454 .zip(self.srgb.iter_mut())
455 {
456 let red = linear_to_srgb_approx_simdx8(red);
457 let green = linear_to_srgb_approx_simdx8(green);
458 let blue = linear_to_srgb_approx_simdx8(blue);
459
460 let unpacked = channels.map(|c| to_u32x8(c.select(red, green, blue, alpha)));
461
462 *srgb = u8x32::from_u32_interleaved(unpacked);
463 }
464 }
465
466 #[allow(clippy::too_many_arguments)]
467 pub fn paint_tile_row<S: LayerProps, L: Layout>(
468 &mut self,
469 workbench: &mut LayerWorkbench,
470 tile_y: usize,
471 mut segments: &[PixelSegment<TILE_WIDTH, TILE_HEIGHT>],
472 props: &S,
473 channels: [Channel; 4],
474 clear_color: Color,
475 previous_clear_color: Option<Color>,
476 cached_tiles: Option<&[CachedTile]>,
477 row: ChunksExactMut<'_, Slice<'_, u8>>,
478 crop: &Option<Rect>,
479 flusher: Option<&dyn Flusher>,
480 ) {
481 let mut covers_left_of_row: BTreeMap<u32, Cover> = BTreeMap::new();
483 let tile_x_start = crop.as_ref().map(|rect| rect.hor.start as i16).unwrap_or_default();
484 if let Ok(last_clipped_index) =
485 search_last_by_key(segments, false, |segment| segment.tile_x() >= tile_x_start)
486 {
487 for segment in &segments[..=last_clipped_index] {
489 let cover = covers_left_of_row.entry(segment.layer_id()).or_default();
490 cover.as_slice_mut()[segment.local_y() as usize] += segment.cover();
491 }
492
493 segments = &segments[last_clipped_index + 1..];
494 }
495
496 workbench.init(
497 covers_left_of_row.into_iter().map(|(layer_id, cover)| CoverCarry { cover, layer_id }),
498 );
499
500 for (tile_x, slices) in row.enumerate() {
501 if let Some(rect) = &crop {
502 if !rect.hor.contains(&tile_x) {
503 continue;
504 }
505 }
506
507 let current_segments =
508 search_last_by_key(segments, tile_x as i16, |segment| segment.tile_x())
509 .map(|last_index| {
510 let current_segments = &segments[..=last_index];
511 segments = &segments[last_index + 1..];
512 current_segments
513 })
514 .unwrap_or(&[]);
515
516 let context = Context {
517 tile_x,
518 tile_y,
519 segments: current_segments,
520 props,
521 cached_clear_color: previous_clear_color,
522 cached_tile: cached_tiles.map(|cached_tiles| &cached_tiles[tile_x]),
523 channels,
524 clear_color,
525 };
526
527 self.clip = None;
528
529 match workbench.drive_tile_painting(self, &context) {
530 TileWriteOp::None => (),
531 TileWriteOp::Solid(color) => L::write(slices, flusher, TileFill::Solid(color)),
532 TileWriteOp::ColorBuffer => {
533 self.compute_srgb(channels);
534 let colors: &[[u8; 4]] = unsafe {
535 std::slice::from_raw_parts(
536 self.srgb.as_ptr().cast(),
537 self.srgb.len() * mem::size_of::<u8x32>() / mem::size_of::<[u8; 4]>(),
538 )
539 };
540 L::write(slices, flusher, TileFill::Full(colors));
541 }
542 }
543 }
544 }
545}
546
547thread_local!(static PAINTER_WORKBENCH: RefCell<(Painter, LayerWorkbench)> = RefCell::new((
548 Painter::new(),
549 LayerWorkbench::new(),
550)));
551
552#[allow(clippy::too_many_arguments)]
553fn print_row<S: LayerProps, L: Layout>(
554 segments: &[PixelSegment<TILE_WIDTH, TILE_HEIGHT>],
555 channels: [Channel; 4],
556 clear_color: Color,
557 crop: &Option<Rect>,
558 styles: &S,
559 j: usize,
560 row: ChunksExactMut<'_, Slice<'_, u8>>,
561 previous_clear_color: Option<Color>,
562 cached_tiles: Option<&[CachedTile]>,
563 flusher: Option<&dyn Flusher>,
564) {
565 if let Some(rect) = crop {
566 if !rect.vert.contains(&j) {
567 return;
568 }
569 }
570
571 let segments = search_last_by_key(segments, j as i16, |segment| segment.tile_y())
572 .map(|end| {
573 let result =
574 search_last_by_key(&segments[..end], j as i16 - 1, |segment| segment.tile_y());
575 let start = match result {
576 Ok(i) => i + 1,
577 Err(i) => i,
578 };
579
580 &segments[start..=end]
581 })
582 .unwrap_or(&[]);
583
584 PAINTER_WORKBENCH.with(|pair| {
585 let (mut painter, mut workbench) =
586 RefMut::map_split(pair.borrow_mut(), |pair| (&mut pair.0, &mut pair.1));
587
588 painter.paint_tile_row::<S, L>(
589 &mut workbench,
590 j,
591 segments,
592 styles,
593 channels,
594 clear_color,
595 previous_clear_color,
596 cached_tiles,
597 row,
598 crop,
599 flusher,
600 );
601 });
602}
603
604#[derive(Clone, Debug, Default)]
605pub struct CachedTile {
606 tags: Cell<u8>,
608 layer_count: Cell<[u8; 3]>,
610 solid_color: Cell<[u8; 4]>,
612}
613
614impl CachedTile {
615 pub fn layer_count(&self) -> Option<u32> {
616 let layer_count = self.layer_count.get();
617 let layer_count = u32::from_le_bytes([layer_count[0], layer_count[1], layer_count[2], 0]);
618
619 match self.tags.get() {
620 0b10 | 0b11 => Some(layer_count),
621 _ => None,
622 }
623 }
624
625 pub fn solid_color(&self) -> Option<[u8; 4]> {
626 match self.tags.get() {
627 0b01 | 0b11 => Some(self.solid_color.get()),
628 _ => None,
629 }
630 }
631
632 pub fn update_layer_count(&self, layer_count: Option<u32>) -> Option<u32> {
633 let previous_layer_count = self.layer_count();
634 match layer_count {
635 None => {
636 self.tags.set(self.tags.get() & 0b01);
637 }
638 Some(layer_count) => {
639 self.tags.set(self.tags.get() | 0b10);
640 self.layer_count.set(layer_count.to_le_bytes()[..3].try_into().unwrap());
641 }
642 };
643 previous_layer_count
644 }
645
646 pub fn update_solid_color(&self, solid_color: Option<[u8; 4]>) -> Option<[u8; 4]> {
647 let previous_solid_color = self.solid_color();
648 match solid_color {
649 None => {
650 self.tags.set(self.tags.get() & 0b10);
651 }
652 Some(color) => {
653 self.tags.set(self.tags.get() | 0b01);
654 self.solid_color.set(color);
655 }
656 };
657 previous_solid_color
658 }
659
660 pub fn convert_optimizer_op<P: LayerProps>(
661 tile_op: ControlFlow<OptimizerTileWriteOp>,
662 context: &Context<'_, P>,
663 ) -> ControlFlow<TileWriteOp> {
664 match tile_op {
665 ControlFlow::Break(OptimizerTileWriteOp::Solid(color)) => {
666 let color = to_srgb_bytes(context.channels.map(|c| color.channel(c)));
667 let color_is_unchanged = context
668 .cached_tile
669 .as_ref()
670 .map(|cached_tile| cached_tile.update_solid_color(Some(color)) == Some(color))
671 .unwrap_or_default();
672
673 if color_is_unchanged {
674 ControlFlow::Break(TileWriteOp::None)
675 } else {
676 ControlFlow::Break(TileWriteOp::Solid(color))
677 }
678 }
679 ControlFlow::Break(OptimizerTileWriteOp::None) => ControlFlow::Break(TileWriteOp::None),
680 _ => {
681 if let Some(cached_tile) = context.cached_tile {
682 cached_tile.update_solid_color(None);
683 }
684
685 ControlFlow::Continue(())
686 }
687 }
688 }
689}
690
691#[allow(clippy::too_many_arguments)]
692#[inline]
693pub fn for_each_row<L: Layout, S: LayerProps>(
694 layout: &mut L,
695 buffer: &mut [u8],
696 channels: [Channel; 4],
697 flusher: Option<&dyn Flusher>,
698 previous_clear_color: Option<Color>,
699 cached_tiles: Option<RefMut<'_, Vec<CachedTile>>>,
700 mut segments: &[PixelSegment<TILE_WIDTH, TILE_HEIGHT>],
701 clear_color: Color,
702 crop: &Option<Rect>,
703 styles: &S,
704) {
705 if let Ok(start) = search_last_by_key(segments, false, |segment| segment.tile_y() >= 0) {
707 segments = &segments[start + 1..];
708 }
709
710 let width_in_tiles = layout.width_in_tiles();
711 let row_of_tiles_len = width_in_tiles * layout.slices_per_tile();
712 let mut slices = layout.slices(buffer);
713
714 if let Some(mut cached_tiles) = cached_tiles {
715 slices
716 .par_chunks_mut(row_of_tiles_len)
717 .zip_eq(cached_tiles.par_chunks_mut(width_in_tiles))
718 .enumerate()
719 .for_each(|(j, (row_of_tiles, cached_tiles))| {
720 print_row::<S, L>(
721 segments,
722 channels,
723 clear_color,
724 crop,
725 styles,
726 j,
727 row_of_tiles.chunks_exact_mut(row_of_tiles.len() / width_in_tiles),
728 previous_clear_color,
729 Some(cached_tiles),
730 flusher,
731 );
732 });
733 } else {
734 slices.par_chunks_mut(row_of_tiles_len).enumerate().for_each(|(j, row_of_tiles)| {
735 print_row::<S, L>(
736 segments,
737 channels,
738 clear_color,
739 crop,
740 styles,
741 j,
742 row_of_tiles.chunks_exact_mut(row_of_tiles.len() / width_in_tiles),
743 previous_clear_color,
744 None,
745 flusher,
746 );
747 });
748 }
749}
750
751#[cfg(feature = "bench")]
752pub fn painter_fill_at_bench(width: usize, height: usize, style: &Style) -> f32x8 {
753 let mut sum = f32x8::indexed();
754 for y in 0..width {
755 for x in 0..height {
756 for c in Painter::fill_at(x, y, style) {
757 sum += c;
758 }
759 }
760 }
761 sum
762}
763
764#[cfg(test)]
765mod tests {
766 use super::*;
767
768 use std::collections::HashMap;
769 use std::iter;
770
771 use crate::layout::LinearLayout;
772 use crate::point::Point;
773 use crate::rasterizer::Rasterizer;
774 use crate::{GeomId, Layer, LinesBuilder, Order};
775
776 const RED: Color = Color { r: 1.0, g: 0.0, b: 0.0, a: 1.0 };
777 const RED_GREEN_50: Color = Color { r: 1.0, g: 0.5, b: 0.0, a: 1.0 };
778 const RED_50: Color = Color { r: 0.5, g: 0.0, b: 0.0, a: 1.0 };
779 const RED_50_GREEN_50: Color = Color { r: 0.5, g: 0.5, b: 0.0, a: 1.0 };
780 const GREEN: Color = Color { r: 0.0, g: 1.0, b: 0.0, a: 1.0 };
781 const GREEN_50: Color = Color { r: 0.0, g: 0.5, b: 0.0, a: 1.0 };
782 const BLUE: Color = Color { r: 0.0, g: 0.0, b: 1.0, a: 1.0 };
783 const WHITE: Color = Color { r: 1.0, g: 1.0, b: 1.0, a: 1.0 };
784 const BLACK: Color = Color { r: 0.0, g: 0.0, b: 0.0, a: 1.0 };
785 const BLACK_ALPHA_50: Color = Color { r: 0.0, g: 0.0, b: 0.0, a: 0.5 };
786 const BLACK_ALPHA_0: Color = Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 };
787
788 const BLACK_RGBA: [u8; 4] = [0, 0, 0, 255];
789 const RED_RGBA: [u8; 4] = [255, 0, 0, 255];
790 const GREEN_RGBA: [u8; 4] = [0, 255, 0, 255];
791 const BLUE_RGBA: [u8; 4] = [0, 0, 255, 255];
792
793 impl LayerProps for HashMap<u32, Style> {
794 fn get(&self, layer_id: u32) -> Cow<'_, Props> {
795 let style = self.get(&layer_id).unwrap().clone();
796
797 Cow::Owned(Props { fill_rule: FillRule::NonZero, func: Func::Draw(style) })
798 }
799
800 fn is_unchanged(&self, _: u32) -> bool {
801 false
802 }
803 }
804
805 impl LayerProps for HashMap<u32, Props> {
806 fn get(&self, layer_id: u32) -> Cow<'_, Props> {
807 Cow::Owned(self.get(&layer_id).unwrap().clone())
808 }
809
810 fn is_unchanged(&self, _: u32) -> bool {
811 false
812 }
813 }
814
815 impl<F> LayerProps for F
816 where
817 F: Fn(u32) -> Style + Send + Sync,
818 {
819 fn get(&self, layer_id: u32) -> Cow<'_, Props> {
820 let style = self(layer_id);
821
822 Cow::Owned(Props { fill_rule: FillRule::NonZero, func: Func::Draw(style) })
823 }
824
825 fn is_unchanged(&self, _: u32) -> bool {
826 false
827 }
828 }
829
830 impl Painter {
831 fn colors(&self) -> [[f32; 4]; TILE_WIDTH * TILE_HEIGHT] {
832 let mut colors = [[0.0, 0.0, 0.0, 1.0]; TILE_WIDTH * TILE_HEIGHT];
833
834 for (i, (((c0, c1), c2), alpha)) in self
835 .red
836 .iter()
837 .copied()
838 .flat_map(f32x8::to_array)
839 .zip(self.green.iter().copied().flat_map(f32x8::to_array))
840 .zip(self.blue.iter().copied().flat_map(f32x8::to_array))
841 .zip(self.alpha.iter().copied().flat_map(f32x8::to_array))
842 .enumerate()
843 {
844 colors[i] = [c0, c1, c2, alpha];
845 }
846
847 colors
848 }
849 }
850
851 fn line_segments(
852 points: &[(Point, Point)],
853 same_layer: bool,
854 ) -> Vec<PixelSegment<TILE_WIDTH, TILE_HEIGHT>> {
855 let mut builder = LinesBuilder::new();
856 let ids = iter::successors(Some(GeomId::default()), |id| Some(id.next()));
857
858 for (&(p0, p1), id) in points.iter().zip(ids) {
859 let id = if same_layer { GeomId::default() } else { id };
860 builder.push(id, [p0, p1]);
861 }
862
863 let lines = builder.build(|id| {
864 Some(Layer { order: Some(Order::new(id.get() as u32).unwrap()), ..Default::default() })
865 });
866
867 let mut rasterizer = Rasterizer::new();
868 rasterizer.rasterize(&lines);
869
870 let mut segments: Vec<_> = rasterizer.segments().to_vec();
871 segments.sort_unstable();
872
873 segments
874 }
875
876 fn paint_tile(
877 cover_carries: impl IntoIterator<Item = CoverCarry>,
878 segments: &[PixelSegment<TILE_WIDTH, TILE_HEIGHT>],
879 props: &impl LayerProps,
880 clear_color: Color,
881 ) -> [[f32; 4]; TILE_WIDTH * TILE_HEIGHT] {
882 let mut painter = Painter::new();
883 let mut workbench = LayerWorkbench::new();
884
885 let context = Context {
886 tile_x: 0,
887 tile_y: 0,
888 segments,
889 props,
890 cached_clear_color: None,
891 cached_tile: None,
892 channels: RGBA,
893 clear_color,
894 };
895
896 workbench.init(cover_carries);
897 workbench.drive_tile_painting(&mut painter, &context);
898
899 painter.colors()
900 }
901
902 fn coverage(double_area: i32, fill_rules: FillRule) -> f32 {
903 let array = doubled_area_to_coverage(i32x8::splat(double_area), fill_rules).to_array();
904
905 for val in array {
906 assert_eq!(val, array[0]);
907 }
908
909 array[0]
910 }
911
912 #[test]
913 fn double_area_non_zero() {
914 let area = PIXEL_DOUBLE_AREA as i32;
915
916 assert_eq!(coverage(-area * 2, FillRule::NonZero), 1.0);
917 assert_eq!(coverage(-area * 3 / 2, FillRule::NonZero), 1.0);
918 assert_eq!(coverage(-area, FillRule::NonZero), 1.0);
919 assert_eq!(coverage(-area / 2, FillRule::NonZero), 0.5);
920 assert_eq!(coverage(0, FillRule::NonZero), 0.0);
921 assert_eq!(coverage(area / 2, FillRule::NonZero), 0.5);
922 assert_eq!(coverage(area, FillRule::NonZero), 1.0);
923 assert_eq!(coverage(area * 3 / 2, FillRule::NonZero), 1.0);
924 assert_eq!(coverage(area * 2, FillRule::NonZero), 1.0);
925 }
926
927 #[test]
928 fn double_area_even_odd() {
929 let area = PIXEL_DOUBLE_AREA as i32;
930
931 assert_eq!(coverage(-area * 2, FillRule::NonZero), 1.0);
932 assert_eq!(coverage(-area * 3 / 2, FillRule::EvenOdd), 0.5);
933 assert_eq!(coverage(-area, FillRule::EvenOdd), 1.0);
934 assert_eq!(coverage(-area / 2, FillRule::EvenOdd), 0.5);
935 assert_eq!(coverage(0, FillRule::EvenOdd), 0.0);
936 assert_eq!(coverage(area / 2, FillRule::EvenOdd), 0.5);
937 assert_eq!(coverage(area, FillRule::EvenOdd), 1.0);
938 assert_eq!(coverage(area * 3 / 2, FillRule::EvenOdd), 0.5);
939 assert_eq!(coverage(area * 2, FillRule::NonZero), 1.0);
940 }
941
942 #[test]
943 fn carry_cover() {
944 let mut cover_carry = CoverCarry { cover: Cover::default(), layer_id: 0 };
945 cover_carry.cover.covers[0].as_mut_array()[1] = 16;
946 cover_carry.layer_id = 1;
947
948 let segments =
949 line_segments(&[(Point::new(0.0, 0.0), Point::new(0.0, TILE_HEIGHT as f32))], false);
950
951 let mut styles = HashMap::new();
952
953 styles.insert(0, Style { fill: Fill::Solid(GREEN), ..Default::default() });
954 styles.insert(1, Style { fill: Fill::Solid(RED), ..Default::default() });
955
956 assert_eq!(
957 paint_tile([cover_carry], &segments, &styles, BLACK)[0..2],
958 [GREEN, RED].map(Color::to_array),
959 );
960 }
961
962 #[test]
963 fn overlapping_triangles() {
964 let segments = line_segments(
965 &[
966 (Point::new(0.0, 0.0), Point::new(4.0, 4.0)),
967 (Point::new(4.0, 0.0), Point::new(0.0, 4.0)),
968 ],
969 false,
970 );
971
972 let mut styles = HashMap::new();
973
974 styles.insert(0, Style { fill: Fill::Solid(GREEN), ..Default::default() });
975 styles.insert(1, Style { fill: Fill::Solid(RED), ..Default::default() });
976
977 let colors = paint_tile([], &segments, &styles, BLACK);
978
979 let row_start = 0;
980 let row_end = 4;
981
982 let mut column = 0;
983 assert_eq!(
984 colors[column + row_start..column + row_end],
985 [GREEN_50, BLACK, BLACK, RED_50].map(Color::to_array),
986 );
987
988 column += TILE_HEIGHT;
989 assert_eq!(
990 colors[column + row_start..column + row_end],
991 [GREEN, GREEN_50, RED_50, RED].map(Color::to_array),
992 );
993
994 column += TILE_HEIGHT;
995 assert_eq!(
996 colors[column + row_start..column + row_end],
997 [GREEN, RED_50_GREEN_50, RED, RED].map(Color::to_array),
998 );
999
1000 column += TILE_HEIGHT;
1001 assert_eq!(
1002 colors[column + row_start..column + row_end],
1003 [RED_50_GREEN_50, RED, RED, RED].map(Color::to_array),
1004 );
1005 }
1006
1007 #[test]
1008 fn transparent_overlay() {
1009 let segments = line_segments(
1010 &[
1011 (Point::new(0.0, 0.0), Point::new(0.0, TILE_HEIGHT as f32)),
1012 (Point::new(0.0, 0.0), Point::new(0.0, TILE_HEIGHT as f32)),
1013 ],
1014 false,
1015 );
1016
1017 let mut styles = HashMap::new();
1018
1019 styles.insert(0, Style { fill: Fill::Solid(RED), ..Default::default() });
1020 styles.insert(1, Style { fill: Fill::Solid(BLACK_ALPHA_50), ..Default::default() });
1021
1022 assert_eq!(paint_tile([], &segments, &styles, BLACK)[0], RED_50.to_array());
1023 }
1024
1025 #[test]
1026 fn linear_blend_over() {
1027 let segments = line_segments(
1028 &[
1029 (Point::new(0.0, 0.0), Point::new(0.0, TILE_HEIGHT as f32)),
1030 (Point::new(0.0, 0.0), Point::new(0.0, TILE_HEIGHT as f32)),
1031 ],
1032 false,
1033 );
1034
1035 let mut styles = HashMap::new();
1036
1037 styles.insert(0, Style { fill: Fill::Solid(RED), ..Default::default() });
1038 styles.insert(
1039 1,
1040 Style { fill: Fill::Solid(Color { a: 0.5, ..GREEN }), ..Default::default() },
1041 );
1042
1043 assert_eq!(paint_tile([], &segments, &styles, BLACK)[0], RED_50_GREEN_50.to_array());
1044 }
1045
1046 #[test]
1047 fn linear_blend_difference() {
1048 let segments = line_segments(
1049 &[
1050 (Point::new(0.0, 0.0), Point::new(0.0, TILE_HEIGHT as f32)),
1051 (Point::new(0.0, 0.0), Point::new(0.0, TILE_HEIGHT as f32)),
1052 ],
1053 false,
1054 );
1055
1056 let mut styles = HashMap::new();
1057
1058 styles.insert(0, Style { fill: Fill::Solid(RED), ..Default::default() });
1059 styles.insert(
1060 1,
1061 Style {
1062 fill: Fill::Solid(Color { a: 0.5, ..GREEN }),
1063 blend_mode: BlendMode::Difference,
1064 ..Default::default()
1065 },
1066 );
1067
1068 assert_eq!(paint_tile([], &segments, &styles, BLACK)[0], RED_GREEN_50.to_array());
1069 }
1070
1071 #[test]
1072 fn linear_blend_hue_white_opaque_brackground() {
1073 let segments =
1074 line_segments(&[(Point::new(0.0, 0.0), Point::new(0.0, TILE_HEIGHT as f32))], false);
1075
1076 let mut styles = HashMap::new();
1077
1078 styles.insert(
1079 0,
1080 Style {
1081 fill: Fill::Solid(Color { a: 0.5, ..GREEN }),
1082 blend_mode: BlendMode::Hue,
1083 ..Default::default()
1084 },
1085 );
1086
1087 assert_eq!(paint_tile([], &segments, &styles, WHITE)[0], WHITE.to_array());
1088 }
1089
1090 #[test]
1091 fn linear_blend_hue_white_transparent_brackground() {
1092 let segments =
1093 line_segments(&[(Point::new(0.0, 0.0), Point::new(0.0, TILE_HEIGHT as f32))], false);
1094
1095 let mut styles = HashMap::new();
1096
1097 styles.insert(
1098 0,
1099 Style {
1100 fill: Fill::Solid(Color { a: 0.5, ..GREEN }),
1101 blend_mode: BlendMode::Hue,
1102 ..Default::default()
1103 },
1104 );
1105
1106 assert_eq!(
1107 paint_tile([], &segments, &styles, Color { a: 0.0, ..WHITE })[0],
1108 [0.5, 1.0, 0.5, 0.5],
1109 );
1110 }
1111
1112 #[test]
1113 fn cover_carry_is_empty() {
1114 assert!(Cover { covers: [i8x16::splat(0); TILE_HEIGHT / 16] }.is_empty(FillRule::NonZero));
1115 assert!(!Cover { covers: [i8x16::splat(1); TILE_HEIGHT / 16] }.is_empty(FillRule::NonZero));
1116 assert!(!Cover { covers: [i8x16::splat(-1); TILE_HEIGHT / 16] }.is_empty(FillRule::NonZero));
1117 assert!(!Cover { covers: [i8x16::splat(16); TILE_HEIGHT / 16] }.is_empty(FillRule::NonZero));
1118 assert!(
1119 !Cover { covers: [i8x16::splat(-16); TILE_HEIGHT / 16] }.is_empty(FillRule::NonZero)
1120 );
1121
1122 assert!(Cover { covers: [i8x16::splat(0); TILE_HEIGHT / 16] }.is_empty(FillRule::EvenOdd));
1123 assert!(!Cover { covers: [i8x16::splat(1); TILE_HEIGHT / 16] }.is_empty(FillRule::EvenOdd));
1124 assert!(!Cover { covers: [i8x16::splat(-1); TILE_HEIGHT / 16] }.is_empty(FillRule::EvenOdd));
1125 assert!(!Cover { covers: [i8x16::splat(16); TILE_HEIGHT / 16] }.is_empty(FillRule::EvenOdd));
1126 assert!(
1127 !Cover { covers: [i8x16::splat(-16); TILE_HEIGHT / 16] }.is_empty(FillRule::EvenOdd)
1128 );
1129 assert!(Cover { covers: [i8x16::splat(32); TILE_HEIGHT / 16] }.is_empty(FillRule::EvenOdd));
1130 assert!(Cover { covers: [i8x16::splat(-32); TILE_HEIGHT / 16] }.is_empty(FillRule::EvenOdd));
1131 assert!(!Cover { covers: [i8x16::splat(48); TILE_HEIGHT / 16] }.is_empty(FillRule::EvenOdd));
1132 assert!(
1133 !Cover { covers: [i8x16::splat(-48); TILE_HEIGHT / 16] }.is_empty(FillRule::EvenOdd)
1134 );
1135 }
1136
1137 #[test]
1138 fn cover_carry_is_full() {
1139 assert!(!Cover { covers: [i8x16::splat(0); TILE_HEIGHT / 16] }.is_full(FillRule::NonZero));
1140 assert!(!Cover { covers: [i8x16::splat(1); TILE_HEIGHT / 16] }.is_full(FillRule::NonZero));
1141 assert!(!Cover { covers: [i8x16::splat(-1); TILE_HEIGHT / 16] }.is_full(FillRule::NonZero));
1142 assert!(Cover { covers: [i8x16::splat(16); TILE_HEIGHT / 16] }.is_full(FillRule::NonZero));
1143 assert!(Cover { covers: [i8x16::splat(-16); TILE_HEIGHT / 16] }.is_full(FillRule::NonZero));
1144
1145 assert!(!Cover { covers: [i8x16::splat(0); TILE_HEIGHT / 16] }.is_full(FillRule::EvenOdd));
1146 assert!(!Cover { covers: [i8x16::splat(1); TILE_HEIGHT / 16] }.is_full(FillRule::EvenOdd));
1147 assert!(!Cover { covers: [i8x16::splat(-1); TILE_HEIGHT / 16] }.is_full(FillRule::EvenOdd));
1148 assert!(Cover { covers: [i8x16::splat(16); TILE_HEIGHT / 16] }.is_full(FillRule::EvenOdd));
1149 assert!(Cover { covers: [i8x16::splat(-16); TILE_HEIGHT / 16] }.is_full(FillRule::EvenOdd));
1150 assert!(!Cover { covers: [i8x16::splat(32); TILE_HEIGHT / 16] }.is_full(FillRule::EvenOdd));
1151 assert!(!Cover { covers: [i8x16::splat(-32); TILE_HEIGHT / 16] }.is_full(FillRule::EvenOdd));
1152 assert!(Cover { covers: [i8x16::splat(48); TILE_HEIGHT / 16] }.is_full(FillRule::EvenOdd));
1153 assert!(Cover { covers: [i8x16::splat(-48); TILE_HEIGHT / 16] }.is_full(FillRule::EvenOdd));
1154 }
1155
1156 #[test]
1157 fn clip() {
1158 let segments = line_segments(
1159 &[
1160 (Point::new(0.0, 0.0), Point::new(4.0, 4.0)),
1161 (Point::new(0.0, 0.0), Point::new(0.0, 4.0)),
1162 (Point::new(0.0, 0.0), Point::new(0.0, 4.0)),
1163 ],
1164 false,
1165 );
1166
1167 let mut props = HashMap::new();
1168
1169 props.insert(0, Props { fill_rule: FillRule::NonZero, func: Func::Clip(2) });
1170 props.insert(
1171 1,
1172 Props {
1173 fill_rule: FillRule::NonZero,
1174 func: Func::Draw(Style {
1175 fill: Fill::Solid(GREEN),
1176 is_clipped: true,
1177 ..Default::default()
1178 }),
1179 },
1180 );
1181 props.insert(
1182 2,
1183 Props {
1184 fill_rule: FillRule::NonZero,
1185 func: Func::Draw(Style {
1186 fill: Fill::Solid(RED),
1187 is_clipped: true,
1188 ..Default::default()
1189 }),
1190 },
1191 );
1192 props.insert(
1193 3,
1194 Props {
1195 fill_rule: FillRule::NonZero,
1196 func: Func::Draw(Style { fill: Fill::Solid(GREEN), ..Default::default() }),
1197 },
1198 );
1199
1200 let mut painter = Painter::new();
1201 let mut workbench = LayerWorkbench::new();
1202
1203 let mut context = Context {
1204 tile_x: 0,
1205 tile_y: 0,
1206 segments: &segments,
1207 props: &props,
1208 cached_clear_color: None,
1209 cached_tile: None,
1210 channels: RGBA,
1211 clear_color: BLACK,
1212 };
1213
1214 workbench.drive_tile_painting(&mut painter, &context);
1215
1216 let colors = painter.colors();
1217 let mut col = [BLACK.to_array(); 4];
1218
1219 for i in 0..4 {
1220 col[i] = [0.5, 0.25, 0.0, 1.0];
1221
1222 if i >= 1 {
1223 col[i - 1] = RED.to_array();
1224 }
1225
1226 assert_eq!(colors[i * TILE_HEIGHT..i * TILE_HEIGHT + 4], col);
1227 }
1228
1229 let segments = line_segments(&[(Point::new(4.0, 0.0), Point::new(4.0, 4.0))], false);
1230
1231 context.tile_x = 1;
1232 context.segments = &segments;
1233
1234 workbench.drive_tile_painting(&mut painter, &context);
1235 for i in 0..4 {
1236 assert_eq!(painter.colors()[i * TILE_HEIGHT..i * TILE_HEIGHT + 4], [RED.to_array(); 4]);
1237 }
1238 }
1239
1240 #[test]
1241 fn f32_to_u8_scaled() {
1242 fn convert(val: f32) -> u8 {
1243 let vals: [u8; 4] = to_u32x4(f32x4::splat(val)).into();
1244 vals[0]
1245 }
1246
1247 assert_eq!(convert(-0.001), 0);
1248 assert_eq!(convert(1.001), 255);
1249
1250 for i in 0..255 {
1251 assert_eq!(convert(f32::from(i) * 255.0f32.recip()), i);
1252 }
1253 }
1254
1255 #[test]
1256 fn srgb() {
1257 let premultiplied = [
1258 0.001 * 0.5,
1260 0.2 * 0.5,
1262 0.5 * 0.5,
1264 0.5,
1266 ];
1267
1268 assert_eq!(to_srgb_bytes(premultiplied), [2, 89, 137, 128]);
1269 }
1270
1271 #[test]
1272 fn flusher() {
1273 macro_rules! seg {
1274 ( $j:expr, $i:expr ) => {
1275 PixelSegment::new($j, $i, 0, 0, 0, 0, 0)
1276 };
1277 }
1278
1279 #[derive(Debug)]
1280 struct WhiteFlusher;
1281
1282 impl Flusher for WhiteFlusher {
1283 fn flush(&self, slice: &mut [u8]) {
1284 for color in slice {
1285 *color = 255u8;
1286 }
1287 }
1288 }
1289
1290 let width = TILE_WIDTH + TILE_WIDTH / 2;
1291 let mut buffer = vec![0u8; width * TILE_HEIGHT * 4];
1292 let mut buffer_layout = LinearLayout::new(width, width * 4, TILE_HEIGHT);
1293
1294 let segments = &[seg!(0, 0), seg!(0, 1), seg!(1, 0), seg!(1, 1)];
1295
1296 for_each_row(
1297 &mut buffer_layout,
1298 &mut buffer,
1299 RGBA,
1300 Some(&WhiteFlusher),
1301 None,
1302 None,
1303 segments,
1304 BLACK_ALPHA_0,
1305 &None,
1306 &|_| Style::default(),
1307 );
1308
1309 assert!(buffer.iter().all(|&color| color == 255u8));
1310 }
1311
1312 #[test]
1313 fn flush_background() {
1314 #[derive(Debug)]
1315 struct WhiteFlusher;
1316
1317 impl Flusher for WhiteFlusher {
1318 fn flush(&self, slice: &mut [u8]) {
1319 for color in slice {
1320 *color = 255u8;
1321 }
1322 }
1323 }
1324
1325 let mut buffer = vec![0u8; TILE_WIDTH * TILE_HEIGHT * 4];
1326 let mut buffer_layout = LinearLayout::new(TILE_WIDTH, TILE_WIDTH * 4, TILE_HEIGHT);
1327
1328 for_each_row(
1329 &mut buffer_layout,
1330 &mut buffer,
1331 RGBA,
1332 Some(&WhiteFlusher),
1333 None,
1334 None,
1335 &[],
1336 BLACK_ALPHA_0,
1337 &None,
1338 &|_| Style::default(),
1339 );
1340
1341 assert!(buffer.iter().all(|&color| color == 255u8));
1342 }
1343
1344 #[test]
1345 fn skip_opaque_tiles() {
1346 let mut buffer = vec![0u8; TILE_WIDTH * TILE_HEIGHT * 3 * 4];
1347
1348 let mut buffer_layout = LinearLayout::new(TILE_WIDTH * 3, TILE_WIDTH * 3 * 4, TILE_HEIGHT);
1349
1350 let mut segments = vec![];
1351 for y in 0..TILE_HEIGHT {
1352 segments.push(PixelSegment::new(
1353 2,
1354 -1,
1355 0,
1356 TILE_WIDTH as u8 - 1,
1357 y as u8,
1358 0,
1359 PIXEL_WIDTH as i8,
1360 ));
1361 }
1362
1363 segments.push(PixelSegment::new(0, -1, 0, TILE_WIDTH as u8 - 1, 0, 0, PIXEL_WIDTH as i8));
1364 segments.push(PixelSegment::new(1, 0, 0, 0, 1, 0, PIXEL_WIDTH as i8));
1365
1366 for y in 0..TILE_HEIGHT {
1367 segments.push(PixelSegment::new(
1368 2,
1369 1,
1370 0,
1371 TILE_WIDTH as u8 - 1,
1372 y as u8,
1373 0,
1374 -(PIXEL_WIDTH as i8),
1375 ));
1376 }
1377
1378 segments.sort();
1379
1380 let mut styles = HashMap::new();
1381
1382 styles.insert(0, Style { fill: Fill::Solid(BLUE), ..Default::default() });
1383 styles.insert(1, Style { fill: Fill::Solid(GREEN), ..Default::default() });
1384 styles.insert(2, Style { fill: Fill::Solid(RED), ..Default::default() });
1385
1386 for_each_row(
1387 &mut buffer_layout,
1388 &mut buffer,
1389 RGBA,
1390 None,
1391 None,
1392 None,
1393 &segments,
1394 BLACK,
1395 &None,
1396 &|layer| styles[&layer].clone(),
1397 );
1398
1399 let tiles = buffer_layout.slices(&mut buffer);
1400
1401 assert_eq!(
1402 tiles.iter().map(|slice| slice.to_vec()).collect::<Vec<_>>(),
1403 iter::repeat(vec![RED_RGBA; TILE_WIDTH].concat())
1405 .take(TILE_HEIGHT)
1406 .chain(iter::repeat(vec![RED_RGBA; TILE_WIDTH].concat()).take(TILE_HEIGHT))
1407 .chain(
1408 iter::once(vec![BLUE_RGBA; TILE_WIDTH].concat())
1410 .chain(iter::once(vec![GREEN_RGBA; TILE_WIDTH].concat()))
1411 .chain(
1413 iter::repeat(vec![BLACK_RGBA; TILE_WIDTH].concat())
1414 .take(TILE_HEIGHT - 2)
1415 )
1416 )
1417 .collect::<Vec<_>>()
1418 );
1419 }
1420
1421 #[test]
1422 fn crop() {
1423 let mut buffer = vec![0u8; TILE_WIDTH * TILE_HEIGHT * 9 * 4];
1424
1425 let mut buffer_layout =
1426 LinearLayout::new(TILE_WIDTH * 3, TILE_WIDTH * 3 * 4, TILE_HEIGHT * 3);
1427
1428 let mut segments = vec![];
1429 for j in 0..3 {
1430 for y in 0..TILE_HEIGHT {
1431 segments.push(PixelSegment::new(
1432 0,
1433 0,
1434 j,
1435 TILE_WIDTH as u8 - 1,
1436 y as u8,
1437 0,
1438 PIXEL_WIDTH as i8,
1439 ));
1440 }
1441 }
1442
1443 segments.sort();
1444
1445 let mut styles = HashMap::new();
1446
1447 styles.insert(0, Style { fill: Fill::Solid(BLUE), ..Default::default() });
1448
1449 for_each_row(
1450 &mut buffer_layout,
1451 &mut buffer,
1452 RGBA,
1453 None,
1454 None,
1455 None,
1456 &segments,
1457 RED,
1458 &Some(Rect::new(
1459 TILE_WIDTH..TILE_WIDTH * 2 + TILE_WIDTH / 2,
1460 TILE_HEIGHT..TILE_HEIGHT * 2,
1461 )),
1462 &|layer| styles[&layer].clone(),
1463 );
1464
1465 let tiles = buffer_layout.slices(&mut buffer);
1466
1467 assert_eq!(
1468 tiles.iter().map(|slice| slice.to_vec()).collect::<Vec<_>>(),
1469 iter::repeat(vec![0u8; TILE_WIDTH * 4])
1471 .take(TILE_HEIGHT * 3)
1472 .chain(iter::repeat(vec![0u8; TILE_WIDTH * 4]).take(TILE_HEIGHT))
1474 .chain(iter::repeat(vec![BLUE_RGBA; TILE_WIDTH].concat()).take(TILE_HEIGHT * 2))
1475 .chain(iter::repeat(vec![0u8; TILE_WIDTH * 4]).take(TILE_HEIGHT * 3))
1477 .collect::<Vec<_>>()
1478 );
1479 }
1480
1481 #[test]
1482 fn tiles_len() {
1483 let width = TILE_WIDTH * 4;
1484 let width_stride = TILE_WIDTH * 5 * 4;
1485 let height = TILE_HEIGHT * 8;
1486
1487 let buffer_layout = LinearLayout::new(width, width_stride, height);
1488
1489 assert_eq!(buffer_layout.width_in_tiles() * buffer_layout.height_in_tiles(), 32);
1490 }
1491
1492 #[test]
1493 fn cached_tiles() {
1494 const RED: [u8; 4] = [255, 0, 0, 255];
1495
1496 let cached_tile = CachedTile::default();
1497
1498 assert_eq!(cached_tile.solid_color(), None);
1500 assert_eq!(cached_tile.update_solid_color(Some(RED)), None);
1501 assert_eq!(cached_tile.solid_color(), Some(RED));
1502
1503 assert_eq!(cached_tile.layer_count(), None);
1505 assert_eq!(cached_tile.update_layer_count(Some(2)), None);
1506 assert_eq!(cached_tile.layer_count(), Some(2));
1507 }
1508}