surpass/painter/
mod.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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
36// From Hacker's Delight, p. 378-380. 2 ^ 23 as f32.
37const 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// From Hacker's Delight, p. 378-380.
120
121#[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    // Clips the subsequent layer with this one.
170    // From this order up to to order + n included are affected, if
171    // their `is_clipped` property is `true`.
172    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    // Proportion of the pixel area covered for each pixel row of a tile row.
195    // 0 is none, 16 is full coverage. Value above 16 happens when paths self-overlap.
196    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        // Map layer id to cover carry information.
482        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            // Accumulate cover for clipped tiles.
488            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    // Bitfield used to store the existence of `layer_count` and `solid_color` values
607    tags: Cell<u8>,
608    // (0b0x)
609    layer_count: Cell<[u8; 3]>,
610    // (0bx0)
611    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    // Skip content with negative y coordinates.
706    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            // Small values will still result in > 0 in sRGB.
1259            0.001 * 0.5,
1260            // Expected to be < 128.
1261            0.2 * 0.5,
1262            // Expected to be > 128.
1263            0.5 * 0.5,
1264            // Should convert linearly.
1265            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            // First two tiles need to be completely red.
1404            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                    // The last tile contains one blue and one green line.
1409                    iter::once(vec![BLUE_RGBA; TILE_WIDTH].concat())
1410                        .chain(iter::once(vec![GREEN_RGBA; TILE_WIDTH].concat()))
1411                        // Followed by black lines (clear color).
1412                        .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            // First row of tiles needs to be completely black.
1470            iter::repeat(vec![0u8; TILE_WIDTH * 4])
1471                .take(TILE_HEIGHT * 3)
1472                // Second row begins with a black tile.
1473                .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                // Third row of tiles needs to be completely black as well.
1476                .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        // Solid color
1499        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        // Layer count
1504        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}