blob: c3937cc9f6832299974982619893a7a92c37fde8 [file] [log] [blame]
/*
* 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);
}
~SkHighContrast_Filter() override {}
#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