1use crate::basics::CoverType;
9use crate::color::Rgba8;
10use crate::pixfmt_rgba::PixelFormat;
11use crate::rendering_buffer::RowAccessor;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22#[repr(u8)]
23pub enum CompOp {
24 Clear = 0,
25 Src = 1,
26 Dst = 2,
27 SrcOver = 3,
28 DstOver = 4,
29 SrcIn = 5,
30 DstIn = 6,
31 SrcOut = 7,
32 DstOut = 8,
33 SrcAtop = 9,
34 DstAtop = 10,
35 Xor = 11,
36 Plus = 12,
37 Minus = 13,
38 Multiply = 14,
39 Screen = 15,
40 Overlay = 16,
41 Darken = 17,
42 Lighten = 18,
43 ColorDodge = 19,
44 ColorBurn = 20,
45 HardLight = 21,
46 SoftLight = 22,
47 Difference = 23,
48 Exclusion = 24,
49}
50
51impl Default for CompOp {
52 fn default() -> Self {
53 CompOp::SrcOver
54 }
55}
56
57#[derive(Debug, Clone, Copy)]
63struct PremulRgba {
64 r: f64,
65 g: f64,
66 b: f64,
67 a: f64,
68}
69
70impl PremulRgba {
71 #[inline(always)]
79 fn get(r: u8, g: u8, b: u8, a: u8, cover: u8) -> Self {
80 if cover == 0 {
81 return Self {
82 r: 0.0,
83 g: 0.0,
84 b: 0.0,
85 a: 0.0,
86 };
87 }
88 let mut c = Self {
89 r: Rgba8::to_double(r),
90 g: Rgba8::to_double(g),
91 b: Rgba8::to_double(b),
92 a: Rgba8::to_double(a),
93 };
94 if cover < 255 {
95 let x = cover as f64 / 255.0;
96 c.r *= x;
97 c.g *= x;
98 c.b *= x;
99 c.a *= x;
100 }
101 c
102 }
103
104 #[inline(always)]
110 fn get_pix(p: &[u8; 4], cover: u8) -> Self {
111 Self::get(p[0], p[1], p[2], p[3], cover)
112 }
113
114 #[inline(always)]
116 fn set(p: &mut [u8; 4], c: &PremulRgba) {
117 p[0] = Rgba8::from_double(c.r);
118 p[1] = Rgba8::from_double(c.g);
119 p[2] = Rgba8::from_double(c.b);
120 p[3] = Rgba8::from_double(c.a);
121 }
122
123 #[inline(always)]
125 fn set_rgba(p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8) {
126 p[0] = r;
127 p[1] = g;
128 p[2] = b;
129 p[3] = a;
130 }
131
132 #[inline]
139 fn clip(c: &mut PremulRgba) {
140 c.a = c.a.clamp(0.0, 1.0);
141 c.r = c.r.clamp(0.0, c.a);
142 c.g = c.g.clamp(0.0, c.a);
143 c.b = c.b.clamp(0.0, c.a);
144 }
145}
146
147#[inline]
156fn comp_op_blend(op: CompOp, p: &mut [u8; 4], sr: u8, sg: u8, sb: u8, sa: u8, cover: u8) {
157 let r = Rgba8::multiply(sr, sa);
159 let g = Rgba8::multiply(sg, sa);
160 let b = Rgba8::multiply(sb, sa);
161 let a = sa;
162
163 match op {
164 CompOp::Clear => blend_clear(p, cover),
165 CompOp::Src => blend_src(p, r, g, b, a, cover),
166 CompOp::Dst => {} CompOp::SrcOver => blend_src_over(p, r, g, b, a, cover),
168 CompOp::DstOver => blend_dst_over(p, r, g, b, a, cover),
169 CompOp::SrcIn => blend_src_in(p, r, g, b, a, cover),
170 CompOp::DstIn => blend_dst_in(p, a, cover),
171 CompOp::SrcOut => blend_src_out(p, r, g, b, a, cover),
172 CompOp::DstOut => blend_dst_out(p, a, cover),
173 CompOp::SrcAtop => blend_src_atop(p, r, g, b, a, cover),
174 CompOp::DstAtop => blend_dst_atop(p, r, g, b, a, cover),
175 CompOp::Xor => blend_xor(p, r, g, b, a, cover),
176 CompOp::Plus => blend_plus(p, r, g, b, a, cover),
177 CompOp::Minus => blend_minus(p, r, g, b, a, cover),
178 CompOp::Multiply => blend_multiply(p, r, g, b, a, cover),
179 CompOp::Screen => blend_screen(p, r, g, b, a, cover),
180 CompOp::Overlay => blend_overlay(p, r, g, b, a, cover),
181 CompOp::Darken => blend_darken(p, r, g, b, a, cover),
182 CompOp::Lighten => blend_lighten(p, r, g, b, a, cover),
183 CompOp::ColorDodge => blend_color_dodge(p, r, g, b, a, cover),
184 CompOp::ColorBurn => blend_color_burn(p, r, g, b, a, cover),
185 CompOp::HardLight => blend_hard_light(p, r, g, b, a, cover),
186 CompOp::SoftLight => blend_soft_light(p, r, g, b, a, cover),
187 CompOp::Difference => blend_difference(p, r, g, b, a, cover),
188 CompOp::Exclusion => blend_exclusion(p, r, g, b, a, cover),
189 }
190}
191
192#[inline(always)]
194fn blend_clear(p: &mut [u8; 4], cover: u8) {
195 if cover >= 255 {
196 p[0] = 0;
197 p[1] = 0;
198 p[2] = 0;
199 p[3] = 0;
200 } else if cover > 0 {
201 let d = PremulRgba::get_pix(p, 255 - cover);
202 PremulRgba::set(p, &d);
203 }
204}
205
206#[inline(always)]
208fn blend_src(p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8) {
209 if cover >= 255 {
210 PremulRgba::set_rgba(p, r, g, b, a);
211 } else {
212 let s = PremulRgba::get(r, g, b, a, cover);
213 let d = PremulRgba::get_pix(p, 255 - cover);
214 let out = PremulRgba {
215 r: d.r + s.r,
216 g: d.g + s.g,
217 b: d.b + s.b,
218 a: d.a + s.a,
219 };
220 PremulRgba::set(p, &out);
221 }
222}
223
224#[inline(always)]
236fn blend_src_over(p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8) {
237 let cr = Rgba8::mult_cover(r, cover);
238 let cg = Rgba8::mult_cover(g, cover);
239 let cb = Rgba8::mult_cover(b, cover);
240 let ca = Rgba8::mult_cover(a, cover);
241 let d = *p;
246 *p = [
247 Rgba8::prelerp(d[0], cr, ca),
248 Rgba8::prelerp(d[1], cg, ca),
249 Rgba8::prelerp(d[2], cb, ca),
250 Rgba8::prelerp(d[3], ca, ca),
251 ];
252}
253
254#[inline(always)]
256fn blend_dst_over(p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8) {
257 let s = PremulRgba::get(r, g, b, a, cover);
258 let mut d = PremulRgba::get_pix(p, 255);
259 let d1a = 1.0 - d.a;
260 d.r += s.r * d1a;
261 d.g += s.g * d1a;
262 d.b += s.b * d1a;
263 d.a += s.a * d1a;
264 PremulRgba::set(p, &d);
265}
266
267#[inline(always)]
269fn blend_src_in(p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8) {
270 let da = Rgba8::to_double(p[3]);
271 if da > 0.0 {
272 let s = PremulRgba::get(r, g, b, a, cover);
273 let mut d = PremulRgba::get_pix(p, 255 - cover);
274 d.r += s.r * da;
275 d.g += s.g * da;
276 d.b += s.b * da;
277 d.a += s.a * da;
278 PremulRgba::set(p, &d);
279 }
280}
281
282#[inline(always)]
284fn blend_dst_in(p: &mut [u8; 4], a: u8, cover: u8) {
285 let sa = Rgba8::to_double(a);
286 let mut d = PremulRgba::get_pix(p, 255 - cover);
287 let d2 = PremulRgba::get_pix(p, cover);
288 d.r += d2.r * sa;
289 d.g += d2.g * sa;
290 d.b += d2.b * sa;
291 d.a += d2.a * sa;
292 PremulRgba::set(p, &d);
293}
294
295#[inline(always)]
297fn blend_src_out(p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8) {
298 let s = PremulRgba::get(r, g, b, a, cover);
299 let mut d = PremulRgba::get_pix(p, 255 - cover);
300 let d1a = 1.0 - Rgba8::to_double(p[3]);
301 d.r += s.r * d1a;
302 d.g += s.g * d1a;
303 d.b += s.b * d1a;
304 d.a += s.a * d1a;
305 PremulRgba::set(p, &d);
306}
307
308#[inline(always)]
310fn blend_dst_out(p: &mut [u8; 4], a: u8, cover: u8) {
311 let mut d = PremulRgba::get_pix(p, 255 - cover);
312 let dc = PremulRgba::get_pix(p, cover);
313 let s1a = 1.0 - Rgba8::to_double(a);
314 d.r += dc.r * s1a;
315 d.g += dc.g * s1a;
316 d.b += dc.b * s1a;
317 d.a += dc.a * s1a;
318 PremulRgba::set(p, &d);
319}
320
321#[inline(always)]
323fn blend_src_atop(p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8) {
324 let s = PremulRgba::get(r, g, b, a, cover);
325 let mut d = PremulRgba::get_pix(p, 255);
326 let s1a = 1.0 - s.a;
327 d.r = s.r * d.a + d.r * s1a;
328 d.g = s.g * d.a + d.g * s1a;
329 d.b = s.b * d.a + d.g * s1a;
337 PremulRgba::set(p, &d);
339}
340
341#[inline(always)]
343fn blend_dst_atop(p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8) {
344 let sc = PremulRgba::get(r, g, b, a, cover);
345 let dc = PremulRgba::get_pix(p, cover);
346 let mut d = PremulRgba::get_pix(p, 255 - cover);
347 let sa = Rgba8::to_double(a);
348 let d1a = 1.0 - Rgba8::to_double(p[3]);
349 d.r += dc.r * sa + sc.r * d1a;
350 d.g += dc.g * sa + sc.g * d1a;
351 d.b += dc.b * sa + sc.b * d1a;
352 d.a += sc.a;
353 PremulRgba::set(p, &d);
354}
355
356#[inline(always)]
358fn blend_xor(p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8) {
359 let s = PremulRgba::get(r, g, b, a, cover);
360 let mut d = PremulRgba::get_pix(p, 255);
361 let s1a = 1.0 - s.a;
362 let d1a = 1.0 - Rgba8::to_double(p[3]);
363 d.r = s.r * d1a + d.r * s1a;
364 d.g = s.g * d1a + d.g * s1a;
365 d.b = s.b * d1a + d.b * s1a;
366 d.a = s.a + d.a - 2.0 * s.a * d.a;
367 PremulRgba::set(p, &d);
368}
369
370#[inline(always)]
372fn blend_plus(p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8) {
373 let s = PremulRgba::get(r, g, b, a, cover);
374 if s.a > 0.0 {
375 let mut d = PremulRgba::get_pix(p, 255);
376 d.a = (d.a + s.a).min(1.0);
377 d.r = (d.r + s.r).min(d.a);
378 d.g = (d.g + s.g).min(d.a);
379 d.b = (d.b + s.b).min(d.a);
380 PremulRgba::clip(&mut d);
381 PremulRgba::set(p, &d);
382 }
383}
384
385#[inline(always)]
387fn blend_minus(p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8) {
388 let s = PremulRgba::get(r, g, b, a, cover);
389 if s.a > 0.0 {
390 let mut d = PremulRgba::get_pix(p, 255);
391 d.a += s.a - s.a * d.a;
392 d.r = (d.r - s.r).max(0.0);
393 d.g = (d.g - s.g).max(0.0);
394 d.b = (d.b - s.b).max(0.0);
395 PremulRgba::clip(&mut d);
396 PremulRgba::set(p, &d);
397 }
398}
399
400#[inline(always)]
402fn blend_multiply(p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8) {
403 let s = PremulRgba::get(r, g, b, a, cover);
404 if s.a > 0.0 {
405 let mut d = PremulRgba::get_pix(p, 255);
406 let s1a = 1.0 - s.a;
407 let d1a = 1.0 - d.a;
408 d.r = s.r * d.r + s.r * d1a + d.r * s1a;
409 d.g = s.g * d.g + s.g * d1a + d.g * s1a;
410 d.b = s.b * d.b + s.b * d1a + d.b * s1a;
411 d.a += s.a - s.a * d.a;
412 PremulRgba::clip(&mut d);
413 PremulRgba::set(p, &d);
414 }
415}
416
417#[inline(always)]
419fn blend_screen(p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8) {
420 let s = PremulRgba::get(r, g, b, a, cover);
421 if s.a > 0.0 {
422 let mut d = PremulRgba::get_pix(p, 255);
423 d.r += s.r - s.r * d.r;
424 d.g += s.g - s.g * d.g;
425 d.b += s.b - s.b * d.b;
426 d.a += s.a - s.a * d.a;
427 PremulRgba::clip(&mut d);
428 PremulRgba::set(p, &d);
429 }
430}
431
432#[inline]
434fn overlay_calc(dca: f64, sca: f64, da: f64, sa: f64, sada: f64, d1a: f64, s1a: f64) -> f64 {
435 if 2.0 * dca <= da {
436 2.0 * sca * dca + sca * d1a + dca * s1a
437 } else {
438 sada - 2.0 * (da - dca) * (sa - sca) + sca * d1a + dca * s1a
439 }
440}
441
442#[inline(always)]
443fn blend_overlay(p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8) {
444 let s = PremulRgba::get(r, g, b, a, cover);
445 if s.a > 0.0 {
446 let mut d = PremulRgba::get_pix(p, 255);
447 let d1a = 1.0 - d.a;
448 let s1a = 1.0 - s.a;
449 let sada = s.a * d.a;
450 d.r = overlay_calc(d.r, s.r, d.a, s.a, sada, d1a, s1a);
451 d.g = overlay_calc(d.g, s.g, d.a, s.a, sada, d1a, s1a);
452 d.b = overlay_calc(d.b, s.b, d.a, s.a, sada, d1a, s1a);
453 d.a += s.a - s.a * d.a;
454 PremulRgba::clip(&mut d);
455 PremulRgba::set(p, &d);
456 }
457}
458
459#[inline(always)]
461fn blend_darken(p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8) {
462 let s = PremulRgba::get(r, g, b, a, cover);
463 if s.a > 0.0 {
464 let mut d = PremulRgba::get_pix(p, 255);
465 let d1a = 1.0 - d.a;
466 let s1a = 1.0 - s.a;
467 d.r = (s.r * d.a).min(d.r * s.a) + s.r * d1a + d.r * s1a;
468 d.g = (s.g * d.a).min(d.g * s.a) + s.g * d1a + d.g * s1a;
469 d.b = (s.b * d.a).min(d.b * s.a) + s.b * d1a + d.b * s1a;
470 d.a += s.a - s.a * d.a;
471 PremulRgba::clip(&mut d);
472 PremulRgba::set(p, &d);
473 }
474}
475
476#[inline(always)]
478fn blend_lighten(p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8) {
479 let s = PremulRgba::get(r, g, b, a, cover);
480 if s.a > 0.0 {
481 let mut d = PremulRgba::get_pix(p, 255);
482 let d1a = 1.0 - d.a;
483 let s1a = 1.0 - s.a;
484 d.r = (s.r * d.a).max(d.r * s.a) + s.r * d1a + d.r * s1a;
485 d.g = (s.g * d.a).max(d.g * s.a) + s.g * d1a + d.g * s1a;
486 d.b = (s.b * d.a).max(d.b * s.a) + s.b * d1a + d.b * s1a;
487 d.a += s.a - s.a * d.a;
488 PremulRgba::clip(&mut d);
489 PremulRgba::set(p, &d);
490 }
491}
492
493#[inline]
495fn color_dodge_calc(dca: f64, sca: f64, da: f64, sa: f64, sada: f64, d1a: f64, s1a: f64) -> f64 {
496 if sca < sa {
497 sada * (1.0f64).min((dca / da) * sa / (sa - sca)) + sca * d1a + dca * s1a
498 } else if dca > 0.0 {
499 sada + sca * d1a + dca * s1a
500 } else {
501 sca * d1a
502 }
503}
504
505#[inline(always)]
506fn blend_color_dodge(p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8) {
507 let s = PremulRgba::get(r, g, b, a, cover);
508 if s.a > 0.0 {
509 let mut d = PremulRgba::get_pix(p, 255);
510 if d.a > 0.0 {
511 let sada = s.a * d.a;
512 let s1a = 1.0 - s.a;
513 let d1a = 1.0 - d.a;
514 d.r = color_dodge_calc(d.r, s.r, d.a, s.a, sada, d1a, s1a);
515 d.g = color_dodge_calc(d.g, s.g, d.a, s.a, sada, d1a, s1a);
516 d.b = color_dodge_calc(d.b, s.b, d.a, s.a, sada, d1a, s1a);
517 d.a += s.a - s.a * d.a;
518 PremulRgba::clip(&mut d);
519 PremulRgba::set(p, &d);
520 } else {
521 PremulRgba::set(p, &s);
522 }
523 }
524}
525
526#[inline]
528fn color_burn_calc(dca: f64, sca: f64, da: f64, sa: f64, sada: f64, d1a: f64, s1a: f64) -> f64 {
529 if sca > 0.0 {
530 sada * (1.0 - (1.0f64).min((1.0 - dca / da) * sa / sca)) + sca * d1a + dca * s1a
531 } else if dca > da {
532 sada + dca * s1a
533 } else {
534 dca * s1a
535 }
536}
537
538#[inline(always)]
539fn blend_color_burn(p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8) {
540 let s = PremulRgba::get(r, g, b, a, cover);
541 if s.a > 0.0 {
542 let mut d = PremulRgba::get_pix(p, 255);
543 if d.a > 0.0 {
544 let sada = s.a * d.a;
545 let s1a = 1.0 - s.a;
546 let d1a = 1.0 - d.a;
547 d.r = color_burn_calc(d.r, s.r, d.a, s.a, sada, d1a, s1a);
548 d.g = color_burn_calc(d.g, s.g, d.a, s.a, sada, d1a, s1a);
549 d.b = color_burn_calc(d.b, s.b, d.a, s.a, sada, d1a, s1a);
550 d.a += s.a - sada;
551 PremulRgba::clip(&mut d);
552 PremulRgba::set(p, &d);
553 } else {
554 PremulRgba::set(p, &s);
555 }
556 }
557}
558
559#[inline]
561fn hard_light_calc(dca: f64, sca: f64, da: f64, sa: f64, sada: f64, d1a: f64, s1a: f64) -> f64 {
562 if 2.0 * sca < sa {
563 2.0 * sca * dca + sca * d1a + dca * s1a
564 } else {
565 sada - 2.0 * (da - dca) * (sa - sca) + sca * d1a + dca * s1a
566 }
567}
568
569#[inline(always)]
570fn blend_hard_light(p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8) {
571 let s = PremulRgba::get(r, g, b, a, cover);
572 if s.a > 0.0 {
573 let mut d = PremulRgba::get_pix(p, 255);
574 let d1a = 1.0 - d.a;
575 let s1a = 1.0 - s.a;
576 let sada = s.a * d.a;
577 d.r = hard_light_calc(d.r, s.r, d.a, s.a, sada, d1a, s1a);
578 d.g = hard_light_calc(d.g, s.g, d.a, s.a, sada, d1a, s1a);
579 d.b = hard_light_calc(d.b, s.b, d.a, s.a, sada, d1a, s1a);
580 d.a += s.a - sada;
581 PremulRgba::clip(&mut d);
582 PremulRgba::set(p, &d);
583 }
584}
585
586#[inline]
588fn soft_light_calc(dca: f64, sca: f64, da: f64, sa: f64, sada: f64, d1a: f64, s1a: f64) -> f64 {
589 let dcasa = dca * sa;
590 if 2.0 * sca <= sa {
591 dcasa - (sada - 2.0 * sca * da) * dcasa * (sada - dcasa) + sca * d1a + dca * s1a
592 } else if 4.0 * dca <= da {
593 dcasa
594 + (2.0 * sca * da - sada)
595 * ((((16.0 * dcasa - 12.0) * dcasa + 4.0) * dca * da) - dca * da)
596 + sca * d1a
597 + dca * s1a
598 } else {
599 dcasa + (2.0 * sca * da - sada) * (dcasa.sqrt() - dcasa) + sca * d1a + dca * s1a
600 }
601}
602
603#[inline(always)]
604fn blend_soft_light(p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8) {
605 let s = PremulRgba::get(r, g, b, a, cover);
606 if s.a > 0.0 {
607 let mut d = PremulRgba::get_pix(p, 255);
608 if d.a > 0.0 {
609 let sada = s.a * d.a;
610 let s1a = 1.0 - s.a;
611 let d1a = 1.0 - d.a;
612 d.r = soft_light_calc(d.r, s.r, d.a, s.a, sada, d1a, s1a);
613 d.g = soft_light_calc(d.g, s.g, d.a, s.a, sada, d1a, s1a);
614 d.b = soft_light_calc(d.b, s.b, d.a, s.a, sada, d1a, s1a);
615 d.a += s.a - sada;
616 PremulRgba::clip(&mut d);
617 PremulRgba::set(p, &d);
618 } else {
619 PremulRgba::set(p, &s);
620 }
621 }
622}
623
624#[inline(always)]
626fn blend_difference(p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8) {
627 let s = PremulRgba::get(r, g, b, a, cover);
628 if s.a > 0.0 {
629 let mut d = PremulRgba::get_pix(p, 255);
630 d.r += s.r - 2.0 * (s.r * d.a).min(d.r * s.a);
631 d.g += s.g - 2.0 * (s.g * d.a).min(d.g * s.a);
632 d.b += s.b - 2.0 * (s.b * d.a).min(d.b * s.a);
633 d.a += s.a - s.a * d.a;
634 PremulRgba::clip(&mut d);
635 PremulRgba::set(p, &d);
636 }
637}
638
639#[inline(always)]
641fn blend_exclusion(p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8) {
642 let s = PremulRgba::get(r, g, b, a, cover);
643 if s.a > 0.0 {
644 let mut d = PremulRgba::get_pix(p, 255);
645 let d1a = 1.0 - d.a;
646 let s1a = 1.0 - s.a;
647 d.r = (s.r * d.a + d.r * s.a - 2.0 * s.r * d.r) + s.r * d1a + d.r * s1a;
648 d.g = (s.g * d.a + d.g * s.a - 2.0 * s.g * d.g) + s.g * d1a + d.g * s1a;
649 d.b = (s.b * d.a + d.b * s.a - 2.0 * s.b * d.b) + s.b * d1a + d.b * s1a;
650 d.a += s.a - s.a * d.a;
651 PremulRgba::clip(&mut d);
652 PremulRgba::set(p, &d);
653 }
654}
655
656macro_rules! comp_op_span {
674 ($op:expr, |$blend:ident| $body:block) => {
675 match $op {
676 CompOp::Clear => {
677 let $blend = |p: &mut [u8; 4], _r: u8, _g: u8, _b: u8, _a: u8, cover: u8| blend_clear(p, cover);
678 $body
679 }
680 CompOp::Src => {
681 let $blend = |p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8| blend_src(p, r, g, b, a, cover);
682 $body
683 }
684 CompOp::Dst => {}
685 CompOp::SrcOver => {
686 let $blend = |p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8| blend_src_over(p, r, g, b, a, cover);
687 $body
688 }
689 CompOp::DstOver => {
690 let $blend = |p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8| blend_dst_over(p, r, g, b, a, cover);
691 $body
692 }
693 CompOp::SrcIn => {
694 let $blend = |p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8| blend_src_in(p, r, g, b, a, cover);
695 $body
696 }
697 CompOp::DstIn => {
698 let $blend = |p: &mut [u8; 4], _r: u8, _g: u8, _b: u8, a: u8, cover: u8| blend_dst_in(p, a, cover);
699 $body
700 }
701 CompOp::SrcOut => {
702 let $blend = |p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8| blend_src_out(p, r, g, b, a, cover);
703 $body
704 }
705 CompOp::DstOut => {
706 let $blend = |p: &mut [u8; 4], _r: u8, _g: u8, _b: u8, a: u8, cover: u8| blend_dst_out(p, a, cover);
707 $body
708 }
709 CompOp::SrcAtop => {
710 let $blend = |p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8| blend_src_atop(p, r, g, b, a, cover);
711 $body
712 }
713 CompOp::DstAtop => {
714 let $blend = |p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8| blend_dst_atop(p, r, g, b, a, cover);
715 $body
716 }
717 CompOp::Xor => {
718 let $blend = |p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8| blend_xor(p, r, g, b, a, cover);
719 $body
720 }
721 CompOp::Plus => {
722 let $blend = |p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8| blend_plus(p, r, g, b, a, cover);
723 $body
724 }
725 CompOp::Minus => {
726 let $blend = |p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8| blend_minus(p, r, g, b, a, cover);
727 $body
728 }
729 CompOp::Multiply => {
730 let $blend = |p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8| blend_multiply(p, r, g, b, a, cover);
731 $body
732 }
733 CompOp::Screen => {
734 let $blend = |p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8| blend_screen(p, r, g, b, a, cover);
735 $body
736 }
737 CompOp::Overlay => {
738 let $blend = |p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8| blend_overlay(p, r, g, b, a, cover);
739 $body
740 }
741 CompOp::Darken => {
742 let $blend = |p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8| blend_darken(p, r, g, b, a, cover);
743 $body
744 }
745 CompOp::Lighten => {
746 let $blend = |p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8| blend_lighten(p, r, g, b, a, cover);
747 $body
748 }
749 CompOp::ColorDodge => {
750 let $blend = |p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8| blend_color_dodge(p, r, g, b, a, cover);
751 $body
752 }
753 CompOp::ColorBurn => {
754 let $blend = |p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8| blend_color_burn(p, r, g, b, a, cover);
755 $body
756 }
757 CompOp::HardLight => {
758 let $blend = |p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8| blend_hard_light(p, r, g, b, a, cover);
759 $body
760 }
761 CompOp::SoftLight => {
762 let $blend = |p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8| blend_soft_light(p, r, g, b, a, cover);
763 $body
764 }
765 CompOp::Difference => {
766 let $blend = |p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8| blend_difference(p, r, g, b, a, cover);
767 $body
768 }
769 CompOp::Exclusion => {
770 let $blend = |p: &mut [u8; 4], r: u8, g: u8, b: u8, a: u8, cover: u8| blend_exclusion(p, r, g, b, a, cover);
771 $body
772 }
773 }
774 };
775}
776
777const BPP: usize = 4;
782
783pub struct PixfmtRgba32CompOp<'a> {
789 rbuf: &'a mut RowAccessor,
790 comp_op: CompOp,
791}
792
793impl<'a> PixfmtRgba32CompOp<'a> {
794 pub fn new(rbuf: &'a mut RowAccessor) -> Self {
795 Self {
796 rbuf,
797 comp_op: CompOp::SrcOver,
798 }
799 }
800
801 pub fn new_with_op(rbuf: &'a mut RowAccessor, op: CompOp) -> Self {
802 Self { rbuf, comp_op: op }
803 }
804
805 pub fn comp_op(&self) -> CompOp {
806 self.comp_op
807 }
808
809 pub fn set_comp_op(&mut self, op: CompOp) {
810 self.comp_op = op;
811 }
812
813 #[inline]
815 fn row(&self, y: i32) -> &[u8] {
816 unsafe {
817 let ptr = self.rbuf.row_ptr(y);
818 std::slice::from_raw_parts(ptr, self.rbuf.width() as usize * BPP)
819 }
820 }
821
822 #[inline]
824 fn row_mut(&mut self, y: i32) -> &mut [u8] {
825 unsafe {
826 let ptr = self.rbuf.row_ptr(y);
827 std::slice::from_raw_parts_mut(ptr, self.rbuf.width() as usize * BPP)
828 }
829 }
830
831 pub fn clear(&mut self, c: &Rgba8) {
833 let w = self.rbuf.width();
834 let h = self.rbuf.height();
835 for y in 0..h {
836 let row = self.row_mut(y as i32);
837 for x in 0..w as usize {
838 let off = x * BPP;
839 row[off] = c.r;
840 row[off + 1] = c.g;
841 row[off + 2] = c.b;
842 row[off + 3] = c.a;
843 }
844 }
845 }
846}
847
848impl<'a> PixelFormat for PixfmtRgba32CompOp<'a> {
849 type ColorType = Rgba8;
850
851 fn width(&self) -> u32 {
852 self.rbuf.width()
853 }
854
855 fn height(&self) -> u32 {
856 self.rbuf.height()
857 }
858
859 fn pixel(&self, x: i32, y: i32) -> Rgba8 {
860 let row = self.row(y);
861 let off = x as usize * BPP;
862 Rgba8::new(
863 row[off] as u32,
864 row[off + 1] as u32,
865 row[off + 2] as u32,
866 row[off + 3] as u32,
867 )
868 }
869
870 fn copy_pixel(&mut self, x: i32, y: i32, c: &Rgba8) {
871 let row = self.row_mut(y);
872 let off = x as usize * BPP;
873 row[off] = c.r;
874 row[off + 1] = c.g;
875 row[off + 2] = c.b;
876 row[off + 3] = c.a;
877 }
878
879 fn copy_hline(&mut self, x: i32, y: i32, len: u32, c: &Rgba8) {
880 let row = self.row_mut(y);
881 for i in 0..len as usize {
882 let off = (x as usize + i) * BPP;
883 row[off] = c.r;
884 row[off + 1] = c.g;
885 row[off + 2] = c.b;
886 row[off + 3] = c.a;
887 }
888 }
889
890 fn blend_pixel(&mut self, x: i32, y: i32, c: &Rgba8, cover: CoverType) {
891 let comp_op = self.comp_op;
892 let row = self.row_mut(y);
893 let off = x as usize * BPP;
894 let px: &mut [u8; 4] = (&mut row[off..off + BPP]).try_into().unwrap();
895 comp_op_blend(comp_op, px, c.r, c.g, c.b, c.a, cover);
896 }
897
898 fn blend_hline(&mut self, x: i32, y: i32, len: u32, c: &Rgba8, cover: CoverType) {
899 let comp_op = self.comp_op;
900 let row = self.row_mut(y);
901 let span = &mut row[x as usize * BPP..(x as usize + len as usize) * BPP];
904 comp_op_span!(comp_op, |blend| {
905 let r = Rgba8::multiply(c.r, c.a);
908 let g = Rgba8::multiply(c.g, c.a);
909 let b = Rgba8::multiply(c.b, c.a);
910 let a = c.a;
911 let (pixels, _rem) = span.as_chunks_mut::<BPP>();
914 for px in pixels {
915 blend(px, r, g, b, a, cover);
916 }
917 });
918 }
919
920 fn blend_solid_hspan(&mut self, x: i32, y: i32, len: u32, c: &Rgba8, covers: &[CoverType]) {
921 let comp_op = self.comp_op;
922 let row = self.row_mut(y);
923 let span = &mut row[x as usize * BPP..(x as usize + len as usize) * BPP];
924 comp_op_span!(comp_op, |blend| {
925 let r = Rgba8::multiply(c.r, c.a);
927 let g = Rgba8::multiply(c.g, c.a);
928 let b = Rgba8::multiply(c.b, c.a);
929 let a = c.a;
930 let (pixels, _rem) = span.as_chunks_mut::<BPP>();
931 for (px, &cov) in pixels.iter_mut().zip(covers.iter()) {
932 blend(px, r, g, b, a, cov);
933 }
934 });
935 }
936
937 fn blend_color_hspan(
938 &mut self,
939 x: i32,
940 y: i32,
941 len: u32,
942 colors: &[Rgba8],
943 covers: &[CoverType],
944 cover: CoverType,
945 ) {
946 let comp_op = self.comp_op;
947 let row = self.row_mut(y);
948 let span = &mut row[x as usize * BPP..(x as usize + len as usize) * BPP];
949 comp_op_span!(comp_op, |blend| {
950 let (pixels, _rem) = span.as_chunks_mut::<BPP>();
954 if covers.is_empty() {
955 for (px, c) in pixels.iter_mut().zip(colors.iter()) {
956 let r = Rgba8::multiply(c.r, c.a);
957 let g = Rgba8::multiply(c.g, c.a);
958 let b = Rgba8::multiply(c.b, c.a);
959 blend(px, r, g, b, c.a, cover);
960 }
961 } else {
962 for ((px, c), &cov) in pixels.iter_mut().zip(colors.iter()).zip(covers.iter()) {
963 let r = Rgba8::multiply(c.r, c.a);
964 let g = Rgba8::multiply(c.g, c.a);
965 let b = Rgba8::multiply(c.b, c.a);
966 blend(px, r, g, b, c.a, cov);
967 }
968 }
969 });
970 }
971}
972
973#[cfg(test)]
978mod tests {
979 use super::*;
980 use crate::rendering_buffer::RowAccessor;
981
982 fn make_buffer(w: u32, h: u32) -> (Vec<u8>, RowAccessor) {
983 let stride = (w * 4) as i32;
984 let buf = vec![0u8; (h * w * 4) as usize];
985 let mut ra = RowAccessor::new();
986 unsafe {
987 ra.attach(buf.as_ptr() as *mut u8, w, h, stride);
988 }
989 (buf, ra)
990 }
991
992 fn get_pixel(buf: &[u8], w: u32, x: u32, y: u32) -> (u8, u8, u8, u8) {
993 let off = (y * w * 4 + x * 4) as usize;
994 (buf[off], buf[off + 1], buf[off + 2], buf[off + 3])
995 }
996
997 #[test]
998 fn test_comp_op_default() {
999 assert_eq!(CompOp::default(), CompOp::SrcOver);
1000 }
1001
1002 #[test]
1003 fn test_clear() {
1004 let (_buf, mut ra) = make_buffer(10, 10);
1005 let mut pf = PixfmtRgba32CompOp::new(&mut ra);
1006 pf.copy_pixel(5, 5, &Rgba8::new(255, 0, 0, 255));
1008 let p = pf.pixel(5, 5);
1009 assert_eq!(p.r, 255);
1010
1011 pf.set_comp_op(CompOp::Clear);
1013 pf.blend_pixel(5, 5, &Rgba8::new(0, 0, 0, 255), 255);
1014 let p = pf.pixel(5, 5);
1015 assert_eq!(p.r, 0);
1016 assert_eq!(p.a, 0);
1017 }
1018
1019 #[test]
1020 fn test_src() {
1021 let (_buf, mut ra) = make_buffer(10, 10);
1022 let mut pf = PixfmtRgba32CompOp::new(&mut ra);
1023 pf.copy_pixel(0, 0, &Rgba8::new(100, 100, 100, 255));
1024
1025 pf.set_comp_op(CompOp::Src);
1026 pf.blend_pixel(0, 0, &Rgba8::new(200, 50, 0, 255), 255);
1027 let p = pf.pixel(0, 0);
1028 assert_eq!(p.r, 200);
1030 assert_eq!(p.g, 50);
1031 assert_eq!(p.b, 0);
1032 assert_eq!(p.a, 255);
1033 }
1034
1035 #[test]
1036 fn test_dst_is_noop() {
1037 let (_buf, mut ra) = make_buffer(10, 10);
1038 let mut pf = PixfmtRgba32CompOp::new(&mut ra);
1039 pf.copy_pixel(0, 0, &Rgba8::new(42, 43, 44, 200));
1040
1041 pf.set_comp_op(CompOp::Dst);
1042 pf.blend_pixel(0, 0, &Rgba8::new(255, 255, 255, 255), 255);
1043 let p = pf.pixel(0, 0);
1044 assert_eq!(p.r, 42);
1045 assert_eq!(p.g, 43);
1046 assert_eq!(p.b, 44);
1047 assert_eq!(p.a, 200);
1048 }
1049
1050 #[test]
1051 fn test_src_over_opaque() {
1052 let (_buf, mut ra) = make_buffer(10, 10);
1053 let mut pf = PixfmtRgba32CompOp::new(&mut ra);
1054 pf.copy_pixel(0, 0, &Rgba8::new(100, 100, 100, 255));
1055
1056 pf.set_comp_op(CompOp::SrcOver);
1057 pf.blend_pixel(0, 0, &Rgba8::new(200, 50, 25, 255), 255);
1058 let p = pf.pixel(0, 0);
1059 assert_eq!(p.r, 200);
1061 assert_eq!(p.g, 50);
1062 assert_eq!(p.b, 25);
1063 assert_eq!(p.a, 255);
1064 }
1065
1066 #[test]
1067 fn test_src_over_semitransparent() {
1068 let (_buf, mut ra) = make_buffer(10, 10);
1069 let mut pf = PixfmtRgba32CompOp::new(&mut ra);
1070 pf.copy_pixel(0, 0, &Rgba8::new(0, 0, 0, 255));
1071
1072 pf.set_comp_op(CompOp::SrcOver);
1073 pf.blend_pixel(0, 0, &Rgba8::new(255, 0, 0, 128), 255);
1075 let p = pf.pixel(0, 0);
1076 assert!(p.r > 100 && p.r < 140, "r={}", p.r);
1078 assert_eq!(p.a, 255);
1079 }
1080
1081 #[test]
1082 fn test_multiply_mode() {
1083 let (_buf, mut ra) = make_buffer(10, 10);
1084 let mut pf = PixfmtRgba32CompOp::new(&mut ra);
1085 pf.copy_pixel(0, 0, &Rgba8::new(255, 255, 255, 255));
1087
1088 pf.set_comp_op(CompOp::Multiply);
1089 pf.blend_pixel(0, 0, &Rgba8::new(128, 128, 128, 255), 255);
1090 let p = pf.pixel(0, 0);
1091 assert!(p.r > 120 && p.r < 140, "r={}", p.r);
1093 }
1094
1095 #[test]
1096 fn test_screen_mode() {
1097 let (_buf, mut ra) = make_buffer(10, 10);
1098 let mut pf = PixfmtRgba32CompOp::new(&mut ra);
1099 pf.copy_pixel(0, 0, &Rgba8::new(0, 0, 0, 255));
1101
1102 pf.set_comp_op(CompOp::Screen);
1103 pf.blend_pixel(0, 0, &Rgba8::new(128, 128, 128, 255), 255);
1104 let p = pf.pixel(0, 0);
1105 assert!(p.r > 120 && p.r < 140, "r={}", p.r);
1107 }
1108
1109 #[test]
1110 fn test_xor_mode() {
1111 let (_buf, mut ra) = make_buffer(10, 10);
1112 let mut pf = PixfmtRgba32CompOp::new(&mut ra);
1113 pf.copy_pixel(0, 0, &Rgba8::new(255, 0, 0, 255));
1115
1116 pf.set_comp_op(CompOp::Xor);
1117 pf.blend_pixel(0, 0, &Rgba8::new(0, 0, 255, 255), 255);
1119 let p = pf.pixel(0, 0);
1120 assert_eq!(p.a, 0);
1122 }
1123
1124 #[test]
1125 fn test_blend_hline() {
1126 let (_buf, mut ra) = make_buffer(20, 1);
1127 let mut pf = PixfmtRgba32CompOp::new(&mut ra);
1128 pf.set_comp_op(CompOp::SrcOver);
1129 let red = Rgba8::new(255, 0, 0, 255);
1130 pf.blend_hline(5, 0, 10, &red, 255);
1131 let p = pf.pixel(10, 0);
1133 assert_eq!(p.r, 255);
1134 let p = pf.pixel(0, 0);
1136 assert_eq!(p.a, 0);
1137 }
1138
1139 #[test]
1140 fn test_blend_solid_hspan() {
1141 let (_buf, mut ra) = make_buffer(10, 1);
1142 let mut pf = PixfmtRgba32CompOp::new(&mut ra);
1143 pf.set_comp_op(CompOp::SrcOver);
1144 let green = Rgba8::new(0, 255, 0, 255);
1145 let covers = [255u8, 128, 64, 0];
1146 pf.blend_solid_hspan(0, 0, 4, &green, &covers);
1147 let p = pf.pixel(0, 0);
1149 assert_eq!(p.g, 255);
1150 let p = pf.pixel(3, 0);
1152 assert_eq!(p.g, 0);
1153 }
1154
1155 #[test]
1156 fn test_all_ops_no_panic() {
1157 let ops = [
1158 CompOp::Clear,
1159 CompOp::Src,
1160 CompOp::Dst,
1161 CompOp::SrcOver,
1162 CompOp::DstOver,
1163 CompOp::SrcIn,
1164 CompOp::DstIn,
1165 CompOp::SrcOut,
1166 CompOp::DstOut,
1167 CompOp::SrcAtop,
1168 CompOp::DstAtop,
1169 CompOp::Xor,
1170 CompOp::Plus,
1171 CompOp::Minus,
1172 CompOp::Multiply,
1173 CompOp::Screen,
1174 CompOp::Overlay,
1175 CompOp::Darken,
1176 CompOp::Lighten,
1177 CompOp::ColorDodge,
1178 CompOp::ColorBurn,
1179 CompOp::HardLight,
1180 CompOp::SoftLight,
1181 CompOp::Difference,
1182 CompOp::Exclusion,
1183 ];
1184 let (_buf, mut ra) = make_buffer(10, 10);
1185 let mut pf = PixfmtRgba32CompOp::new(&mut ra);
1186 let c = Rgba8::new(128, 64, 32, 200);
1187 pf.copy_pixel(0, 0, &Rgba8::new(100, 150, 200, 180));
1189 for &op in &ops {
1190 pf.set_comp_op(op);
1191 pf.blend_pixel(0, 0, &c, 128);
1192 }
1193 }
1194
1195 #[test]
1196 fn test_difference_mode() {
1197 let (_buf, mut ra) = make_buffer(10, 10);
1198 let mut pf = PixfmtRgba32CompOp::new(&mut ra);
1199 pf.copy_pixel(0, 0, &Rgba8::new(255, 255, 255, 255));
1201
1202 pf.set_comp_op(CompOp::Difference);
1203 pf.blend_pixel(0, 0, &Rgba8::new(255, 255, 255, 255), 255);
1204 let p = pf.pixel(0, 0);
1205 assert!(p.r < 5, "r={}", p.r);
1207 assert!(p.g < 5, "g={}", p.g);
1208 assert!(p.b < 5, "b={}", p.b);
1209 }
1210
1211 #[test]
1212 fn test_plus_saturates() {
1213 let (_buf, mut ra) = make_buffer(10, 10);
1214 let mut pf = PixfmtRgba32CompOp::new(&mut ra);
1215 pf.copy_pixel(0, 0, &Rgba8::new(200, 200, 200, 255));
1216
1217 pf.set_comp_op(CompOp::Plus);
1218 pf.blend_pixel(0, 0, &Rgba8::new(200, 200, 200, 255), 255);
1219 let p = pf.pixel(0, 0);
1220 assert_eq!(p.r, 255);
1222 assert_eq!(p.g, 255);
1223 assert_eq!(p.b, 255);
1224 }
1225
1226 #[test]
1247 fn test_src_atop_replicates_cpp_blue_channel_typo() {
1248 let (_buf, mut ra) = make_buffer(10, 10);
1249 let mut pf = PixfmtRgba32CompOp::new(&mut ra);
1250 pf.copy_pixel(0, 0, &Rgba8::new(10, 200, 40, 200));
1252
1253 pf.set_comp_op(CompOp::SrcAtop);
1254 pf.blend_pixel(0, 0, &Rgba8::new(80, 60, 20, 100), 255);
1255 let p = pf.pixel(0, 0);
1256 assert_eq!(p.r, 30);
1257 assert_eq!(p.g, 140);
1258 assert_eq!(p.b, 92);
1259 assert_eq!(p.a, 200);
1260 }
1261
1262 #[test]
1279 fn test_minus_clip_clamps_rgb_to_alpha() {
1280 let (_buf, mut ra) = make_buffer(10, 10);
1281 let mut pf = PixfmtRgba32CompOp::new(&mut ra);
1282 pf.copy_pixel(0, 0, &Rgba8::new(250, 10, 10, 40));
1284
1285 pf.set_comp_op(CompOp::Minus);
1286 pf.blend_pixel(0, 0, &Rgba8::new(0, 0, 0, 100), 255);
1287 let p = pf.pixel(0, 0);
1288 assert_eq!(p.a, 124);
1289 assert_eq!(p.r, 124); assert_eq!(p.g, 10);
1291 assert_eq!(p.b, 10);
1292 }
1293}