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