diff options
author | Dominic Mazzoni <dmazzoni@chromium.org> | 2017-02-14 11:15:31 -0800 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2017-02-16 02:34:44 +0000 |
commit | 394d414452a5d654731b0b5a3669f8e2420048b3 (patch) | |
tree | 78dca199a9b3fb2abb5b62e0dcc537cb7c257348 /src/effects | |
parent | e1caee1ad884def91b8afb50e5672f1f0ee278f1 (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')
-rw-r--r-- | src/effects/SkHighContrastFilter.cpp | 469 |
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 |