diff options
-rw-r--r-- | gm/highcontrastfilter.cpp | 143 | ||||
-rw-r--r-- | gn/effects.gni | 1 | ||||
-rw-r--r-- | gn/gm.gni | 1 | ||||
-rw-r--r-- | gn/tests.gni | 1 | ||||
-rw-r--r-- | include/effects/SkHighContrastFilter.h | 81 | ||||
-rw-r--r-- | src/core/SkRasterPipeline.h | 4 | ||||
-rw-r--r-- | src/effects/SkHighContrastFilter.cpp | 469 | ||||
-rw-r--r-- | src/opts/SkRasterPipeline_opts.h | 42 | ||||
-rw-r--r-- | src/ports/SkGlobalInitialization_default.cpp | 2 | ||||
-rw-r--r-- | tests/HighContrastFilterTest.cpp | 93 |
10 files changed, 836 insertions, 1 deletions
diff --git a/gm/highcontrastfilter.cpp b/gm/highcontrastfilter.cpp new file mode 100644 index 0000000000..68a5449293 --- /dev/null +++ b/gm/highcontrastfilter.cpp @@ -0,0 +1,143 @@ +/* + * 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 "gm.h" +#include "SkCanvas.h" +#include "SkGradientShader.h" +#include "SkHighContrastFilter.h" + +using InvertStyle = SkHighContrastConfig::InvertStyle; + +static SkScalar kSize = 200; +static SkColor kColor1 = SkColorSetARGB(0xff, 0xff, 0xff, 0); +static SkColor kColor2 = SkColorSetARGB(0xff, 0x82, 0xff, 0); + +static void draw_label(SkCanvas* canvas, const SkHighContrastConfig& config) { + char labelBuffer[256]; + const char* invertStr = + (config.fInvertStyle == InvertStyle::kInvertBrightness ? + "InvBrightness" : + (config.fInvertStyle == InvertStyle::kInvertLightness ? + "InvLightness" : "NoInvert")); + + snprintf(labelBuffer, sizeof(labelBuffer), "%s%s contrast=%.1f", + config.fGrayscale ? "Gray " : "", + invertStr, + config.fContrast); + + SkPaint paint; + sk_tool_utils::set_portable_typeface(&paint); + paint.setTextSize(0.05f); + size_t len = strlen(labelBuffer); + + SkScalar width = paint.measureText(labelBuffer, len); + canvas->drawText(labelBuffer, len, 0.5f - width / 2, 0.16f, paint); +} + +static void draw_scene(SkCanvas* canvas, const SkHighContrastConfig& config) { + SkRect bounds = SkRect::MakeLTRB(0.0f, 0.0f, 1.0f, 1.0f); + SkPaint xferPaint; + xferPaint.setColorFilter(SkHighContrastFilter::Make(config)); + canvas->saveLayer(&bounds, &xferPaint); + + SkPaint paint; + bounds = SkRect::MakeLTRB(0.1f, 0.2f, 0.9f, 0.4f); + paint.setARGB(0xff, 0x66, 0x11, 0x11); + canvas->drawRect(bounds, paint); + + paint.setARGB(0xff, 0xbb, 0x77, 0x77); + paint.setTextSize(0.15f); + canvas->drawText("A", 1, 0.15f, 0.35f, paint); + + bounds = SkRect::MakeLTRB(0.1f, 0.8f, 0.9f, 1.0f); + paint.setARGB(0xff, 0xcc, 0xcc, 0xff); + canvas->drawRect(bounds, paint); + + paint.setARGB(0xff, 0x88, 0x88, 0xbb); + paint.setTextSize(0.15f); + canvas->drawText("Z", 1, 0.75f, 0.95f, paint); + + bounds = SkRect::MakeLTRB(0.1f, 0.4f, 0.9f, 0.6f); + SkPoint pts[] = { { 0, 0 }, { 1, 0 } }; + SkColor colors[] = { SK_ColorWHITE, SK_ColorBLACK }; + SkScalar pos[] = { 0.2f, 0.8f }; + paint.setShader(SkGradientShader::MakeLinear( + pts, colors, pos, + SK_ARRAY_COUNT(colors), SkShader::kClamp_TileMode)); + canvas->drawRect(bounds, paint); + + bounds = SkRect::MakeLTRB(0.1f, 0.6f, 0.9f, 0.8f); + SkColor colors2[] = { SK_ColorGREEN, SK_ColorWHITE }; + paint.setShader(SkGradientShader::MakeLinear( + pts, colors2, pos, + SK_ARRAY_COUNT(colors2), SkShader::kClamp_TileMode)); + canvas->drawRect(bounds, paint); + + canvas->restore(); +} + +class HighContrastFilterGM : public skiagm::GM { +public: + HighContrastFilterGM() { + SkColor g1Colors[] = { kColor1, SkColorSetA(kColor1, 0x20) }; + SkColor g2Colors[] = { kColor2, SkColorSetA(kColor2, 0x20) }; + SkPoint g1Points[] = { { 0, 0 }, { 0, 100 } }; + SkPoint g2Points[] = { { 0, 0 }, { kSize, 0 } }; + SkScalar pos[] = { 0.2f, 1.0f }; + + SkHighContrastConfig fConfig; + fFilter = SkHighContrastFilter::Make(fConfig); + fGr1 = SkGradientShader::MakeLinear( + g1Points, g1Colors, pos, SK_ARRAY_COUNT(g1Colors), + SkShader::kClamp_TileMode); + fGr2 = SkGradientShader::MakeLinear( + g2Points, g2Colors, pos, SK_ARRAY_COUNT(g2Colors), + SkShader::kClamp_TileMode); + } + +protected: + + SkString onShortName() override { + return SkString("highcontrastfilter"); + } + + SkISize onISize() override { + return SkISize::Make(600, 420); + } + + void onDraw(SkCanvas* canvas) override { + SkHighContrastConfig configs[] = { + { false, InvertStyle::kNoInvert, 0.0f }, + { false, InvertStyle::kInvertBrightness, 0.0f }, + { false, InvertStyle::kInvertLightness, 0.0f }, + { false, InvertStyle::kInvertLightness, 0.2f }, + { true, InvertStyle::kNoInvert, 0.0f }, + { true, InvertStyle::kInvertBrightness, 0.0f }, + { true, InvertStyle::kInvertLightness, 0.0f }, + { true, InvertStyle::kInvertLightness, 0.2f }, + }; + + for (size_t i = 0; i < SK_ARRAY_COUNT(configs); ++i) { + SkScalar x = kSize * (i % 4); + SkScalar y = kSize * (i / 4); + canvas->save(); + canvas->translate(x, y); + canvas->scale(kSize, kSize); + draw_scene(canvas, configs[i]); + draw_label(canvas, configs[i]); + canvas->restore(); + } + } + +private: + sk_sp<SkColorFilter> fFilter; + sk_sp<SkShader> fGr1, fGr2; + + typedef skiagm::GM INHERITED; +}; + +DEF_GM(return new HighContrastFilterGM;) diff --git a/gn/effects.gni b/gn/effects.gni index d79481fb90..3ae4ebec3e 100644 --- a/gn/effects.gni +++ b/gn/effects.gni @@ -40,6 +40,7 @@ skia_effects_sources = [ "$_src/effects/SkEmbossMaskFilter.cpp", "$_src/effects/SkImageSource.cpp", "$_src/effects/SkGaussianEdgeShader.cpp", + "$_src/effects/SkHighContrastFilter.cpp", "$_src/effects/SkLayerDrawLooper.cpp", "$_src/effects/SkLayerRasterizer.cpp", "$_src/effects/SkLightingImageFilter.cpp", @@ -147,6 +147,7 @@ gm_sources = [ "$_gm/hairlines.cpp", "$_gm/hairmodes.cpp", "$_gm/hardstop_gradients.cpp", + "$_gm/highcontrastfilter.cpp", "$_gm/hittestpath.cpp", "$_gm/image.cpp", "$_gm/image_pict.cpp", diff --git a/gn/tests.gni b/gn/tests.gni index 3a257602cc..6646f3df5a 100644 --- a/gn/tests.gni +++ b/gn/tests.gni @@ -97,6 +97,7 @@ tests_sources = [ "$_tests/GrTextureStripAtlasTest.cpp", "$_tests/GrTRecorderTest.cpp", "$_tests/HashTest.cpp", + "$_tests/HighContrastFilterTest.cpp", "$_tests/HSVRoundTripTest.cpp", "$_tests/image-bitmap.cpp", "$_tests/ICCTest.cpp", diff --git a/include/effects/SkHighContrastFilter.h b/include/effects/SkHighContrastFilter.h new file mode 100644 index 0000000000..2ac37e7975 --- /dev/null +++ b/include/effects/SkHighContrastFilter.h @@ -0,0 +1,81 @@ +/* +* Copyright 2017 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#ifndef SkHighContrastFilter_DEFINED +#define SkHighContrastFilter_DEFINED + +#include "SkColorFilter.h" +#include "SkPaint.h" + +/** + * Configuration struct for SkHighContrastFilter. + * + * Provides transformations to improve contrast for users with low vision. + */ +struct SkHighContrastConfig { + enum class InvertStyle { + kNoInvert, + kInvertBrightness, + kInvertLightness, + }; + + SkHighContrastConfig() { + fGrayscale = false; + fInvertStyle = InvertStyle::kNoInvert; + fContrast = 0.0f; + } + + SkHighContrastConfig(bool grayscale, + InvertStyle invertStyle, + SkScalar contrast) + : fGrayscale(grayscale), + fInvertStyle(invertStyle), + fContrast(contrast) {} + + // Returns true if all of the fields are set within the valid range. + bool isValid() const { + return fInvertStyle >= InvertStyle::kNoInvert && + fInvertStyle <= InvertStyle::kInvertLightness && + fContrast >= -1.0 && + fContrast <= 1.0; + } + + // If true, the color will be converted to grayscale. + bool fGrayscale; + + // Whether to invert brightness, lightness, or neither. + InvertStyle fInvertStyle; + + // After grayscale and inverting, the contrast can be adjusted linearly. + // The valid range is -1.0 through 1.0, where 0.0 is no adjustment. + SkScalar fContrast; +}; + +/** + * Color filter that provides transformations to improve contrast + * for users with low vision. + * + * Applies the following transformations in this order. Each of these + * can be configured using SkHighContrastConfig. + * + * - Conversion to grayscale + * - Color inversion (either in RGB or HSL space) + * - Increasing the resulting contrast. + * + * Calling SkHighContrastFilter::Make will return nullptr if the config is + * not valid, e.g. if you try to call it with a contrast outside the range of + * -1.0 to 1.0. + */ +class SK_API SkHighContrastFilter { +public: + // Returns the filter, or nullptr if the config is invalid. + static sk_sp<SkColorFilter> Make(const SkHighContrastConfig& config); + + SK_DECLARE_FLATTENABLE_REGISTRAR_GROUP() +}; + +#endif diff --git a/src/core/SkRasterPipeline.h b/src/core/SkRasterPipeline.h index c5661a2e1d..34b0744a48 100644 --- a/src/core/SkRasterPipeline.h +++ b/src/core/SkRasterPipeline.h @@ -98,7 +98,9 @@ M(save_xy) M(accumulate) \ M(linear_gradient_2stops) \ M(byte_tables) \ - M(shader_adapter) + M(shader_adapter) \ + M(rgb_to_hsl) \ + M(hsl_to_rgb) class SkRasterPipeline { public: 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 diff --git a/src/opts/SkRasterPipeline_opts.h b/src/opts/SkRasterPipeline_opts.h index a81516877a..fb0271b822 100644 --- a/src/opts/SkRasterPipeline_opts.h +++ b/src/opts/SkRasterPipeline_opts.h @@ -776,6 +776,48 @@ STAGE(luminance_to_alpha) { r = g = b = 0; } +STAGE(rgb_to_hsl) { + auto max = SkNf::Max(SkNf::Max(r, g), b); + auto min = SkNf::Min(SkNf::Min(r, g), b); + auto l = 0.5f * (max + min); + + auto d = max - min; + auto d_inv = 1.0f/d; + auto s = (max == min).thenElse(0.0f, + d/(l > 0.5f).thenElse(2.0f - max - min, max + min)); + SkNf h = (max != r).thenElse(0.0f, + (g - b)*d_inv + (g < b).thenElse(6.0f, 0.0f)); + h = (max == g).thenElse((b - r)*d_inv + 2.0f, h); + h = (max == b).thenElse((r - g)*d_inv + 4.0f, h); + h *= (1/6.0f); + + h = (max == min).thenElse(0.0f, h); + + r = h; + g = s; + b = l; +} + +STAGE(hsl_to_rgb) { + auto h = r; + auto s = g; + auto l = b; + auto q = (l < 0.5f).thenElse(l*(1.0f + s), l + s - l*s); + auto p = 2.0f*l - q; + + auto hue_to_rgb = [](const SkNf& p, const SkNf& q, const SkNf& t) { + auto t2 = (t < 0.0f).thenElse(t + 1.0f, (t > 1.0f).thenElse(t - 1.0f, t)); + return (t2 < (1/6.0f)).thenElse( + p + (q - p)*6.0f*t, (t2 < (3/6.0f)).thenElse( + q, (t2 < (4/6.0f)).thenElse( + p + (q - p)*((4/6.0f) - t2)*6.0f, p))); + }; + + r = (s == 0.f).thenElse(l, hue_to_rgb(p, q, h + (1/3.0f))); + g = (s == 0.f).thenElse(l, hue_to_rgb(p, q, h)); + b = (s == 0.f).thenElse(l, hue_to_rgb(p, q, h - (1/3.0f))); +} + STAGE_CTX(matrix_2x3, const float*) { auto m = ctx; diff --git a/src/ports/SkGlobalInitialization_default.cpp b/src/ports/SkGlobalInitialization_default.cpp index dd6ef873b3..60213bdb3a 100644 --- a/src/ports/SkGlobalInitialization_default.cpp +++ b/src/ports/SkGlobalInitialization_default.cpp @@ -26,6 +26,7 @@ #include "SkGaussianEdgeShader.h" #include "SkRRectsGaussianEdgeMaskFilter.h" #include "SkGradientShader.h" +#include "SkHighContrastFilter.h" #include "SkImageSource.h" #include "SkLayerDrawLooper.h" #include "SkLayerRasterizer.h" @@ -86,6 +87,7 @@ void SkFlattenable::PrivateInitializer::InitEffects() { SkArithmeticMode::InitializeFlattenables(); SkTableColorFilter::InitializeFlattenables(); SkOverdrawColorFilter::InitializeFlattenables(); + SkHighContrastFilter::InitializeFlattenables(); // Shader SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkPerlinNoiseShader) diff --git a/tests/HighContrastFilterTest.cpp b/tests/HighContrastFilterTest.cpp new file mode 100644 index 0000000000..35b0c9febd --- /dev/null +++ b/tests/HighContrastFilterTest.cpp @@ -0,0 +1,93 @@ +/* + * 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 "SkCanvas.h" +#include "SkHighContrastFilter.h" +#include "Test.h" + +DEF_TEST(HighContrastFilter_FilterImage, reporter) { + SkHighContrastConfig config; + config.fInvertStyle = SkHighContrastConfig::InvertStyle::kInvertLightness; + + int w = 10, h = 10; + SkBitmap filterResult, paintResult; + + filterResult.allocN32Pixels(w, h); + SkCanvas canvasFilter(filterResult); + canvasFilter.clear(0x00000000); + + paintResult.allocN32Pixels(w, h); + SkCanvas canvasPaint(paintResult); + canvasPaint.clear(0x00000000); + + SkPaint paint; + paint.setColor(SK_ColorBLUE); + SkRect r = SkRect::MakeLTRB(SkIntToScalar(2), SkIntToScalar(2), + SkIntToScalar(8), SkIntToScalar(8)); + canvasPaint.drawRect(r, paint); + + paint.setColorFilter(SkHighContrastFilter::Make(config)); + canvasFilter.drawRect(r, paint); + + paintResult.lockPixels(); + filterResult.lockPixels(); + for (int y = r.top(); y < r.bottom(); ++y) { + for (int x = r.left(); x < r.right(); ++x) { + SkColor paintColor = paintResult.getColor(x, y); + SkColor filterColor = filterResult.getColor(x, y); + REPORTER_ASSERT( + reporter, filterColor == + paint.getColorFilter()->filterColor(paintColor)); + } + } + paintResult.unlockPixels(); + filterResult.unlockPixels(); +} + +DEF_TEST(HighContrastFilter_SanityCheck, reporter) { + SkHighContrastConfig config; + config.fInvertStyle = SkHighContrastConfig::InvertStyle::kInvertLightness; + sk_sp<SkColorFilter> filter = SkHighContrastFilter::Make(config); + + SkColor white_inverted = filter->filterColor(SK_ColorWHITE); + REPORTER_ASSERT(reporter, white_inverted == SK_ColorBLACK); + + SkColor black_inverted = filter->filterColor(SK_ColorBLACK); + REPORTER_ASSERT(reporter, black_inverted == SK_ColorWHITE); +} + +DEF_TEST(HighContrastFilter_InvalidInputs, reporter) { + SkHighContrastConfig config; + REPORTER_ASSERT(reporter, config.isValid()); + + // Valid invert style + config.fInvertStyle = SkHighContrastConfig::InvertStyle::kInvertBrightness; + REPORTER_ASSERT(reporter, config.isValid()); + config.fInvertStyle = SkHighContrastConfig::InvertStyle::kInvertLightness; + REPORTER_ASSERT(reporter, config.isValid()); + sk_sp<SkColorFilter> filter = SkHighContrastFilter::Make(config); + REPORTER_ASSERT(reporter, filter); + + // Invalid invert style + config.fInvertStyle = static_cast<SkHighContrastConfig::InvertStyle>(999); + REPORTER_ASSERT(reporter, !config.isValid()); + filter = SkHighContrastFilter::Make(config); + REPORTER_ASSERT(reporter, !filter); + + // Valid contrast + config.fInvertStyle = SkHighContrastConfig::InvertStyle::kInvertBrightness; + config.fContrast = 0.5f; + REPORTER_ASSERT(reporter, config.isValid()); + filter = SkHighContrastFilter::Make(config); + REPORTER_ASSERT(reporter, filter); + + // Invalid contrast + config.fContrast = 1.1f; + REPORTER_ASSERT(reporter, !config.isValid()); + filter = SkHighContrastFilter::Make(config); + REPORTER_ASSERT(reporter, !filter); +} |