aboutsummaryrefslogtreecommitdiffhomepage
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
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>
-rw-r--r--gm/highcontrastfilter.cpp143
-rw-r--r--gn/effects.gni1
-rw-r--r--gn/gm.gni1
-rw-r--r--gn/tests.gni1
-rw-r--r--include/effects/SkHighContrastFilter.h81
-rw-r--r--src/core/SkRasterPipeline.h4
-rw-r--r--src/effects/SkHighContrastFilter.cpp469
-rw-r--r--src/opts/SkRasterPipeline_opts.h42
-rw-r--r--src/ports/SkGlobalInitialization_default.cpp2
-rw-r--r--tests/HighContrastFilterTest.cpp93
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",
diff --git a/gn/gm.gni b/gn/gm.gni
index 8fd946b746..d9c25652b0 100644
--- a/gn/gm.gni
+++ b/gn/gm.gni
@@ -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);
+}