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