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
26using InvertStyle = SkHighContrastConfig::InvertStyle;
27
28class SkHighContrast_Filter : public SkColorFilter {
29public:
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
49protected:
50 void flatten(SkWriteBuffer&) const override;
51
52private:
53 SK_FLATTENABLE_HOOKS(SkHighContrast_Filter)
54
55 SkHighContrastConfig fConfig;
56
57 friend class SkHighContrastFilter;
58
59 typedef SkColorFilter INHERITED;
60};
61
62bool 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
135skvm::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
189void 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
195sk_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
204sk_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
212void SkHighContrastFilter::RegisterFlattenables() {
213 SK_REGISTER_FLATTENABLE(SkHighContrast_Filter);
214}
215
216#if SK_SUPPORT_GPU
217class HighContrastFilterEffect : public GrFragmentProcessor {
218public:
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
234private:
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
259class GLHighContrastFilterEffect : public GrGLSLFragmentProcessor {
260public:
261 static void GenKey(const GrProcessor&, const GrShaderCaps&, GrProcessorKeyBuilder*);
262
263protected:
264 void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override;
265 void emitCode(EmitArgs& args) override;
266
267private:
268 UniformHandle fContrastUni;
269
270 typedef GrGLSLFragmentProcessor INHERITED;
271};
272
273GrGLSLFragmentProcessor* HighContrastFilterEffect::onCreateGLSLInstance() const {
274 return new GLHighContrastFilterEffect();
275}
276
277void HighContrastFilterEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
278 GrProcessorKeyBuilder* b) const {
279 GLHighContrastFilterEffect::GenKey(*this, caps, b);
280}
281
282void 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
288void 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
296void 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
414std::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