aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/effects/SkHighContrastFilter.cpp
diff options
context:
space:
mode:
authorGravatar Dominic Mazzoni <dmazzoni@chromium.org>2017-02-14 11:15:31 -0800
committerGravatar Skia Commit-Bot <skia-commit-bot@chromium.org>2017-02-16 02:34:44 +0000
commit394d414452a5d654731b0b5a3669f8e2420048b3 (patch)
tree78dca199a9b3fb2abb5b62e0dcc537cb7c257348 /src/effects/SkHighContrastFilter.cpp
parente1caee1ad884def91b8afb50e5672f1f0ee278f1 (diff)
Implement SkHighContrastFilter
This is a color filter to apply several contrast adjustments for users with low vision, including inverting the colors (in either RGB or HSL space), applying gamma correction, converting to grayscale, and increasing the contrast. BUG=skia:6235 CQ_INCLUDE_TRYBOTS=skia.primary:Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-SKNX_NO_SIMD Change-Id: Icb8f3e290932d8bcd9387fb1f39dd20767e15cf6 Reviewed-on: https://skia-review.googlesource.com/7460 Commit-Queue: Mike Klein <mtklein@chromium.org> Reviewed-by: Mike Reed <reed@google.com> Reviewed-by: Mike Klein <mtklein@chromium.org>
Diffstat (limited to 'src/effects/SkHighContrastFilter.cpp')
-rw-r--r--src/effects/SkHighContrastFilter.cpp469
1 files changed, 469 insertions, 0 deletions
diff --git a/src/effects/SkHighContrastFilter.cpp b/src/effects/SkHighContrastFilter.cpp
new file mode 100644
index 0000000000..267a8ef0dc
--- /dev/null
+++ b/src/effects/SkHighContrastFilter.cpp
@@ -0,0 +1,469 @@
+/*
+* Copyright 2017 Google Inc.
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#include "SkHighContrastFilter.h"
+
+#include "SkArenaAlloc.h"
+#include "SkRasterPipeline.h"
+#include "SkReadBuffer.h"
+#include "SkString.h"
+#include "SkWriteBuffer.h"
+
+#if SK_SUPPORT_GPU
+#include "GrContext.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#endif
+
+using InvertStyle = SkHighContrastConfig::InvertStyle;
+
+namespace {
+
+SkScalar Hue2RGB(SkScalar p, SkScalar q, SkScalar t) {
+ if (t < 0) {
+ t += 1;
+ } else if (t > 1) {
+ t -= 1;
+ }
+
+ if (t < 1/6.f) {
+ return p + (q - p) * 6 * t;
+ }
+
+ if (t < 1/2.f) {
+ return q;
+ }
+
+ if (t < 2/3.f) {
+ return p + (q - p) * (2/3.f - t) * 6;
+ }
+
+ return p;
+}
+
+uint8_t SkScalarToUint8Clamp(SkScalar f) {
+ if (f <= 0) {
+ return 0;
+ } else if (f >= 1) {
+ return 255;
+ }
+ return static_cast<unsigned char>(255 * f);
+}
+
+SkScalar IncreaseContrast(SkScalar f, SkScalar contrast) {
+ SkScalar m = (1 + contrast) / (1 - contrast);
+ SkScalar b = (-0.5f * m + 0.5f);
+ return m * f + b;
+}
+
+static SkPMColor ApplyHighContrastFilter(const SkHighContrastConfig& config,
+ SkPMColor pmColor) {
+ SkColor color = SkUnPreMultiply::PMColorToColor(pmColor);
+ SkScalar rf = SkColorGetR(color) / 255.f;
+ SkScalar gf = SkColorGetG(color) / 255.f;
+ SkScalar bf = SkColorGetB(color) / 255.f;
+
+ // Apply a gamma of 2.0 so that the rest of the calculations
+ // happen roughly in linear space.
+ rf *= rf;
+ gf *= gf;
+ bf *= bf;
+
+ // Convert to grayscale using luminance coefficients.
+ if (config.fGrayscale) {
+ SkScalar lum =
+ rf * SK_LUM_COEFF_R + gf * SK_LUM_COEFF_G + bf * SK_LUM_COEFF_B;
+ rf = lum;
+ gf = lum;
+ bf = lum;
+ }
+
+ // Now invert.
+ if (config.fInvertStyle == InvertStyle::kInvertBrightness) {
+ rf = 1 - rf;
+ gf = 1 - gf;
+ bf = 1 - bf;
+ } else if (config.fInvertStyle == InvertStyle::kInvertLightness) {
+ // Convert to HSL
+ SkScalar max = SkTMax(SkTMax(rf, gf), bf);
+ SkScalar min = SkTMin(SkTMin(rf, gf), bf);
+ SkScalar l = (max + min) / 2;
+ SkScalar h, s;
+
+ if (max == min) {
+ h = 0;
+ s = 0;
+ } else {
+ SkScalar d = max - min;
+ s = l > 0.5f ? d / (2 - max - min) : d / (max + min);
+ if (max == rf) {
+ h = (gf - bf) / d + (gf < bf ? 6 : 0);
+ } else if (max == gf) {
+ h = (bf - rf) / d + 2;
+ } else {
+ h = (rf - gf) / d + 4;
+ }
+ h /= 6;
+ }
+
+ // Invert lightness.
+ l = 1 - l;
+
+ // Now convert back to RGB.
+ if (s == 0) {
+ // Grayscale
+ rf = l;
+ gf = l;
+ bf = l;
+ } else {
+ SkScalar q = l < 0.5f ? l * (1 + s) : l + s - l * s;
+ SkScalar p = 2 * l - q;
+ rf = Hue2RGB(p, q, h + 1/3.f);
+ gf = Hue2RGB(p, q, h);
+ bf = Hue2RGB(p, q, h - 1/3.f);
+ }
+ }
+
+ // Increase contrast.
+ if (config.fContrast != 0.0f) {
+ rf = IncreaseContrast(rf, config.fContrast);
+ gf = IncreaseContrast(gf, config.fContrast);
+ bf = IncreaseContrast(bf, config.fContrast);
+ }
+
+ // Convert back from linear to a color space with a gamma of ~2.0.
+ rf = SkScalarSqrt(rf);
+ gf = SkScalarSqrt(gf);
+ bf = SkScalarSqrt(bf);
+
+ return SkPremultiplyARGBInline(SkColorGetA(color),
+ SkScalarToUint8Clamp(rf),
+ SkScalarToUint8Clamp(gf),
+ SkScalarToUint8Clamp(bf));
+}
+
+} // namespace
+
+class SkHighContrast_Filter : public SkColorFilter {
+public:
+ SkHighContrast_Filter(const SkHighContrastConfig& config) {
+ fConfig = config;
+ // Clamp contrast to just inside -1 to 1 to avoid division by zero.
+ fConfig.fContrast = SkScalarPin(fConfig.fContrast,
+ -1.0f + FLT_EPSILON,
+ 1.0f - FLT_EPSILON);
+ }
+
+ virtual ~SkHighContrast_Filter() { }
+
+#if SK_SUPPORT_GPU
+ sk_sp<GrFragmentProcessor> asFragmentProcessor(GrContext*, SkColorSpace*) const override;
+ #endif
+
+ void filterSpan(const SkPMColor src[], int count, SkPMColor dst[]) const
+ override;
+ bool onAppendStages(SkRasterPipeline* p,
+ SkColorSpace* dst,
+ SkArenaAlloc* scratch,
+ bool shaderIsOpaque) const override;
+
+ SK_TO_STRING_OVERRIDE()
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkHighContrast_Filter)
+
+protected:
+ void flatten(SkWriteBuffer&) const override;
+
+private:
+ SkHighContrastConfig fConfig;
+
+ friend class SkHighContrastFilter;
+
+ typedef SkColorFilter INHERITED;
+};
+
+void SkHighContrast_Filter::filterSpan(const SkPMColor src[], int count,
+ SkPMColor dst[]) const {
+ for (int i = 0; i < count; ++i)
+ dst[i] = ApplyHighContrastFilter(fConfig, src[i]);
+}
+
+bool SkHighContrast_Filter::onAppendStages(SkRasterPipeline* p,
+ SkColorSpace* dst,
+ SkArenaAlloc* scratch,
+ bool shaderIsOpaque) const {
+ if (!shaderIsOpaque) {
+ p->append(SkRasterPipeline::unpremul);
+ }
+
+ if (fConfig.fGrayscale) {
+ float r = SK_LUM_COEFF_R;
+ float g = SK_LUM_COEFF_G;
+ float b = SK_LUM_COEFF_B;
+ float* matrix = scratch->makeArray<float>(12);
+ matrix[0] = matrix[1] = matrix[2] = r;
+ matrix[3] = matrix[4] = matrix[5] = g;
+ matrix[6] = matrix[7] = matrix[8] = b;
+ p->append(SkRasterPipeline::matrix_3x4, matrix);
+ }
+
+ if (fConfig.fInvertStyle == InvertStyle::kInvertBrightness) {
+ float* matrix = scratch->makeArray<float>(12);
+ matrix[0] = matrix[4] = matrix[8] = -1;
+ matrix[9] = matrix[10] = matrix[11] = 1;
+ p->append(SkRasterPipeline::matrix_3x4, matrix);
+ } else if (fConfig.fInvertStyle == InvertStyle::kInvertLightness) {
+ p->append(SkRasterPipeline::rgb_to_hsl);
+ float* matrix = scratch->makeArray<float>(12);
+ matrix[0] = matrix[4] = matrix[11] = 1;
+ matrix[8] = -1;
+ p->append(SkRasterPipeline::matrix_3x4, matrix);
+ p->append(SkRasterPipeline::hsl_to_rgb);
+ }
+
+ if (fConfig.fContrast != 0.0) {
+ float* matrix = scratch->makeArray<float>(12);
+ float c = fConfig.fContrast;
+ float m = (1 + c) / (1 - c);
+ float b = (-0.5f * m + 0.5f);
+ matrix[0] = matrix[4] = matrix[8] = m;
+ matrix[9] = matrix[10] = matrix[11] = b;
+ p->append(SkRasterPipeline::matrix_3x4, matrix);
+ }
+
+ p->append(SkRasterPipeline::clamp_0);
+ p->append(SkRasterPipeline::clamp_1);
+
+ if (!shaderIsOpaque) {
+ p->append(SkRasterPipeline::premul);
+ }
+
+ return true;
+}
+
+void SkHighContrast_Filter::flatten(SkWriteBuffer& buffer) const {
+ buffer.writeBool(fConfig.fGrayscale);
+ buffer.writeInt(static_cast<int>(fConfig.fInvertStyle));
+ buffer.writeScalar(fConfig.fContrast);
+}
+
+sk_sp<SkFlattenable> SkHighContrast_Filter::CreateProc(SkReadBuffer& buffer) {
+ SkHighContrastConfig config;
+ config.fGrayscale = buffer.readBool();
+ config.fInvertStyle = static_cast<InvertStyle>(buffer.readInt());
+ config.fContrast = buffer.readScalar();
+ return SkHighContrastFilter::Make(config);
+}
+
+sk_sp<SkColorFilter> SkHighContrastFilter::Make(
+ const SkHighContrastConfig& config) {
+ if (!config.isValid())
+ return nullptr;
+ return sk_make_sp<SkHighContrast_Filter>(config);
+}
+
+#ifndef SK_IGNORE_TO_STRING
+void SkHighContrast_Filter::toString(SkString* str) const {
+ str->append("SkHighContrastColorFilter ");
+}
+#endif
+
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkHighContrastFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkHighContrast_Filter)
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
+
+#if SK_SUPPORT_GPU
+class HighContrastFilterEffect : public GrFragmentProcessor {
+public:
+ static sk_sp<GrFragmentProcessor> Make(const SkHighContrastConfig& config) {
+ return sk_sp<GrFragmentProcessor>(new HighContrastFilterEffect(config));
+ }
+
+ const char* name() const override { return "HighContrastFilter"; }
+
+ const SkHighContrastConfig& config() const { return fConfig; }
+
+private:
+ HighContrastFilterEffect(const SkHighContrastConfig& config)
+ : INHERITED(kNone_OptimizationFlags)
+ , fConfig(config) {
+ this->initClassID<HighContrastFilterEffect>();
+ }
+
+ GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+
+ virtual void onGetGLSLProcessorKey(const GrShaderCaps& caps,
+ GrProcessorKeyBuilder* b) const override;
+
+ bool onIsEqual(const GrFragmentProcessor& other) const override {
+ const HighContrastFilterEffect& that = other.cast<HighContrastFilterEffect>();
+ return fConfig.fGrayscale == that.fConfig.fGrayscale &&
+ fConfig.fInvertStyle == that.fConfig.fInvertStyle &&
+ fConfig.fContrast == that.fConfig.fContrast;
+ }
+
+ SkHighContrastConfig fConfig;
+
+ typedef GrFragmentProcessor INHERITED;
+};
+
+class GLHighContrastFilterEffect : public GrGLSLFragmentProcessor {
+public:
+ static void GenKey(const GrProcessor&, const GrShaderCaps&, GrProcessorKeyBuilder*);
+
+ GLHighContrastFilterEffect(const SkHighContrastConfig& config);
+
+protected:
+ void onSetData(const GrGLSLProgramDataManager&, const GrProcessor&) override;
+ void emitCode(EmitArgs& args) override;
+
+private:
+ UniformHandle fContrastUni;
+ SkHighContrastConfig fConfig;
+
+ typedef GrGLSLFragmentProcessor INHERITED;
+};
+
+GrGLSLFragmentProcessor* HighContrastFilterEffect::onCreateGLSLInstance() const {
+ return new GLHighContrastFilterEffect(fConfig);
+}
+
+void HighContrastFilterEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+ GrProcessorKeyBuilder* b) const {
+ GLHighContrastFilterEffect::GenKey(*this, caps, b);
+}
+
+void GLHighContrastFilterEffect::onSetData(const GrGLSLProgramDataManager& pdm, const GrProcessor& proc) {
+ const HighContrastFilterEffect& hcfe = proc.cast<HighContrastFilterEffect>();
+ pdm.set1f(fContrastUni, hcfe.config().fContrast);
+}
+
+GLHighContrastFilterEffect::GLHighContrastFilterEffect(const SkHighContrastConfig& config)
+ : INHERITED()
+ , fConfig(config) {
+}
+
+void GLHighContrastFilterEffect::GenKey(
+ const GrProcessor& proc, const GrShaderCaps&, GrProcessorKeyBuilder* b) {
+ const HighContrastFilterEffect& hcfe = proc.cast<HighContrastFilterEffect>();
+ b->add32(static_cast<uint32_t>(hcfe.config().fGrayscale));
+ b->add32(static_cast<uint32_t>(hcfe.config().fInvertStyle));
+}
+
+void GLHighContrastFilterEffect::emitCode(EmitArgs& args) {
+ const char* contrast;
+ fContrastUni = args.fUniformHandler->addUniform(kFragment_GrShaderFlag,
+ kFloat_GrSLType, kDefault_GrSLPrecision,
+ "contrast", &contrast);
+
+ if (nullptr == args.fInputColor) {
+ args.fInputColor = "vec4(1)";
+ }
+
+ GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+
+ fragBuilder->codeAppendf("vec4 color = %s;", args.fInputColor);
+
+ // Unpremultiply. The max() is to guard against 0 / 0.
+ fragBuilder->codeAppendf("float nonZeroAlpha = max(color.a, 0.00001);");
+ fragBuilder->codeAppendf("color = vec4(color.rgb / nonZeroAlpha, nonZeroAlpha);");
+
+ // Grayscale.
+ if (fConfig.fGrayscale) {
+ fragBuilder->codeAppendf("float luma = dot(color, vec4(%f, %f, %f, 0));",
+ SK_LUM_COEFF_R, SK_LUM_COEFF_G, SK_LUM_COEFF_B);
+ fragBuilder->codeAppendf("color = vec4(luma, luma, luma, 0);");
+ }
+
+ if (fConfig.fInvertStyle == InvertStyle::kInvertBrightness) {
+ fragBuilder->codeAppendf("color = vec4(1, 1, 1, 1) - color;");
+ }
+
+ if (fConfig.fInvertStyle == InvertStyle::kInvertLightness) {
+ // Convert from RGB to HSL.
+ fragBuilder->codeAppendf("float fmax = max(color.r, max(color.g, color.b));");
+ fragBuilder->codeAppendf("float fmin = min(color.r, min(color.g, color.b));");
+ fragBuilder->codeAppendf("float l = (fmax + fmin) / 2;");
+
+ fragBuilder->codeAppendf("float h;");
+ fragBuilder->codeAppendf("float s;");
+
+ fragBuilder->codeAppendf("if (fmax == fmin) {");
+ fragBuilder->codeAppendf(" h = 0;");
+ fragBuilder->codeAppendf(" s = 0;");
+ fragBuilder->codeAppendf("} else {");
+ fragBuilder->codeAppendf(" float d = fmax - fmin;");
+ fragBuilder->codeAppendf(" s = l > 0.5 ?");
+ fragBuilder->codeAppendf(" d / (2 - fmax - fmin) :");
+ fragBuilder->codeAppendf(" d / (fmax + fmin);");
+ fragBuilder->codeAppendf(" if (fmax == color.r) {");
+ fragBuilder->codeAppendf(" h = (color.g - color.b) / d + ");
+ fragBuilder->codeAppendf(" (color.g < color.b ? 6 : 0);");
+ fragBuilder->codeAppendf(" } else if (fmax == color.g) {");
+ fragBuilder->codeAppendf(" h = (color.b - color.r) / d + 2;");
+ fragBuilder->codeAppendf(" } else {");
+ fragBuilder->codeAppendf(" h = (color.r - color.g) / d + 4;");
+ fragBuilder->codeAppendf(" }");
+ fragBuilder->codeAppendf("}");
+ fragBuilder->codeAppendf("h /= 6;");
+ fragBuilder->codeAppendf("l = 1.0 - l;");
+ // Convert back from HSL to RGB.
+ SkString hue2rgbFuncName;
+ static const GrShaderVar gHue2rgbArgs[] = {
+ GrShaderVar("p", kFloat_GrSLType),
+ GrShaderVar("q", kFloat_GrSLType),
+ GrShaderVar("t", kFloat_GrSLType),
+ };
+ fragBuilder->emitFunction(kFloat_GrSLType,
+ "hue2rgb",
+ SK_ARRAY_COUNT(gHue2rgbArgs),
+ gHue2rgbArgs,
+ "if (t < 0)"
+ " t += 1;"
+ "if (t > 1)"
+ " t -= 1;"
+ "if (t < 1/6.)"
+ " return p + (q - p) * 6 * t;"
+ "if (t < 1/2.)"
+ " return q;"
+ "if (t < 2/3.)"
+ " return p + (q - p) * (2/3. - t) * 6;"
+ "return p;",
+ &hue2rgbFuncName);
+ fragBuilder->codeAppendf("if (s == 0) {");
+ fragBuilder->codeAppendf(" color = vec4(l, l, l, 0);");
+ fragBuilder->codeAppendf("} else {");
+ fragBuilder->codeAppendf(" float q = l < 0.5 ? l * (1 + s) : l + s - l * s;");
+ fragBuilder->codeAppendf(" float p = 2 * l - q;");
+ fragBuilder->codeAppendf(" color.r = %s(p, q, h + 1/3.);", hue2rgbFuncName.c_str());
+ fragBuilder->codeAppendf(" color.g = %s(p, q, h);", hue2rgbFuncName.c_str());
+ fragBuilder->codeAppendf(" color.b = %s(p, q, h - 1/3.);", hue2rgbFuncName.c_str());
+ fragBuilder->codeAppendf("}");
+ }
+
+ // Contrast.
+ fragBuilder->codeAppendf("if (%s != 0) {", contrast);
+ fragBuilder->codeAppendf(" float m = (1 + %s) / (1 - %s);", contrast, contrast);
+ fragBuilder->codeAppendf(" float off = (-0.5 * m + 0.5);");
+ fragBuilder->codeAppendf(" color = m * color + off;");
+ fragBuilder->codeAppendf("}");
+
+ // Clamp.
+ fragBuilder->codeAppendf("color = clamp(color, 0, 1);");
+
+ // Restore the original alpha and premultiply.
+ fragBuilder->codeAppendf("color.a = %s.a;", args.fInputColor);
+ fragBuilder->codeAppendf("color.rgb *= color.a;");
+
+ // Copy to the output color.
+ fragBuilder->codeAppendf("%s = color;", args.fOutputColor);
+}
+
+sk_sp<GrFragmentProcessor> SkHighContrast_Filter::asFragmentProcessor(GrContext*, SkColorSpace*) const {
+ return HighContrastFilterEffect::Make(fConfig);
+}
+#endif