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 "SkHighContrastFilter.h"
9
10#include "SkArenaAlloc.h"
11#include "SkRasterPipeline.h"
12#include "SkReadBuffer.h"
13#include "SkString.h"
14#include "SkWriteBuffer.h"
15
16#if SK_SUPPORT_GPU
17#include "GrContext.h"
18#include "glsl/GrGLSLFragmentProcessor.h"
19#include "glsl/GrGLSLFragmentShaderBuilder.h"
20#endif
21
22using InvertStyle = SkHighContrastConfig::InvertStyle;
23
24namespace {
25
26SkScalar Hue2RGB(SkScalar p, SkScalar q, SkScalar t) {
27    if (t < 0) {
28        t += 1;
29    } else if (t > 1) {
30        t -= 1;
31    }
32
33    if (t < 1/6.f) {
34        return p + (q - p) * 6 * t;
35    }
36
37    if (t < 1/2.f) {
38        return q;
39    }
40
41    if (t < 2/3.f) {
42        return p + (q - p) * (2/3.f - t) * 6;
43    }
44
45    return p;
46}
47
48uint8_t SkScalarToUint8Clamp(SkScalar f) {
49    if (f <= 0) {
50        return 0;
51    } else if (f >= 1) {
52        return 255;
53    }
54    return static_cast<unsigned char>(255 * f);
55}
56
57SkScalar IncreaseContrast(SkScalar f, SkScalar contrast) {
58    SkScalar m = (1 + contrast) / (1 - contrast);
59    SkScalar b = (-0.5f * m + 0.5f);
60    return m * f + b;
61}
62
63static SkPMColor ApplyHighContrastFilter(const SkHighContrastConfig& config,
64                                         SkPMColor pmColor) {
65    SkColor color = SkUnPreMultiply::PMColorToColor(pmColor);
66    SkScalar rf = SkColorGetR(color) / 255.f;
67    SkScalar gf = SkColorGetG(color) / 255.f;
68    SkScalar bf = SkColorGetB(color) / 255.f;
69
70    // Apply a gamma of 2.0 so that the rest of the calculations
71    // happen roughly in linear space.
72    rf *= rf;
73    gf *= gf;
74    bf *= bf;
75
76    // Convert to grayscale using luminance coefficients.
77    if (config.fGrayscale) {
78        SkScalar lum =
79            rf * SK_LUM_COEFF_R + gf * SK_LUM_COEFF_G + bf * SK_LUM_COEFF_B;
80        rf = lum;
81        gf = lum;
82        bf = lum;
83    }
84
85    // Now invert.
86    if (config.fInvertStyle == InvertStyle::kInvertBrightness) {
87        rf = 1 - rf;
88        gf = 1 - gf;
89        bf = 1 - bf;
90    } else if (config.fInvertStyle == InvertStyle::kInvertLightness) {
91        // Convert to HSL
92        SkScalar max = SkTMax(SkTMax(rf, gf), bf);
93        SkScalar min = SkTMin(SkTMin(rf, gf), bf);
94        SkScalar l = (max + min) / 2;
95        SkScalar h, s;
96
97        if (max == min) {
98            h = 0;
99            s = 0;
100        } else {
101            SkScalar d = max - min;
102            s = l > 0.5f ? d / (2 - max - min) : d / (max + min);
103            if (max == rf) {
104                h = (gf - bf) / d + (gf < bf ? 6 : 0);
105            } else if (max == gf) {
106                h = (bf - rf) / d + 2;
107            } else {
108                h = (rf - gf) / d + 4;
109            }
110            h /= 6;
111        }
112
113        // Invert lightness.
114        l = 1 - l;
115
116        // Now convert back to RGB.
117        if (s == 0) {
118            // Grayscale
119            rf = l;
120            gf = l;
121            bf = l;
122        } else {
123            SkScalar q = l < 0.5f ? l * (1 + s) : l + s - l * s;
124            SkScalar p = 2 * l - q;
125            rf = Hue2RGB(p, q, h + 1/3.f);
126            gf = Hue2RGB(p, q, h);
127            bf = Hue2RGB(p, q, h - 1/3.f);
128        }
129    }
130
131    // Increase contrast.
132    if (config.fContrast != 0.0f) {
133        rf = IncreaseContrast(rf, config.fContrast);
134        gf = IncreaseContrast(gf, config.fContrast);
135        bf = IncreaseContrast(bf, config.fContrast);
136    }
137
138    // Convert back from linear to a color space with a gamma of ~2.0.
139    rf = SkScalarSqrt(rf);
140    gf = SkScalarSqrt(gf);
141    bf = SkScalarSqrt(bf);
142
143    return SkPremultiplyARGBInline(SkColorGetA(color),
144                                   SkScalarToUint8Clamp(rf),
145                                   SkScalarToUint8Clamp(gf),
146                                   SkScalarToUint8Clamp(bf));
147}
148
149}  // namespace
150
151class SkHighContrast_Filter : public SkColorFilter {
152public:
153    SkHighContrast_Filter(const SkHighContrastConfig& config) {
154        fConfig = config;
155        // Clamp contrast to just inside -1 to 1 to avoid division by zero.
156        fConfig.fContrast = SkScalarPin(fConfig.fContrast,
157                                        -1.0f + FLT_EPSILON,
158                                        1.0f - FLT_EPSILON);
159    }
160
161    ~SkHighContrast_Filter() override {}
162
163#if SK_SUPPORT_GPU
164    sk_sp<GrFragmentProcessor> asFragmentProcessor(GrContext*, SkColorSpace*) const override;
165 #endif
166
167    void filterSpan(const SkPMColor src[], int count, SkPMColor dst[]) const
168          override;
169    bool onAppendStages(SkRasterPipeline* p,
170                        SkColorSpace* dst,
171                        SkArenaAlloc* scratch,
172                        bool shaderIsOpaque) const override;
173
174    SK_TO_STRING_OVERRIDE()
175
176    SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkHighContrast_Filter)
177
178protected:
179    void flatten(SkWriteBuffer&) const override;
180
181private:
182    SkHighContrastConfig fConfig;
183
184    friend class SkHighContrastFilter;
185
186    typedef SkColorFilter INHERITED;
187};
188
189void SkHighContrast_Filter::filterSpan(const SkPMColor src[], int count,
190                                       SkPMColor dst[]) const {
191    for (int i = 0; i < count; ++i)
192        dst[i] = ApplyHighContrastFilter(fConfig, src[i]);
193}
194
195bool SkHighContrast_Filter::onAppendStages(SkRasterPipeline* p,
196                                           SkColorSpace* dst,
197                                           SkArenaAlloc* scratch,
198                                           bool shaderIsOpaque) const {
199    if (!shaderIsOpaque) {
200        p->append(SkRasterPipeline::unpremul);
201    }
202
203    if (fConfig.fGrayscale) {
204        float r = SK_LUM_COEFF_R;
205        float g = SK_LUM_COEFF_G;
206        float b = SK_LUM_COEFF_B;
207        float* matrix = scratch->makeArray<float>(12);
208        matrix[0] = matrix[1] = matrix[2] = r;
209        matrix[3] = matrix[4] = matrix[5] = g;
210        matrix[6] = matrix[7] = matrix[8] = b;
211        p->append(SkRasterPipeline::matrix_3x4, matrix);
212    }
213
214    if (fConfig.fInvertStyle == InvertStyle::kInvertBrightness) {
215        float* matrix = scratch->makeArray<float>(12);
216        matrix[0] = matrix[4] = matrix[8] = -1;
217        matrix[9] = matrix[10] = matrix[11] = 1;
218        p->append(SkRasterPipeline::matrix_3x4, matrix);
219    } else if (fConfig.fInvertStyle == InvertStyle::kInvertLightness) {
220        p->append(SkRasterPipeline::rgb_to_hsl);
221        float* matrix = scratch->makeArray<float>(12);
222        matrix[0] = matrix[4] = matrix[11] = 1;
223        matrix[8] = -1;
224        p->append(SkRasterPipeline::matrix_3x4, matrix);
225        p->append(SkRasterPipeline::hsl_to_rgb);
226    }
227
228    if (fConfig.fContrast != 0.0) {
229        float* matrix = scratch->makeArray<float>(12);
230        float c = fConfig.fContrast;
231        float m = (1 + c) / (1 - c);
232        float b = (-0.5f * m + 0.5f);
233        matrix[0] = matrix[4] = matrix[8] = m;
234        matrix[9] = matrix[10] = matrix[11] = b;
235        p->append(SkRasterPipeline::matrix_3x4, matrix);
236    }
237
238    p->append(SkRasterPipeline::clamp_0);
239    p->append(SkRasterPipeline::clamp_1);
240
241    if (!shaderIsOpaque) {
242        p->append(SkRasterPipeline::premul);
243    }
244
245    return true;
246}
247
248void SkHighContrast_Filter::flatten(SkWriteBuffer& buffer) const {
249    buffer.writeBool(fConfig.fGrayscale);
250    buffer.writeInt(static_cast<int>(fConfig.fInvertStyle));
251    buffer.writeScalar(fConfig.fContrast);
252}
253
254sk_sp<SkFlattenable> SkHighContrast_Filter::CreateProc(SkReadBuffer& buffer) {
255    SkHighContrastConfig config;
256    config.fGrayscale = buffer.readBool();
257    config.fInvertStyle = static_cast<InvertStyle>(buffer.readInt());
258    config.fContrast = buffer.readScalar();
259    return SkHighContrastFilter::Make(config);
260}
261
262sk_sp<SkColorFilter> SkHighContrastFilter::Make(
263    const SkHighContrastConfig& config) {
264    if (!config.isValid())
265        return nullptr;
266    return sk_make_sp<SkHighContrast_Filter>(config);
267}
268
269#ifndef SK_IGNORE_TO_STRING
270void SkHighContrast_Filter::toString(SkString* str) const {
271    str->append("SkHighContrastColorFilter ");
272}
273#endif
274
275SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkHighContrastFilter)
276    SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkHighContrast_Filter)
277SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
278
279#if SK_SUPPORT_GPU
280class HighContrastFilterEffect : public GrFragmentProcessor {
281public:
282    static sk_sp<GrFragmentProcessor> Make(const SkHighContrastConfig& config) {
283        return sk_sp<GrFragmentProcessor>(new HighContrastFilterEffect(config));
284    }
285
286    const char* name() const override { return "HighContrastFilter"; }
287
288    const SkHighContrastConfig& config() const { return fConfig; }
289
290private:
291    HighContrastFilterEffect(const SkHighContrastConfig& config)
292        : INHERITED(kNone_OptimizationFlags)
293        , fConfig(config) {
294        this->initClassID<HighContrastFilterEffect>();
295    }
296
297    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
298
299    virtual void onGetGLSLProcessorKey(const GrShaderCaps& caps,
300                                       GrProcessorKeyBuilder* b) const override;
301
302    bool onIsEqual(const GrFragmentProcessor& other) const override {
303        const HighContrastFilterEffect& that = other.cast<HighContrastFilterEffect>();
304        return fConfig.fGrayscale == that.fConfig.fGrayscale &&
305            fConfig.fInvertStyle == that.fConfig.fInvertStyle &&
306            fConfig.fContrast == that.fConfig.fContrast;
307    }
308
309    SkHighContrastConfig fConfig;
310
311    typedef GrFragmentProcessor INHERITED;
312};
313
314class GLHighContrastFilterEffect : public GrGLSLFragmentProcessor {
315public:
316    static void GenKey(const GrProcessor&, const GrShaderCaps&, GrProcessorKeyBuilder*);
317
318    GLHighContrastFilterEffect(const SkHighContrastConfig& config);
319
320protected:
321    void onSetData(const GrGLSLProgramDataManager&, const GrProcessor&) override;
322    void emitCode(EmitArgs& args) override;
323
324private:
325    UniformHandle fContrastUni;
326    SkHighContrastConfig fConfig;
327
328    typedef GrGLSLFragmentProcessor INHERITED;
329};
330
331GrGLSLFragmentProcessor* HighContrastFilterEffect::onCreateGLSLInstance() const {
332    return new GLHighContrastFilterEffect(fConfig);
333}
334
335void HighContrastFilterEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
336                                                     GrProcessorKeyBuilder* b) const {
337    GLHighContrastFilterEffect::GenKey(*this, caps, b);
338}
339
340void GLHighContrastFilterEffect::onSetData(const GrGLSLProgramDataManager& pdm, const GrProcessor& proc) {
341    const HighContrastFilterEffect& hcfe = proc.cast<HighContrastFilterEffect>();
342    pdm.set1f(fContrastUni, hcfe.config().fContrast);
343}
344
345GLHighContrastFilterEffect::GLHighContrastFilterEffect(const SkHighContrastConfig& config)
346    : INHERITED()
347    , fConfig(config) {
348}
349
350void GLHighContrastFilterEffect::GenKey(
351    const GrProcessor& proc, const GrShaderCaps&, GrProcessorKeyBuilder* b) {
352  const HighContrastFilterEffect& hcfe = proc.cast<HighContrastFilterEffect>();
353  b->add32(static_cast<uint32_t>(hcfe.config().fGrayscale));
354  b->add32(static_cast<uint32_t>(hcfe.config().fInvertStyle));
355}
356
357void GLHighContrastFilterEffect::emitCode(EmitArgs& args) {
358    const char* contrast;
359    fContrastUni = args.fUniformHandler->addUniform(kFragment_GrShaderFlag,
360                                                    kFloat_GrSLType, kDefault_GrSLPrecision,
361                                                    "contrast", &contrast);
362
363    if (nullptr == args.fInputColor) {
364        args.fInputColor = "vec4(1)";
365    }
366
367    GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
368
369    fragBuilder->codeAppendf("vec4 color = %s;", args.fInputColor);
370
371    // Unpremultiply. The max() is to guard against 0 / 0.
372    fragBuilder->codeAppendf("float nonZeroAlpha = max(color.a, 0.00001);");
373    fragBuilder->codeAppendf("color = vec4(color.rgb / nonZeroAlpha, nonZeroAlpha);");
374
375    // Grayscale.
376    if (fConfig.fGrayscale) {
377        fragBuilder->codeAppendf("float luma = dot(color, vec4(%f, %f, %f, 0));",
378                                 SK_LUM_COEFF_R, SK_LUM_COEFF_G, SK_LUM_COEFF_B);
379        fragBuilder->codeAppendf("color = vec4(luma, luma, luma, 0);");
380    }
381
382    if (fConfig.fInvertStyle == InvertStyle::kInvertBrightness) {
383        fragBuilder->codeAppendf("color = vec4(1, 1, 1, 1) - color;");
384    }
385
386    if (fConfig.fInvertStyle == InvertStyle::kInvertLightness) {
387        // Convert from RGB to HSL.
388        fragBuilder->codeAppendf("float fmax = max(color.r, max(color.g, color.b));");
389        fragBuilder->codeAppendf("float fmin = min(color.r, min(color.g, color.b));");
390        fragBuilder->codeAppendf("float l = (fmax + fmin) / 2;");
391
392        fragBuilder->codeAppendf("float h;");
393        fragBuilder->codeAppendf("float s;");
394
395        fragBuilder->codeAppendf("if (fmax == fmin) {");
396        fragBuilder->codeAppendf("  h = 0;");
397        fragBuilder->codeAppendf("  s = 0;");
398        fragBuilder->codeAppendf("} else {");
399        fragBuilder->codeAppendf("  float d = fmax - fmin;");
400        fragBuilder->codeAppendf("  s = l > 0.5 ?");
401        fragBuilder->codeAppendf("      d / (2 - fmax - fmin) :");
402        fragBuilder->codeAppendf("      d / (fmax + fmin);");
403        fragBuilder->codeAppendf("  if (fmax == color.r) {");
404        fragBuilder->codeAppendf("    h = (color.g - color.b) / d + ");
405        fragBuilder->codeAppendf("        (color.g < color.b ? 6 : 0);");
406        fragBuilder->codeAppendf("  } else if (fmax == color.g) {");
407        fragBuilder->codeAppendf("    h = (color.b - color.r) / d + 2;");
408        fragBuilder->codeAppendf("  } else {");
409        fragBuilder->codeAppendf("    h = (color.r - color.g) / d + 4;");
410        fragBuilder->codeAppendf("  }");
411        fragBuilder->codeAppendf("}");
412        fragBuilder->codeAppendf("h /= 6;");
413        fragBuilder->codeAppendf("l = 1.0 - l;");
414        // Convert back from HSL to RGB.
415        SkString hue2rgbFuncName;
416        static const GrShaderVar gHue2rgbArgs[] = {
417            GrShaderVar("p", kFloat_GrSLType),
418            GrShaderVar("q", kFloat_GrSLType),
419            GrShaderVar("t", kFloat_GrSLType),
420        };
421        fragBuilder->emitFunction(kFloat_GrSLType,
422                                  "hue2rgb",
423                                  SK_ARRAY_COUNT(gHue2rgbArgs),
424                                  gHue2rgbArgs,
425                                  "if (t < 0)"
426                                  "  t += 1;"
427                                  "if (t > 1)"
428                                  "  t -= 1;"
429                                  "if (t < 1/6.)"
430                                  "  return p + (q - p) * 6 * t;"
431                                  "if (t < 1/2.)"
432                                  "  return q;"
433                                  "if (t < 2/3.)"
434                                  "  return p + (q - p) * (2/3. - t) * 6;"
435                                  "return p;",
436                                  &hue2rgbFuncName);
437        fragBuilder->codeAppendf("if (s == 0) {");
438        fragBuilder->codeAppendf("  color = vec4(l, l, l, 0);");
439        fragBuilder->codeAppendf("} else {");
440        fragBuilder->codeAppendf("  float q = l < 0.5 ? l * (1 + s) : l + s - l * s;");
441        fragBuilder->codeAppendf("  float p = 2 * l - q;");
442        fragBuilder->codeAppendf("  color.r = %s(p, q, h + 1/3.);", hue2rgbFuncName.c_str());
443        fragBuilder->codeAppendf("  color.g = %s(p, q, h);", hue2rgbFuncName.c_str());
444        fragBuilder->codeAppendf("  color.b = %s(p, q, h - 1/3.);", hue2rgbFuncName.c_str());
445        fragBuilder->codeAppendf("}");
446    }
447
448    // Contrast.
449    fragBuilder->codeAppendf("if (%s != 0) {", contrast);
450    fragBuilder->codeAppendf("  float m = (1 + %s) / (1 - %s);", contrast, contrast);
451    fragBuilder->codeAppendf("  float off = (-0.5 * m + 0.5);");
452    fragBuilder->codeAppendf("  color = m * color + off;");
453    fragBuilder->codeAppendf("}");
454
455    // Clamp.
456    fragBuilder->codeAppendf("color = clamp(color, 0, 1);");
457
458    // Restore the original alpha and premultiply.
459    fragBuilder->codeAppendf("color.a = %s.a;", args.fInputColor);
460    fragBuilder->codeAppendf("color.rgb *= color.a;");
461
462    // Copy to the output color.
463    fragBuilder->codeAppendf("%s = color;", args.fOutputColor);
464}
465
466sk_sp<GrFragmentProcessor> SkHighContrast_Filter::asFragmentProcessor(GrContext*, SkColorSpace*) const {
467    return HighContrastFilterEffect::Make(fConfig);
468}
469#endif
470