| 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 | |