blob: d816b7225058573329aab7b88e8dee641a67ce2e [file] [log] [blame]
mtklein767d2732015-07-16 07:01:39 -07001/*
2 * Copyright 2015 Google Inc.
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
commit-bot@chromium.org7f428a92014-02-25 21:31:03 +00008#include "Test.h"
9#include "SkColor.h"
mtklein767d2732015-07-16 07:01:39 -070010#include "SkColorPriv.h"
11#include "SkTaskGroup.h"
12#include "SkXfermode.h"
commit-bot@chromium.org7f428a92014-02-25 21:31:03 +000013
14#define ASSERT(x) REPORTER_ASSERT(r, x)
15
commit-bot@chromium.org40ac5e52014-02-26 21:40:07 +000016static uint8_t double_to_u8(double d) {
17 SkASSERT(d >= 0);
18 SkASSERT(d < 256);
19 return uint8_t(d);
20}
21
commit-bot@chromium.org7f428a92014-02-25 21:31:03 +000022// All algorithms we're testing have this interface.
23// We want a single channel blend, src over dst, assuming src is premultiplied by srcAlpha.
24typedef uint8_t(*Blend)(uint8_t dst, uint8_t src, uint8_t srcAlpha);
25
26// This is our golden algorithm.
27static uint8_t blend_double_round(uint8_t dst, uint8_t src, uint8_t srcAlpha) {
28 SkASSERT(src <= srcAlpha);
commit-bot@chromium.org40ac5e52014-02-26 21:40:07 +000029 return double_to_u8(0.5 + src + dst * (255.0 - srcAlpha) / 255.0);
commit-bot@chromium.org7f428a92014-02-25 21:31:03 +000030}
31
32static uint8_t abs_diff(uint8_t a, uint8_t b) {
33 const int diff = a - b;
34 return diff > 0 ? diff : -diff;
35}
36
37static void test(skiatest::Reporter* r, int maxDiff, Blend algorithm,
38 uint8_t dst, uint8_t src, uint8_t alpha) {
39 const uint8_t golden = blend_double_round(dst, src, alpha);
40 const uint8_t blend = algorithm(dst, src, alpha);
41 if (abs_diff(blend, golden) > maxDiff) {
42 SkDebugf("dst %02x, src %02x, alpha %02x, |%02x - %02x| > %d\n",
43 dst, src, alpha, blend, golden, maxDiff);
44 ASSERT(abs_diff(blend, golden) <= maxDiff);
45 }
46}
47
48// Exhaustively compare an algorithm against our golden, for a given alpha.
49static void test_alpha(skiatest::Reporter* r, uint8_t alpha, int maxDiff, Blend algorithm) {
50 SkASSERT(maxDiff >= 0);
51
52 for (unsigned src = 0; src <= alpha; src++) {
53 for (unsigned dst = 0; dst < 256; dst++) {
54 test(r, maxDiff, algorithm, dst, src, alpha);
55 }
56 }
57}
58
59// Exhaustively compare an algorithm against our golden, for a given dst.
60static void test_dst(skiatest::Reporter* r, uint8_t dst, int maxDiff, Blend algorithm) {
61 SkASSERT(maxDiff >= 0);
62
63 for (unsigned alpha = 0; alpha < 256; alpha++) {
64 for (unsigned src = 0; src <= alpha; src++) {
65 test(r, maxDiff, algorithm, dst, src, alpha);
66 }
67 }
68}
69
70static uint8_t blend_double_trunc(uint8_t dst, uint8_t src, uint8_t srcAlpha) {
commit-bot@chromium.org40ac5e52014-02-26 21:40:07 +000071 return double_to_u8(src + dst * (255.0 - srcAlpha) / 255.0);
commit-bot@chromium.org7f428a92014-02-25 21:31:03 +000072}
73
74static uint8_t blend_float_trunc(uint8_t dst, uint8_t src, uint8_t srcAlpha) {
commit-bot@chromium.org40ac5e52014-02-26 21:40:07 +000075 return double_to_u8(src + dst * (255.0f - srcAlpha) / 255.0f);
commit-bot@chromium.org7f428a92014-02-25 21:31:03 +000076}
77
78static uint8_t blend_float_round(uint8_t dst, uint8_t src, uint8_t srcAlpha) {
commit-bot@chromium.org40ac5e52014-02-26 21:40:07 +000079 return double_to_u8(0.5f + src + dst * (255.0f - srcAlpha) / 255.0f);
commit-bot@chromium.org7f428a92014-02-25 21:31:03 +000080}
81
82static uint8_t blend_255_trunc(uint8_t dst, uint8_t src, uint8_t srcAlpha) {
83 const uint16_t invAlpha = 255 - srcAlpha;
84 const uint16_t product = dst * invAlpha;
85 return src + (product >> 8);
86}
87
88static uint8_t blend_255_round(uint8_t dst, uint8_t src, uint8_t srcAlpha) {
89 const uint16_t invAlpha = 255 - srcAlpha;
90 const uint16_t product = dst * invAlpha + 128;
91 return src + (product >> 8);
92}
93
94static uint8_t blend_256_trunc(uint8_t dst, uint8_t src, uint8_t srcAlpha) {
95 const uint16_t invAlpha = 256 - (srcAlpha + (srcAlpha >> 7));
96 const uint16_t product = dst * invAlpha;
97 return src + (product >> 8);
98}
99
100static uint8_t blend_256_round(uint8_t dst, uint8_t src, uint8_t srcAlpha) {
101 const uint16_t invAlpha = 256 - (srcAlpha + (srcAlpha >> 7));
102 const uint16_t product = dst * invAlpha + 128;
103 return src + (product >> 8);
104}
105
106static uint8_t blend_256_round_alt(uint8_t dst, uint8_t src, uint8_t srcAlpha) {
107 const uint8_t invAlpha8 = 255 - srcAlpha;
108 const uint16_t invAlpha = invAlpha8 + (invAlpha8 >> 7);
109 const uint16_t product = dst * invAlpha + 128;
110 return src + (product >> 8);
111}
112
113static uint8_t blend_256_plus1_trunc(uint8_t dst, uint8_t src, uint8_t srcAlpha) {
114 const uint16_t invAlpha = 256 - (srcAlpha + 1);
115 const uint16_t product = dst * invAlpha;
116 return src + (product >> 8);
117}
118
119static uint8_t blend_256_plus1_round(uint8_t dst, uint8_t src, uint8_t srcAlpha) {
120 const uint16_t invAlpha = 256 - (srcAlpha + 1);
121 const uint16_t product = dst * invAlpha + 128;
122 return src + (product >> 8);
123}
124
125static uint8_t blend_perfect(uint8_t dst, uint8_t src, uint8_t srcAlpha) {
126 const uint8_t invAlpha = 255 - srcAlpha;
127 const uint16_t product = dst * invAlpha + 128;
128 return src + ((product + (product >> 8)) >> 8);
129}
130
131
132// We want 0 diff whenever src is fully transparent.
133DEF_TEST(Blend_alpha_0x00, r) {
134 const uint8_t alpha = 0x00;
135
136 // GOOD
137 test_alpha(r, alpha, 0, blend_256_round);
138 test_alpha(r, alpha, 0, blend_256_round_alt);
139 test_alpha(r, alpha, 0, blend_256_trunc);
140 test_alpha(r, alpha, 0, blend_double_trunc);
141 test_alpha(r, alpha, 0, blend_float_round);
142 test_alpha(r, alpha, 0, blend_float_trunc);
143 test_alpha(r, alpha, 0, blend_perfect);
144
145 // BAD
146 test_alpha(r, alpha, 1, blend_255_round);
147 test_alpha(r, alpha, 1, blend_255_trunc);
148 test_alpha(r, alpha, 1, blend_256_plus1_round);
149 test_alpha(r, alpha, 1, blend_256_plus1_trunc);
150}
151
152// We want 0 diff whenever dst is 0.
153DEF_TEST(Blend_dst_0x00, r) {
154 const uint8_t dst = 0x00;
155
156 // GOOD
157 test_dst(r, dst, 0, blend_255_round);
158 test_dst(r, dst, 0, blend_255_trunc);
159 test_dst(r, dst, 0, blend_256_plus1_round);
160 test_dst(r, dst, 0, blend_256_plus1_trunc);
161 test_dst(r, dst, 0, blend_256_round);
162 test_dst(r, dst, 0, blend_256_round_alt);
163 test_dst(r, dst, 0, blend_256_trunc);
164 test_dst(r, dst, 0, blend_double_trunc);
165 test_dst(r, dst, 0, blend_float_round);
166 test_dst(r, dst, 0, blend_float_trunc);
167 test_dst(r, dst, 0, blend_perfect);
168
169 // BAD
170}
171
172// We want 0 diff whenever src is fully opaque.
173DEF_TEST(Blend_alpha_0xFF, r) {
174 const uint8_t alpha = 0xFF;
175
176 // GOOD
177 test_alpha(r, alpha, 0, blend_255_round);
178 test_alpha(r, alpha, 0, blend_255_trunc);
179 test_alpha(r, alpha, 0, blend_256_plus1_round);
180 test_alpha(r, alpha, 0, blend_256_plus1_trunc);
181 test_alpha(r, alpha, 0, blend_256_round);
182 test_alpha(r, alpha, 0, blend_256_round_alt);
183 test_alpha(r, alpha, 0, blend_256_trunc);
184 test_alpha(r, alpha, 0, blend_double_trunc);
185 test_alpha(r, alpha, 0, blend_float_round);
186 test_alpha(r, alpha, 0, blend_float_trunc);
187 test_alpha(r, alpha, 0, blend_perfect);
188
189 // BAD
190}
191
192// We want 0 diff whenever dst is 0xFF.
193DEF_TEST(Blend_dst_0xFF, r) {
194 const uint8_t dst = 0xFF;
195
196 // GOOD
197 test_dst(r, dst, 0, blend_256_round);
198 test_dst(r, dst, 0, blend_256_round_alt);
199 test_dst(r, dst, 0, blend_double_trunc);
200 test_dst(r, dst, 0, blend_float_round);
201 test_dst(r, dst, 0, blend_float_trunc);
202 test_dst(r, dst, 0, blend_perfect);
203
204 // BAD
205 test_dst(r, dst, 1, blend_255_round);
206 test_dst(r, dst, 1, blend_255_trunc);
207 test_dst(r, dst, 1, blend_256_plus1_round);
208 test_dst(r, dst, 1, blend_256_plus1_trunc);
209 test_dst(r, dst, 1, blend_256_trunc);
210}
211
212// We'd like diff <= 1 everywhere.
213DEF_TEST(Blend_alpha_Exhaustive, r) {
214 for (unsigned alpha = 0; alpha < 256; alpha++) {
215 // PERFECT
216 test_alpha(r, alpha, 0, blend_float_round);
217 test_alpha(r, alpha, 0, blend_perfect);
218
219 // GOOD
220 test_alpha(r, alpha, 1, blend_255_round);
221 test_alpha(r, alpha, 1, blend_256_plus1_round);
222 test_alpha(r, alpha, 1, blend_256_round);
223 test_alpha(r, alpha, 1, blend_256_round_alt);
224 test_alpha(r, alpha, 1, blend_256_trunc);
225 test_alpha(r, alpha, 1, blend_double_trunc);
226 test_alpha(r, alpha, 1, blend_float_trunc);
227
228 // BAD
229 test_alpha(r, alpha, 2, blend_255_trunc);
230 test_alpha(r, alpha, 2, blend_256_plus1_trunc);
231 }
232}
233
234// We'd like diff <= 1 everywhere.
235DEF_TEST(Blend_dst_Exhaustive, r) {
236 for (unsigned dst = 0; dst < 256; dst++) {
237 // PERFECT
238 test_dst(r, dst, 0, blend_float_round);
239 test_dst(r, dst, 0, blend_perfect);
240
241 // GOOD
242 test_dst(r, dst, 1, blend_255_round);
243 test_dst(r, dst, 1, blend_256_plus1_round);
244 test_dst(r, dst, 1, blend_256_round);
245 test_dst(r, dst, 1, blend_256_round_alt);
246 test_dst(r, dst, 1, blend_256_trunc);
247 test_dst(r, dst, 1, blend_double_trunc);
248 test_dst(r, dst, 1, blend_float_trunc);
249
250 // BAD
251 test_dst(r, dst, 2, blend_255_trunc);
252 test_dst(r, dst, 2, blend_256_plus1_trunc);
253 }
254}
255// Overall summary:
256// PERFECT
257// blend_double_round
258// blend_float_round
259// blend_perfect
260// GOOD ENOUGH
261// blend_double_trunc
262// blend_float_trunc
263// blend_256_round
264// blend_256_round_alt
265// NOT GOOD ENOUGH
266// all others
267//
268// Algorithms that make sense to use in Skia: blend_256_round, blend_256_round_alt, blend_perfect
mtklein767d2732015-07-16 07:01:39 -0700269
270DEF_TEST(Blend_premul_begets_premul, r) {
271 // This test is quite slow, even if you have enough cores to run each mode in parallel.
272 if (!r->allowExtendedTest()) {
273 return;
274 }
275
276 // No matter what xfermode we use, premul inputs should create premul outputs.
277 auto test_mode = [&](int m) {
278 SkXfermode::Mode mode = (SkXfermode::Mode)m;
279 if (mode == SkXfermode::kSrcOver_Mode) {
280 return; // TODO: can't create a SrcOver xfermode.
281 }
282 SkAutoTUnref<SkXfermode> xfermode(SkXfermode::Create(mode));
283 SkASSERT(xfermode);
284 // We'll test all alphas and legal color values, assuming all colors work the same.
285 // This is not true for non-separable blend modes, but this test still can't hurt.
286 for (int sa = 0; sa <= 255; sa++) {
287 for (int da = 0; da <= 255; da++) {
288 for (int s = 0; s <= sa; s++) {
289 for (int d = 0; d <= da; d++) {
290 SkPMColor src = SkPackARGB32(sa, s, s, s),
291 dst = SkPackARGB32(da, d, d, d);
292 xfermode->xfer32(&dst, &src, 1, nullptr); // To keep it simple, no AA.
293 if (!SkPMColorValid(dst)) {
294 ERRORF(r, "%08x is not premul using %s", dst, SkXfermode::ModeName(mode));
295 }
296 }}}}
297 };
298
299 // Parallelism helps speed things up on my desktop from ~725s to ~50s.
300 sk_parallel_for(SkXfermode::kLastMode, test_mode);
301}