/* * 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(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 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(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(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(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(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(fConfig.fInvertStyle)); buffer.writeScalar(fConfig.fContrast); } sk_sp SkHighContrast_Filter::CreateProc(SkReadBuffer& buffer) { SkHighContrastConfig config; config.fGrayscale = buffer.readBool(); config.fInvertStyle = static_cast(buffer.readInt()); config.fContrast = buffer.readScalar(); return SkHighContrastFilter::Make(config); } sk_sp SkHighContrastFilter::Make( const SkHighContrastConfig& config) { if (!config.isValid()) return nullptr; return sk_make_sp(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 Make(const SkHighContrastConfig& config) { return sk_sp(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(); } 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(); 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(); 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(); b->add32(static_cast(hcfe.config().fGrayscale)); b->add32(static_cast(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 SkHighContrast_Filter::asFragmentProcessor(GrContext*, SkColorSpace*) const { return HighContrastFilterEffect::Make(fConfig); } #endif