aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-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);
+}