1 | // Copyright 2016 Google Inc. |
2 | // Copyright 2020 Yevhenii Reizner |
3 | // |
4 | // Use of this source code is governed by a BSD-style license that can be |
5 | // found in the LICENSE file. |
6 | |
7 | /*! |
8 | A raster pipeline implementation. |
9 | |
10 | Despite having a lot of changes compared to `SkRasterPipeline`, |
11 | the core principles are the same: |
12 | |
13 | 1. A pipeline consists of stages. |
14 | 1. A pipeline has a global context shared by all stages. |
15 | Unlike Skia, were each stage has it's own, possibly shared, context. |
16 | 1. Each stage has a high precision implementation. See `highp.rs`. |
17 | 1. Some stages have a low precision implementation. See `lowp.rs`. |
18 | 1. Each stage calls the "next" stage after its done. |
19 | 1. During pipeline "compilation", if **all** stages have a lowp implementation, |
20 | the lowp pipeline will be used. Otherwise, the highp variant will be used. |
21 | 1. The pipeline "compilation" produces a list of function pointer. |
22 | The last pointer is a pointer to the "return" function, |
23 | which simply stops the execution of the pipeline. |
24 | |
25 | This implementation is a bit tricky, but it gives the maximum performance. |
26 | A simple and straightforward implementation using traits and loops, like: |
27 | |
28 | ```ignore |
29 | trait StageTrait { |
30 | fn apply(&mut self, pixels: &mut [Pixel]); |
31 | } |
32 | |
33 | let stages: Vec<&mut dyn StageTrait>; |
34 | for stage in stages { |
35 | stage.apply(pixels); |
36 | } |
37 | ``` |
38 | |
39 | will be at least 20-30% slower. Not really sure why. |
40 | |
41 | Also, since this module is all about performance, any kind of branching is |
42 | strictly forbidden. All stage functions must not use `if`, `match` or loops. |
43 | There are still some exceptions, which are basically an imperfect implementations |
44 | and should be optimized out in the future. |
45 | */ |
46 | |
47 | use alloc::vec::Vec; |
48 | |
49 | use arrayvec::ArrayVec; |
50 | |
51 | use tiny_skia_path::NormalizedF32; |
52 | |
53 | use crate::{Color, PremultipliedColor, PremultipliedColorU8, SpreadMode}; |
54 | use crate::{PixmapRef, Transform}; |
55 | |
56 | pub use blitter::RasterPipelineBlitter; |
57 | |
58 | use crate::geom::ScreenIntRect; |
59 | use crate::pixmap::SubPixmapMut; |
60 | use crate::wide::u32x8; |
61 | |
62 | mod blitter; |
63 | #[rustfmt::skip] mod highp; |
64 | #[rustfmt::skip] mod lowp; |
65 | |
66 | const MAX_STAGES: usize = 32; // More than enough. |
67 | |
68 | #[allow (dead_code)] |
69 | #[derive (Copy, Clone, Debug)] |
70 | pub enum Stage { |
71 | MoveSourceToDestination = 0, |
72 | MoveDestinationToSource, |
73 | Clamp0, |
74 | ClampA, |
75 | Premultiply, |
76 | UniformColor, |
77 | SeedShader, |
78 | LoadDestination, |
79 | Store, |
80 | LoadDestinationU8, |
81 | StoreU8, |
82 | Gather, |
83 | LoadMaskU8, |
84 | MaskU8, |
85 | ScaleU8, |
86 | LerpU8, |
87 | Scale1Float, |
88 | Lerp1Float, |
89 | DestinationAtop, |
90 | DestinationIn, |
91 | DestinationOut, |
92 | DestinationOver, |
93 | SourceAtop, |
94 | SourceIn, |
95 | SourceOut, |
96 | SourceOver, |
97 | Clear, |
98 | Modulate, |
99 | Multiply, |
100 | Plus, |
101 | Screen, |
102 | Xor, |
103 | ColorBurn, |
104 | ColorDodge, |
105 | Darken, |
106 | Difference, |
107 | Exclusion, |
108 | HardLight, |
109 | Lighten, |
110 | Overlay, |
111 | SoftLight, |
112 | Hue, |
113 | Saturation, |
114 | Color, |
115 | Luminosity, |
116 | SourceOverRgba, |
117 | Transform, |
118 | Reflect, |
119 | Repeat, |
120 | Bilinear, |
121 | Bicubic, |
122 | PadX1, |
123 | ReflectX1, |
124 | RepeatX1, |
125 | Gradient, |
126 | EvenlySpaced2StopGradient, |
127 | XYToRadius, |
128 | XYTo2PtConicalFocalOnCircle, |
129 | XYTo2PtConicalWellBehaved, |
130 | XYTo2PtConicalGreater, |
131 | Mask2PtConicalDegenerates, |
132 | ApplyVectorMask, |
133 | } |
134 | |
135 | pub const STAGES_COUNT: usize = Stage::ApplyVectorMask as usize + 1; |
136 | |
137 | impl<'a> PixmapRef<'a> { |
138 | #[inline (always)] |
139 | pub(crate) fn gather(&self, index: u32x8) -> [PremultipliedColorU8; highp::STAGE_WIDTH] { |
140 | let index: [u32; 8] = bytemuck::cast(index); |
141 | let pixels: &[PremultipliedColorU8] = self.pixels(); |
142 | [ |
143 | pixels[index[0] as usize], |
144 | pixels[index[1] as usize], |
145 | pixels[index[2] as usize], |
146 | pixels[index[3] as usize], |
147 | pixels[index[4] as usize], |
148 | pixels[index[5] as usize], |
149 | pixels[index[6] as usize], |
150 | pixels[index[7] as usize], |
151 | ] |
152 | } |
153 | } |
154 | |
155 | impl<'a> SubPixmapMut<'a> { |
156 | #[inline (always)] |
157 | pub(crate) fn offset(&self, dx: usize, dy: usize) -> usize { |
158 | self.real_width * dy + dx |
159 | } |
160 | |
161 | #[inline (always)] |
162 | pub(crate) fn slice_at_xy(&mut self, dx: usize, dy: usize) -> &mut [PremultipliedColorU8] { |
163 | let offset = self.offset(dx, dy); |
164 | &mut self.pixels_mut()[offset..] |
165 | } |
166 | |
167 | #[inline (always)] |
168 | pub(crate) fn slice_mask_at_xy(&mut self, dx: usize, dy: usize) -> &mut [u8] { |
169 | let offset = self.offset(dx, dy); |
170 | &mut self.data[offset..] |
171 | } |
172 | |
173 | #[inline (always)] |
174 | pub(crate) fn slice4_at_xy( |
175 | &mut self, |
176 | dx: usize, |
177 | dy: usize, |
178 | ) -> &mut [PremultipliedColorU8; highp::STAGE_WIDTH] { |
179 | arrayref::array_mut_ref!(self.pixels_mut(), self.offset(dx, dy), highp::STAGE_WIDTH) |
180 | } |
181 | |
182 | #[inline (always)] |
183 | pub(crate) fn slice16_at_xy( |
184 | &mut self, |
185 | dx: usize, |
186 | dy: usize, |
187 | ) -> &mut [PremultipliedColorU8; lowp::STAGE_WIDTH] { |
188 | arrayref::array_mut_ref!(self.pixels_mut(), self.offset(dx, dy), lowp::STAGE_WIDTH) |
189 | } |
190 | |
191 | #[inline (always)] |
192 | pub(crate) fn slice16_mask_at_xy( |
193 | &mut self, |
194 | dx: usize, |
195 | dy: usize, |
196 | ) -> &mut [u8; lowp::STAGE_WIDTH] { |
197 | arrayref::array_mut_ref!(self.data, self.offset(dx, dy), lowp::STAGE_WIDTH) |
198 | } |
199 | } |
200 | |
201 | #[derive (Default, Debug)] |
202 | pub struct AAMaskCtx { |
203 | pub pixels: [u8; 2], |
204 | pub stride: u32, // can be zero |
205 | pub shift: usize, // mask offset/position in pixmap coordinates |
206 | } |
207 | |
208 | impl AAMaskCtx { |
209 | #[inline (always)] |
210 | pub fn copy_at_xy(&self, dx: usize, dy: usize, tail: usize) -> [u8; 2] { |
211 | let offset: usize = (self.stride as usize * dy + dx) - self.shift; |
212 | // We have only 3 variants, so unroll them. |
213 | match (offset, tail) { |
214 | (0, 1) => [self.pixels[0], 0], |
215 | (0, 2) => [self.pixels[0], self.pixels[1]], |
216 | (1, 1) => [self.pixels[1], 0], |
217 | _ => [0, 0], // unreachable |
218 | } |
219 | } |
220 | } |
221 | |
222 | #[derive (Copy, Clone, Debug, Default)] |
223 | pub struct MaskCtx<'a> { |
224 | pub data: &'a [u8], |
225 | pub real_width: u32, |
226 | } |
227 | |
228 | impl MaskCtx<'_> { |
229 | #[inline (always)] |
230 | fn offset(&self, dx: usize, dy: usize) -> usize { |
231 | self.real_width as usize * dy + dx |
232 | } |
233 | } |
234 | |
235 | #[derive (Default)] |
236 | pub struct Context { |
237 | pub current_coverage: f32, |
238 | pub sampler: SamplerCtx, |
239 | pub uniform_color: UniformColorCtx, |
240 | pub evenly_spaced_2_stop_gradient: EvenlySpaced2StopGradientCtx, |
241 | pub gradient: GradientCtx, |
242 | pub two_point_conical_gradient: TwoPointConicalGradientCtx, |
243 | pub limit_x: TileCtx, |
244 | pub limit_y: TileCtx, |
245 | pub transform: Transform, |
246 | } |
247 | |
248 | #[derive (Copy, Clone, Default, Debug)] |
249 | pub struct SamplerCtx { |
250 | pub spread_mode: SpreadMode, |
251 | pub inv_width: f32, |
252 | pub inv_height: f32, |
253 | } |
254 | |
255 | #[derive (Copy, Clone, Default, Debug)] |
256 | pub struct UniformColorCtx { |
257 | pub r: f32, |
258 | pub g: f32, |
259 | pub b: f32, |
260 | pub a: f32, |
261 | pub rgba: [u16; 4], // [0,255] in a 16-bit lane. |
262 | } |
263 | |
264 | // A gradient color is an unpremultiplied RGBA not in a 0..1 range. |
265 | // It basically can have any float value. |
266 | #[derive (Copy, Clone, Default, Debug)] |
267 | pub struct GradientColor { |
268 | pub r: f32, |
269 | pub g: f32, |
270 | pub b: f32, |
271 | pub a: f32, |
272 | } |
273 | |
274 | impl GradientColor { |
275 | pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self { |
276 | GradientColor { r, g, b, a } |
277 | } |
278 | } |
279 | |
280 | impl From<Color> for GradientColor { |
281 | fn from(c: Color) -> Self { |
282 | GradientColor { |
283 | r: c.red(), |
284 | g: c.green(), |
285 | b: c.blue(), |
286 | a: c.alpha(), |
287 | } |
288 | } |
289 | } |
290 | |
291 | #[derive (Copy, Clone, Default, Debug)] |
292 | pub struct EvenlySpaced2StopGradientCtx { |
293 | pub factor: GradientColor, |
294 | pub bias: GradientColor, |
295 | } |
296 | |
297 | #[derive (Clone, Default, Debug)] |
298 | pub struct GradientCtx { |
299 | /// This value stores the actual colors count. |
300 | /// `factors` and `biases` must store at least 16 values, |
301 | /// since this is the length of a lowp pipeline stage. |
302 | /// So any any value past `len` is just zeros. |
303 | pub len: usize, |
304 | pub factors: Vec<GradientColor>, |
305 | pub biases: Vec<GradientColor>, |
306 | pub t_values: Vec<NormalizedF32>, |
307 | } |
308 | |
309 | impl GradientCtx { |
310 | pub fn push_const_color(&mut self, color: GradientColor) { |
311 | self.factors.push(GradientColor::new(r:0.0, g:0.0, b:0.0, a:0.0)); |
312 | self.biases.push(color); |
313 | } |
314 | } |
315 | |
316 | #[derive (Copy, Clone, Default, Debug)] |
317 | pub struct TwoPointConicalGradientCtx { |
318 | // This context is used only in highp, where we use Tx4. |
319 | pub mask: u32x8, |
320 | pub p0: f32, |
321 | } |
322 | |
323 | #[derive (Copy, Clone, Default, Debug)] |
324 | pub struct TileCtx { |
325 | pub scale: f32, |
326 | pub inv_scale: f32, // cache of 1/scale |
327 | } |
328 | |
329 | pub struct RasterPipelineBuilder { |
330 | stages: ArrayVec<Stage, MAX_STAGES>, |
331 | force_hq_pipeline: bool, |
332 | pub ctx: Context, |
333 | } |
334 | |
335 | impl RasterPipelineBuilder { |
336 | pub fn new() -> Self { |
337 | RasterPipelineBuilder { |
338 | stages: ArrayVec::new(), |
339 | force_hq_pipeline: false, |
340 | ctx: Context::default(), |
341 | } |
342 | } |
343 | |
344 | pub fn set_force_hq_pipeline(&mut self, hq: bool) { |
345 | self.force_hq_pipeline = hq; |
346 | } |
347 | |
348 | pub fn push(&mut self, stage: Stage) { |
349 | self.stages.push(stage); |
350 | } |
351 | |
352 | pub fn push_transform(&mut self, ts: Transform) { |
353 | if ts.is_finite() && !ts.is_identity() { |
354 | self.stages.push(Stage::Transform); |
355 | self.ctx.transform = ts; |
356 | } |
357 | } |
358 | |
359 | pub fn push_uniform_color(&mut self, c: PremultipliedColor) { |
360 | let r = c.red(); |
361 | let g = c.green(); |
362 | let b = c.blue(); |
363 | let a = c.alpha(); |
364 | let rgba = [ |
365 | (r * 255.0 + 0.5) as u16, |
366 | (g * 255.0 + 0.5) as u16, |
367 | (b * 255.0 + 0.5) as u16, |
368 | (a * 255.0 + 0.5) as u16, |
369 | ]; |
370 | |
371 | let ctx = UniformColorCtx { r, g, b, a, rgba }; |
372 | |
373 | self.stages.push(Stage::UniformColor); |
374 | self.ctx.uniform_color = ctx; |
375 | } |
376 | |
377 | pub fn compile(self) -> RasterPipeline { |
378 | if self.stages.is_empty() { |
379 | return RasterPipeline { |
380 | kind: RasterPipelineKind::High { |
381 | functions: ArrayVec::new(), |
382 | tail_functions: ArrayVec::new(), |
383 | }, |
384 | ctx: Context::default(), |
385 | }; |
386 | } |
387 | |
388 | let is_lowp_compatible = self |
389 | .stages |
390 | .iter() |
391 | .all(|stage| !lowp::fn_ptr_eq(lowp::STAGES[*stage as usize], lowp::null_fn)); |
392 | |
393 | if self.force_hq_pipeline || !is_lowp_compatible { |
394 | let mut functions: ArrayVec<_, MAX_STAGES> = self |
395 | .stages |
396 | .iter() |
397 | .map(|stage| highp::STAGES[*stage as usize] as highp::StageFn) |
398 | .collect(); |
399 | functions.push(highp::just_return as highp::StageFn); |
400 | |
401 | // I wasn't able to reproduce Skia's load_8888_/store_8888_ performance. |
402 | // Skia uses fallthrough switch, which is probably the reason. |
403 | // In Rust, any branching in load/store code drastically affects the performance. |
404 | // So instead, we're using two "programs": one for "full stages" and one for "tail stages". |
405 | // While the only difference is the load/store methods. |
406 | let mut tail_functions = functions.clone(); |
407 | for fun in &mut tail_functions { |
408 | if highp::fn_ptr(*fun) == highp::fn_ptr(highp::load_dst) { |
409 | *fun = highp::load_dst_tail as highp::StageFn; |
410 | } else if highp::fn_ptr(*fun) == highp::fn_ptr(highp::store) { |
411 | *fun = highp::store_tail as highp::StageFn; |
412 | } else if highp::fn_ptr(*fun) == highp::fn_ptr(highp::load_dst_u8) { |
413 | *fun = highp::load_dst_u8_tail as highp::StageFn; |
414 | } else if highp::fn_ptr(*fun) == highp::fn_ptr(highp::store_u8) { |
415 | *fun = highp::store_u8_tail as highp::StageFn; |
416 | } else if highp::fn_ptr(*fun) == highp::fn_ptr(highp::source_over_rgba) { |
417 | // SourceOverRgba calls load/store manually, without the pipeline, |
418 | // therefore we have to switch it too. |
419 | *fun = highp::source_over_rgba_tail as highp::StageFn; |
420 | } |
421 | } |
422 | |
423 | RasterPipeline { |
424 | kind: RasterPipelineKind::High { |
425 | functions, |
426 | tail_functions, |
427 | }, |
428 | ctx: self.ctx, |
429 | } |
430 | } else { |
431 | let mut functions: ArrayVec<_, MAX_STAGES> = self |
432 | .stages |
433 | .iter() |
434 | .map(|stage| lowp::STAGES[*stage as usize] as lowp::StageFn) |
435 | .collect(); |
436 | functions.push(lowp::just_return as lowp::StageFn); |
437 | |
438 | // See above. |
439 | let mut tail_functions = functions.clone(); |
440 | for fun in &mut tail_functions { |
441 | if lowp::fn_ptr(*fun) == lowp::fn_ptr(lowp::load_dst) { |
442 | *fun = lowp::load_dst_tail as lowp::StageFn; |
443 | } else if lowp::fn_ptr(*fun) == lowp::fn_ptr(lowp::store) { |
444 | *fun = lowp::store_tail as lowp::StageFn; |
445 | } else if lowp::fn_ptr(*fun) == lowp::fn_ptr(lowp::load_dst_u8) { |
446 | *fun = lowp::load_dst_u8_tail as lowp::StageFn; |
447 | } else if lowp::fn_ptr(*fun) == lowp::fn_ptr(lowp::store_u8) { |
448 | *fun = lowp::store_u8_tail as lowp::StageFn; |
449 | } else if lowp::fn_ptr(*fun) == lowp::fn_ptr(lowp::source_over_rgba) { |
450 | // SourceOverRgba calls load/store manually, without the pipeline, |
451 | // therefore we have to switch it too. |
452 | *fun = lowp::source_over_rgba_tail as lowp::StageFn; |
453 | } |
454 | } |
455 | |
456 | RasterPipeline { |
457 | kind: RasterPipelineKind::Low { |
458 | functions, |
459 | tail_functions, |
460 | }, |
461 | ctx: self.ctx, |
462 | } |
463 | } |
464 | } |
465 | } |
466 | |
467 | pub enum RasterPipelineKind { |
468 | High { |
469 | functions: ArrayVec<highp::StageFn, MAX_STAGES>, |
470 | tail_functions: ArrayVec<highp::StageFn, MAX_STAGES>, |
471 | }, |
472 | Low { |
473 | functions: ArrayVec<lowp::StageFn, MAX_STAGES>, |
474 | tail_functions: ArrayVec<lowp::StageFn, MAX_STAGES>, |
475 | }, |
476 | } |
477 | |
478 | pub struct RasterPipeline { |
479 | kind: RasterPipelineKind, |
480 | pub ctx: Context, |
481 | } |
482 | |
483 | impl RasterPipeline { |
484 | pub fn run( |
485 | &mut self, |
486 | rect: &ScreenIntRect, |
487 | aa_mask_ctx: AAMaskCtx, |
488 | mask_ctx: MaskCtx, |
489 | pixmap_src: PixmapRef, |
490 | pixmap_dst: &mut SubPixmapMut, |
491 | ) { |
492 | match self.kind { |
493 | RasterPipelineKind::High { |
494 | ref functions, |
495 | ref tail_functions, |
496 | } => { |
497 | highp::start( |
498 | functions.as_slice(), |
499 | tail_functions.as_slice(), |
500 | rect, |
501 | aa_mask_ctx, |
502 | mask_ctx, |
503 | &mut self.ctx, |
504 | pixmap_src, |
505 | pixmap_dst, |
506 | ); |
507 | } |
508 | RasterPipelineKind::Low { |
509 | ref functions, |
510 | ref tail_functions, |
511 | } => { |
512 | lowp::start( |
513 | functions.as_slice(), |
514 | tail_functions.as_slice(), |
515 | rect, |
516 | aa_mask_ctx, |
517 | mask_ctx, |
518 | &mut self.ctx, |
519 | // lowp doesn't support pattern, so no `pixmap_src` for it. |
520 | pixmap_dst, |
521 | ); |
522 | } |
523 | } |
524 | } |
525 | } |
526 | |
527 | #[rustfmt::skip] |
528 | #[cfg (test)] |
529 | mod blend_tests { |
530 | // Test blending modes. |
531 | // |
532 | // Skia has two kinds of a raster pipeline: high and low precision. |
533 | // "High" uses f32 and "low" uses u16. |
534 | // And for basic operations we don't need f32 and u16 simply faster. |
535 | // But those modes are not identical. They can produce slightly different results |
536 | // due rounding. |
537 | |
538 | use super::*; |
539 | use crate::{BlendMode, Color, Pixmap, PremultipliedColorU8}; |
540 | use crate::geom::IntSizeExt; |
541 | |
542 | macro_rules! test_blend { |
543 | ($name:ident, $mode:expr, $is_highp:expr, $r:expr, $g:expr, $b:expr, $a:expr) => { |
544 | #[test] |
545 | fn $name() { |
546 | let mut pixmap = Pixmap::new(1, 1).unwrap(); |
547 | pixmap.fill(Color::from_rgba8(50, 127, 150, 200)); |
548 | |
549 | let pixmap_src = PixmapRef::from_bytes(&[0, 0, 0, 0], 1, 1).unwrap(); |
550 | |
551 | let mut p = RasterPipelineBuilder::new(); |
552 | p.set_force_hq_pipeline($is_highp); |
553 | p.push_uniform_color(Color::from_rgba8(220, 140, 75, 180).premultiply()); |
554 | p.push(Stage::LoadDestination); |
555 | p.push($mode.to_stage().unwrap()); |
556 | p.push(Stage::Store); |
557 | let mut p = p.compile(); |
558 | let rect = pixmap.size().to_screen_int_rect(0, 0); |
559 | p.run(&rect, AAMaskCtx::default(), MaskCtx::default(), pixmap_src, |
560 | &mut pixmap.as_mut().as_subpixmap()); |
561 | |
562 | assert_eq!( |
563 | pixmap.as_ref().pixel(0, 0).unwrap(), |
564 | PremultipliedColorU8::from_rgba($r, $g, $b, $a).unwrap() |
565 | ); |
566 | } |
567 | }; |
568 | } |
569 | |
570 | macro_rules! test_blend_lowp { |
571 | ($name:ident, $mode:expr, $r:expr, $g:expr, $b:expr, $a:expr) => ( |
572 | test_blend!{$name, $mode, false, $r, $g, $b, $a} |
573 | ) |
574 | } |
575 | |
576 | macro_rules! test_blend_highp { |
577 | ($name:ident, $mode:expr, $r:expr, $g:expr, $b:expr, $a:expr) => ( |
578 | test_blend!{$name, $mode, true, $r, $g, $b, $a} |
579 | ) |
580 | } |
581 | |
582 | test_blend_lowp!(clear_lowp, BlendMode::Clear, 0, 0, 0, 0); |
583 | // Source is a no-op |
584 | test_blend_lowp!(destination_lowp, BlendMode::Destination, 39, 100, 118, 200); |
585 | test_blend_lowp!(source_over_lowp, BlendMode::SourceOver, 167, 129, 88, 239); |
586 | test_blend_lowp!(destination_over_lowp, BlendMode::DestinationOver, 73, 122, 130, 239); |
587 | test_blend_lowp!(source_in_lowp, BlendMode::SourceIn, 122, 78, 42, 141); |
588 | test_blend_lowp!(destination_in_lowp, BlendMode::DestinationIn, 28, 71, 83, 141); |
589 | test_blend_lowp!(source_out_lowp, BlendMode::SourceOut, 34, 22, 12, 39); |
590 | test_blend_lowp!(destination_out_lowp, BlendMode::DestinationOut, 12, 30, 35, 59); |
591 | test_blend_lowp!(source_atop_lowp, BlendMode::SourceAtop, 133, 107, 76, 200); |
592 | test_blend_lowp!(destination_atop_lowp, BlendMode::DestinationAtop, 61, 92, 95, 180); |
593 | test_blend_lowp!(xor_lowp, BlendMode::Xor, 45, 51, 46, 98); |
594 | test_blend_lowp!(plus_lowp, BlendMode::Plus, 194, 199, 171, 255); |
595 | test_blend_lowp!(modulate_lowp, BlendMode::Modulate, 24, 39, 25, 141); |
596 | test_blend_lowp!(screen_lowp, BlendMode::Screen, 170, 160, 146, 239); |
597 | test_blend_lowp!(overlay_lowp, BlendMode::Overlay, 92, 128, 106, 239); |
598 | test_blend_lowp!(darken_lowp, BlendMode::Darken, 72, 121, 88, 239); |
599 | test_blend_lowp!(lighten_lowp, BlendMode::Lighten, 166, 128, 129, 239); |
600 | // ColorDodge in not available for lowp. |
601 | // ColorBurn in not available for lowp. |
602 | test_blend_lowp!(hard_light_lowp, BlendMode::HardLight, 154, 128, 95, 239); |
603 | // SoftLight in not available for lowp. |
604 | test_blend_lowp!(difference_lowp, BlendMode::Difference, 138, 57, 87, 239); |
605 | test_blend_lowp!(exclusion_lowp, BlendMode::Exclusion, 146, 121, 121, 239); |
606 | test_blend_lowp!(multiply_lowp, BlendMode::Multiply, 69, 90, 71, 238); |
607 | // Hue in not available for lowp. |
608 | // Saturation in not available for lowp. |
609 | // Color in not available for lowp. |
610 | // Luminosity in not available for lowp. |
611 | |
612 | test_blend_highp!(clear_highp, BlendMode::Clear, 0, 0, 0, 0); |
613 | // Source is a no-op |
614 | test_blend_highp!(destination_highp, BlendMode::Destination, 39, 100, 118, 200); |
615 | test_blend_highp!(source_over_highp, BlendMode::SourceOver, 167, 128, 88, 239); |
616 | test_blend_highp!(destination_over_highp, BlendMode::DestinationOver, 72, 121, 129, 239); |
617 | test_blend_highp!(source_in_highp, BlendMode::SourceIn, 122, 78, 42, 141); |
618 | test_blend_highp!(destination_in_highp, BlendMode::DestinationIn, 28, 71, 83, 141); |
619 | test_blend_highp!(source_out_highp, BlendMode::SourceOut, 33, 21, 11, 39); |
620 | test_blend_highp!(destination_out_highp, BlendMode::DestinationOut, 11, 29, 35, 59); |
621 | test_blend_highp!(source_atop_highp, BlendMode::SourceAtop, 133, 107, 76, 200); |
622 | test_blend_highp!(destination_atop_highp, BlendMode::DestinationAtop, 61, 92, 95, 180); |
623 | test_blend_highp!(xor_highp, BlendMode::Xor, 45, 51, 46, 98); |
624 | test_blend_highp!(plus_highp, BlendMode::Plus, 194, 199, 171, 255); |
625 | test_blend_highp!(modulate_highp, BlendMode::Modulate, 24, 39, 24, 141); |
626 | test_blend_highp!(screen_highp, BlendMode::Screen, 171, 160, 146, 239); |
627 | test_blend_highp!(overlay_highp, BlendMode::Overlay, 92, 128, 106, 239); |
628 | test_blend_highp!(darken_highp, BlendMode::Darken, 72, 121, 88, 239); |
629 | test_blend_highp!(lighten_highp, BlendMode::Lighten, 167, 128, 129, 239); |
630 | test_blend_highp!(color_dodge_highp, BlendMode::ColorDodge, 186, 192, 164, 239); |
631 | test_blend_highp!(color_burn_highp, BlendMode::ColorBurn, 54, 63, 46, 239); |
632 | test_blend_highp!(hard_light_highp, BlendMode::HardLight, 155, 128, 95, 239); |
633 | test_blend_highp!(soft_light_highp, BlendMode::SoftLight, 98, 124, 115, 239); |
634 | test_blend_highp!(difference_highp, BlendMode::Difference, 139, 58, 88, 239); |
635 | test_blend_highp!(exclusion_highp, BlendMode::Exclusion, 147, 121, 122, 239); |
636 | test_blend_highp!(multiply_highp, BlendMode::Multiply, 69, 89, 71, 239); |
637 | test_blend_highp!(hue_highp, BlendMode::Hue, 128, 103, 74, 239); |
638 | test_blend_highp!(saturation_highp, BlendMode::Saturation, 59, 126, 140, 239); |
639 | test_blend_highp!(color_highp, BlendMode::Color, 139, 100, 60, 239); |
640 | test_blend_highp!(luminosity_highp, BlendMode::Luminosity, 100, 149, 157, 239); |
641 | } |
642 | |