1 | /* |
2 | * Copyright 2017 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 | |
8 | #include "include/core/SkString.h" |
9 | #include "include/effects/SkHighContrastFilter.h" |
10 | #include "include/private/SkColorData.h" |
11 | #include "src/core/SkArenaAlloc.h" |
12 | #include "src/core/SkColorSpacePriv.h" |
13 | #include "src/core/SkEffectPriv.h" |
14 | #include "src/core/SkRasterPipeline.h" |
15 | #include "src/core/SkReadBuffer.h" |
16 | #include "src/core/SkVM.h" |
17 | #include "src/core/SkWriteBuffer.h" |
18 | |
19 | #if SK_SUPPORT_GPU |
20 | #include "include/gpu/GrContext.h" |
21 | #include "src/gpu/GrColorInfo.h" |
22 | #include "src/gpu/glsl/GrGLSLFragmentProcessor.h" |
23 | #include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" |
24 | #endif |
25 | |
26 | using InvertStyle = SkHighContrastConfig::InvertStyle; |
27 | |
28 | class SkHighContrast_Filter : public SkColorFilter { |
29 | public: |
30 | SkHighContrast_Filter(const SkHighContrastConfig& config) { |
31 | fConfig = config; |
32 | // Clamp contrast to just inside -1 to 1 to avoid division by zero. |
33 | fConfig.fContrast = SkTPin(fConfig.fContrast, |
34 | -1.0f + FLT_EPSILON, |
35 | 1.0f - FLT_EPSILON); |
36 | } |
37 | |
38 | ~SkHighContrast_Filter() override {} |
39 | |
40 | #if SK_SUPPORT_GPU |
41 | std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(GrRecordingContext*, |
42 | const GrColorInfo&) const override; |
43 | #endif |
44 | |
45 | bool onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const override; |
46 | skvm::Color onProgram(skvm::Builder*, skvm::Color, SkColorSpace*, skvm::Uniforms*, |
47 | SkArenaAlloc*) const override; |
48 | |
49 | protected: |
50 | void flatten(SkWriteBuffer&) const override; |
51 | |
52 | private: |
53 | SK_FLATTENABLE_HOOKS(SkHighContrast_Filter) |
54 | |
55 | SkHighContrastConfig fConfig; |
56 | |
57 | friend class SkHighContrastFilter; |
58 | |
59 | typedef SkColorFilter INHERITED; |
60 | }; |
61 | |
62 | bool SkHighContrast_Filter::onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const { |
63 | SkRasterPipeline* p = rec.fPipeline; |
64 | SkArenaAlloc* alloc = rec.fAlloc; |
65 | |
66 | if (!shaderIsOpaque) { |
67 | p->append(SkRasterPipeline::unpremul); |
68 | } |
69 | |
70 | // Linearize before applying high-contrast filter. |
71 | auto tf = alloc->make<skcms_TransferFunction>(); |
72 | if (rec.fDstCS) { |
73 | rec.fDstCS->transferFn(tf); |
74 | } else { |
75 | // Historically we approximate untagged destinations as gamma 2. |
76 | // TODO: sRGB? |
77 | *tf = {2,1, 0,0,0,0,0}; |
78 | } |
79 | p->append_transfer_function(*tf); |
80 | |
81 | if (fConfig.fGrayscale) { |
82 | float r = SK_LUM_COEFF_R; |
83 | float g = SK_LUM_COEFF_G; |
84 | float b = SK_LUM_COEFF_B; |
85 | float* matrix = alloc->makeArray<float>(12); |
86 | matrix[0] = matrix[1] = matrix[2] = r; |
87 | matrix[3] = matrix[4] = matrix[5] = g; |
88 | matrix[6] = matrix[7] = matrix[8] = b; |
89 | p->append(SkRasterPipeline::matrix_3x4, matrix); |
90 | } |
91 | |
92 | if (fConfig.fInvertStyle == InvertStyle::kInvertBrightness) { |
93 | float* matrix = alloc->makeArray<float>(12); |
94 | matrix[0] = matrix[4] = matrix[8] = -1; |
95 | matrix[9] = matrix[10] = matrix[11] = 1; |
96 | p->append(SkRasterPipeline::matrix_3x4, matrix); |
97 | } else if (fConfig.fInvertStyle == InvertStyle::kInvertLightness) { |
98 | p->append(SkRasterPipeline::rgb_to_hsl); |
99 | float* matrix = alloc->makeArray<float>(12); |
100 | matrix[0] = matrix[4] = matrix[11] = 1; |
101 | matrix[8] = -1; |
102 | p->append(SkRasterPipeline::matrix_3x4, matrix); |
103 | p->append(SkRasterPipeline::hsl_to_rgb); |
104 | } |
105 | |
106 | if (fConfig.fContrast != 0.0) { |
107 | float* matrix = alloc->makeArray<float>(12); |
108 | float c = fConfig.fContrast; |
109 | float m = (1 + c) / (1 - c); |
110 | float b = (-0.5f * m + 0.5f); |
111 | matrix[0] = matrix[4] = matrix[8] = m; |
112 | matrix[9] = matrix[10] = matrix[11] = b; |
113 | p->append(SkRasterPipeline::matrix_3x4, matrix); |
114 | } |
115 | |
116 | p->append(SkRasterPipeline::clamp_0); |
117 | p->append(SkRasterPipeline::clamp_1); |
118 | |
119 | // Re-encode back from linear. |
120 | auto invTF = alloc->make<skcms_TransferFunction>(); |
121 | if (rec.fDstCS) { |
122 | rec.fDstCS->invTransferFn(invTF); |
123 | } else { |
124 | // See above... historically untagged == gamma 2 in this filter. |
125 | *invTF ={0.5f,1, 0,0,0,0,0}; |
126 | } |
127 | p->append_transfer_function(*invTF); |
128 | |
129 | if (!shaderIsOpaque) { |
130 | p->append(SkRasterPipeline::premul); |
131 | } |
132 | return true; |
133 | } |
134 | |
135 | skvm::Color SkHighContrast_Filter::onProgram(skvm::Builder* p, skvm::Color c, SkColorSpace* dstCS, |
136 | skvm::Uniforms* uniforms, SkArenaAlloc* alloc) const { |
137 | c = p->unpremul(c); |
138 | |
139 | // Linearize before applying high-contrast filter. |
140 | skcms_TransferFunction tf; |
141 | if (dstCS) { |
142 | dstCS->transferFn(&tf); |
143 | } else { |
144 | //sk_srgb_singleton()->transferFn(&tf); |
145 | tf = {2,1, 0,0,0,0,0}; |
146 | } |
147 | c = sk_program_transfer_fn(p, uniforms, tf, c); |
148 | |
149 | if (fConfig.fGrayscale) { |
150 | skvm::F32 gray = c.r * SK_LUM_COEFF_R |
151 | + c.g * SK_LUM_COEFF_G |
152 | + c.b * SK_LUM_COEFF_B; |
153 | c = {gray, gray, gray, c.a}; |
154 | } |
155 | |
156 | if (fConfig.fInvertStyle == InvertStyle::kInvertBrightness) { |
157 | c = {1-c.r, 1-c.g, 1-c.b, c.a}; |
158 | } else if (fConfig.fInvertStyle == InvertStyle::kInvertLightness) { |
159 | auto [h, s, l, a] = p->to_hsla(c); |
160 | c = p->to_rgba({h, s, 1-l, a}); |
161 | } |
162 | |
163 | if (fConfig.fContrast != 0.0) { |
164 | const float m = (1 + fConfig.fContrast) / (1 - fConfig.fContrast); |
165 | const float b = (-0.5f * m + 0.5f); |
166 | skvm::F32 M = p->uniformF(uniforms->pushF(m)); |
167 | skvm::F32 B = p->uniformF(uniforms->pushF(b)); |
168 | c.r = c.r * M + B; |
169 | c.g = c.g * M + B; |
170 | c.b = c.b * M + B; |
171 | } |
172 | |
173 | c.r = clamp01(c.r); |
174 | c.g = clamp01(c.g); |
175 | c.b = clamp01(c.b); |
176 | |
177 | // Re-encode back from linear. |
178 | if (dstCS) { |
179 | dstCS->invTransferFn(&tf); |
180 | } else { |
181 | //sk_srgb_singleton()->invTransferFn(&tf); |
182 | tf = {0.5f,1, 0,0,0,0,0}; |
183 | } |
184 | c = sk_program_transfer_fn(p, uniforms, tf, c); |
185 | |
186 | return p->premul(c); |
187 | } |
188 | |
189 | void SkHighContrast_Filter::flatten(SkWriteBuffer& buffer) const { |
190 | buffer.writeBool(fConfig.fGrayscale); |
191 | buffer.writeInt(static_cast<int>(fConfig.fInvertStyle)); |
192 | buffer.writeScalar(fConfig.fContrast); |
193 | } |
194 | |
195 | sk_sp<SkFlattenable> SkHighContrast_Filter::CreateProc(SkReadBuffer& buffer) { |
196 | SkHighContrastConfig config; |
197 | config.fGrayscale = buffer.readBool(); |
198 | config.fInvertStyle = buffer.read32LE(InvertStyle::kLast); |
199 | config.fContrast = buffer.readScalar(); |
200 | |
201 | return SkHighContrastFilter::Make(config); |
202 | } |
203 | |
204 | sk_sp<SkColorFilter> SkHighContrastFilter::Make( |
205 | const SkHighContrastConfig& config) { |
206 | if (!config.isValid()) { |
207 | return nullptr; |
208 | } |
209 | return sk_make_sp<SkHighContrast_Filter>(config); |
210 | } |
211 | |
212 | void SkHighContrastFilter::RegisterFlattenables() { |
213 | SK_REGISTER_FLATTENABLE(SkHighContrast_Filter); |
214 | } |
215 | |
216 | #if SK_SUPPORT_GPU |
217 | class HighContrastFilterEffect : public GrFragmentProcessor { |
218 | public: |
219 | static std::unique_ptr<GrFragmentProcessor> Make(const SkHighContrastConfig& config, |
220 | bool linearize) { |
221 | return std::unique_ptr<GrFragmentProcessor>(new HighContrastFilterEffect(config, |
222 | linearize)); |
223 | } |
224 | |
225 | const char* name() const override { return "HighContrastFilter" ; } |
226 | |
227 | const SkHighContrastConfig& config() const { return fConfig; } |
228 | bool linearize() const { return fLinearize; } |
229 | |
230 | std::unique_ptr<GrFragmentProcessor> clone() const override { |
231 | return Make(fConfig, fLinearize); |
232 | } |
233 | |
234 | private: |
235 | HighContrastFilterEffect(const SkHighContrastConfig& config, bool linearize) |
236 | : INHERITED(kHighContrastFilterEffect_ClassID, kNone_OptimizationFlags) |
237 | , fConfig(config) |
238 | , fLinearize(linearize) {} |
239 | |
240 | GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; |
241 | |
242 | virtual void onGetGLSLProcessorKey(const GrShaderCaps& caps, |
243 | GrProcessorKeyBuilder* b) const override; |
244 | |
245 | bool onIsEqual(const GrFragmentProcessor& other) const override { |
246 | const HighContrastFilterEffect& that = other.cast<HighContrastFilterEffect>(); |
247 | return fConfig.fGrayscale == that.fConfig.fGrayscale && |
248 | fConfig.fInvertStyle == that.fConfig.fInvertStyle && |
249 | fConfig.fContrast == that.fConfig.fContrast && |
250 | fLinearize == that.fLinearize; |
251 | } |
252 | |
253 | SkHighContrastConfig fConfig; |
254 | bool fLinearize; |
255 | |
256 | typedef GrFragmentProcessor INHERITED; |
257 | }; |
258 | |
259 | class GLHighContrastFilterEffect : public GrGLSLFragmentProcessor { |
260 | public: |
261 | static void GenKey(const GrProcessor&, const GrShaderCaps&, GrProcessorKeyBuilder*); |
262 | |
263 | protected: |
264 | void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; |
265 | void emitCode(EmitArgs& args) override; |
266 | |
267 | private: |
268 | UniformHandle fContrastUni; |
269 | |
270 | typedef GrGLSLFragmentProcessor INHERITED; |
271 | }; |
272 | |
273 | GrGLSLFragmentProcessor* HighContrastFilterEffect::onCreateGLSLInstance() const { |
274 | return new GLHighContrastFilterEffect(); |
275 | } |
276 | |
277 | void HighContrastFilterEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, |
278 | GrProcessorKeyBuilder* b) const { |
279 | GLHighContrastFilterEffect::GenKey(*this, caps, b); |
280 | } |
281 | |
282 | void GLHighContrastFilterEffect::onSetData(const GrGLSLProgramDataManager& pdm, |
283 | const GrFragmentProcessor& proc) { |
284 | const HighContrastFilterEffect& hcfe = proc.cast<HighContrastFilterEffect>(); |
285 | pdm.set1f(fContrastUni, hcfe.config().fContrast); |
286 | } |
287 | |
288 | void GLHighContrastFilterEffect::GenKey( |
289 | const GrProcessor& proc, const GrShaderCaps&, GrProcessorKeyBuilder* b) { |
290 | const HighContrastFilterEffect& hcfe = proc.cast<HighContrastFilterEffect>(); |
291 | b->add32(static_cast<uint32_t>(hcfe.config().fGrayscale)); |
292 | b->add32(static_cast<uint32_t>(hcfe.config().fInvertStyle)); |
293 | b->add32(hcfe.linearize() ? 1 : 0); |
294 | } |
295 | |
296 | void GLHighContrastFilterEffect::emitCode(EmitArgs& args) { |
297 | const HighContrastFilterEffect& hcfe = args.fFp.cast<HighContrastFilterEffect>(); |
298 | const SkHighContrastConfig& config = hcfe.config(); |
299 | |
300 | const char* contrast; |
301 | fContrastUni = args.fUniformHandler->addUniform(&hcfe, kFragment_GrShaderFlag, kHalf_GrSLType, |
302 | "contrast" , &contrast); |
303 | |
304 | GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; |
305 | |
306 | fragBuilder->codeAppendf("half4 color = %s;" , args.fInputColor); |
307 | |
308 | // Unpremultiply. The max() is to guard against 0 / 0. |
309 | fragBuilder->codeAppendf("half nonZeroAlpha = max(color.a, 0.0001);" ); |
310 | fragBuilder->codeAppendf("color = half4(color.rgb / nonZeroAlpha, nonZeroAlpha);" ); |
311 | |
312 | if (hcfe.linearize()) { |
313 | fragBuilder->codeAppend("color.rgb = color.rgb * color.rgb;" ); |
314 | } |
315 | |
316 | // Grayscale. |
317 | if (config.fGrayscale) { |
318 | fragBuilder->codeAppendf("half luma = dot(color, half4(%f, %f, %f, 0));" , |
319 | SK_LUM_COEFF_R, SK_LUM_COEFF_G, SK_LUM_COEFF_B); |
320 | fragBuilder->codeAppendf("color = half4(luma, luma, luma, 0);" ); |
321 | } |
322 | |
323 | if (config.fInvertStyle == InvertStyle::kInvertBrightness) { |
324 | fragBuilder->codeAppendf("color = half4(1, 1, 1, 1) - color;" ); |
325 | } |
326 | |
327 | if (config.fInvertStyle == InvertStyle::kInvertLightness) { |
328 | // Convert from RGB to HSL. |
329 | fragBuilder->codeAppendf("half fmax = max(color.r, max(color.g, color.b));" ); |
330 | fragBuilder->codeAppendf("half fmin = min(color.r, min(color.g, color.b));" ); |
331 | fragBuilder->codeAppendf("half l = (fmax + fmin) / 2;" ); |
332 | |
333 | fragBuilder->codeAppendf("half h;" ); |
334 | fragBuilder->codeAppendf("half s;" ); |
335 | |
336 | fragBuilder->codeAppendf("if (fmax == fmin) {" ); |
337 | fragBuilder->codeAppendf(" h = 0;" ); |
338 | fragBuilder->codeAppendf(" s = 0;" ); |
339 | fragBuilder->codeAppendf("} else {" ); |
340 | fragBuilder->codeAppendf(" half d = fmax - fmin;" ); |
341 | fragBuilder->codeAppendf(" s = l > 0.5 ?" ); |
342 | fragBuilder->codeAppendf(" d / (2 - fmax - fmin) :" ); |
343 | fragBuilder->codeAppendf(" d / (fmax + fmin);" ); |
344 | // We'd like to just write "if (color.r == fmax) { ... }". On many GPUs, running the |
345 | // angle_d3d9_es2 config, that failed. It seems that max(x, y) is not necessarily equal |
346 | // to either x or y. Tried several ways to fix it, but this was the only reasonable fix. |
347 | fragBuilder->codeAppendf(" if (color.r >= color.g && color.r >= color.b) {" ); |
348 | fragBuilder->codeAppendf(" h = (color.g - color.b) / d + " ); |
349 | fragBuilder->codeAppendf(" (color.g < color.b ? 6 : 0);" ); |
350 | fragBuilder->codeAppendf(" } else if (color.g >= color.b) {" ); |
351 | fragBuilder->codeAppendf(" h = (color.b - color.r) / d + 2;" ); |
352 | fragBuilder->codeAppendf(" } else {" ); |
353 | fragBuilder->codeAppendf(" h = (color.r - color.g) / d + 4;" ); |
354 | fragBuilder->codeAppendf(" }" ); |
355 | fragBuilder->codeAppendf("}" ); |
356 | fragBuilder->codeAppendf("h /= 6;" ); |
357 | fragBuilder->codeAppendf("l = 1.0 - l;" ); |
358 | // Convert back from HSL to RGB. |
359 | SkString hue2rgbFuncName; |
360 | const GrShaderVar gHue2rgbArgs[] = { |
361 | GrShaderVar("p" , kHalf_GrSLType), |
362 | GrShaderVar("q" , kHalf_GrSLType), |
363 | GrShaderVar("t" , kHalf_GrSLType), |
364 | }; |
365 | fragBuilder->emitFunction(kHalf_GrSLType, |
366 | "hue2rgb" , |
367 | SK_ARRAY_COUNT(gHue2rgbArgs), |
368 | gHue2rgbArgs, |
369 | "if (t < 0)" |
370 | " t += 1;" |
371 | "if (t > 1)" |
372 | " t -= 1;" |
373 | "if (t < 1/6.)" |
374 | " return p + (q - p) * 6 * t;" |
375 | "if (t < 1/2.)" |
376 | " return q;" |
377 | "if (t < 2/3.)" |
378 | " return p + (q - p) * (2/3. - t) * 6;" |
379 | "return p;" , |
380 | &hue2rgbFuncName); |
381 | fragBuilder->codeAppendf("if (s == 0) {" ); |
382 | fragBuilder->codeAppendf(" color = half4(l, l, l, 0);" ); |
383 | fragBuilder->codeAppendf("} else {" ); |
384 | fragBuilder->codeAppendf(" half q = l < 0.5 ? l * (1 + s) : l + s - l * s;" ); |
385 | fragBuilder->codeAppendf(" half p = 2 * l - q;" ); |
386 | fragBuilder->codeAppendf(" color.r = %s(p, q, h + 1/3.);" , hue2rgbFuncName.c_str()); |
387 | fragBuilder->codeAppendf(" color.g = %s(p, q, h);" , hue2rgbFuncName.c_str()); |
388 | fragBuilder->codeAppendf(" color.b = %s(p, q, h - 1/3.);" , hue2rgbFuncName.c_str()); |
389 | fragBuilder->codeAppendf("}" ); |
390 | } |
391 | |
392 | // Contrast. |
393 | fragBuilder->codeAppendf("if (%s != 0) {" , contrast); |
394 | fragBuilder->codeAppendf(" half m = (1 + %s) / (1 - %s);" , contrast, contrast); |
395 | fragBuilder->codeAppendf(" half off = (-0.5 * m + 0.5);" ); |
396 | fragBuilder->codeAppendf(" color = m * color + off;" ); |
397 | fragBuilder->codeAppendf("}" ); |
398 | |
399 | // Clamp. |
400 | fragBuilder->codeAppendf("color = saturate(color);" ); |
401 | |
402 | if (hcfe.linearize()) { |
403 | fragBuilder->codeAppend("color.rgb = sqrt(color.rgb);" ); |
404 | } |
405 | |
406 | // Restore the original alpha and premultiply. |
407 | fragBuilder->codeAppendf("color.a = %s.a;" , args.fInputColor); |
408 | fragBuilder->codeAppendf("color.rgb *= color.a;" ); |
409 | |
410 | // Copy to the output color. |
411 | fragBuilder->codeAppendf("%s = color;" , args.fOutputColor); |
412 | } |
413 | |
414 | std::unique_ptr<GrFragmentProcessor> SkHighContrast_Filter::asFragmentProcessor( |
415 | GrRecordingContext*, const GrColorInfo& csi) const { |
416 | bool linearize = !csi.isLinearlyBlended(); |
417 | return HighContrastFilterEffect::Make(fConfig, linearize); |
418 | } |
419 | #endif |
420 | |