1/*
2* Copyright 2017 Google Inc.
3*
4* Use of this source code is governed by a BSD-style license that can be
5* found in the LICENSE file.
6*/
7
8#include "include/core/SkString.h"
9#include "include/effects/SkHighContrastFilter.h"
10#include "include/private/SkColorData.h"
11#include "src/core/SkArenaAlloc.h"
12#include "src/core/SkColorFilterBase.h"
13#include "src/core/SkColorSpacePriv.h"
14#include "src/core/SkEffectPriv.h"
15#include "src/core/SkRasterPipeline.h"
16#include "src/core/SkReadBuffer.h"
17#include "src/core/SkVM.h"
18#include "src/core/SkWriteBuffer.h"
19
20#if SK_SUPPORT_GPU
21#include "src/gpu/GrColorInfo.h"
22#include "src/gpu/effects/generated/GrHighContrastFilterEffect.h"
23#include "src/gpu/glsl/GrGLSLFragmentProcessor.h"
24#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
25#endif
26
27using InvertStyle = SkHighContrastConfig::InvertStyle;
28
29class SkHighContrast_Filter : public SkColorFilterBase {
30public:
31 SkHighContrast_Filter(const SkHighContrastConfig& config) {
32 fConfig = config;
33 // Clamp contrast to just inside -1 to 1 to avoid division by zero.
34 fConfig.fContrast = SkTPin(fConfig.fContrast,
35 -1.0f + FLT_EPSILON,
36 1.0f - FLT_EPSILON);
37 }
38
39 ~SkHighContrast_Filter() override {}
40
41#if SK_SUPPORT_GPU
42 GrFPResult asFragmentProcessor(std::unique_ptr<GrFragmentProcessor> inputFP,
43 GrRecordingContext*, const GrColorInfo&) const override;
44#endif
45
46 bool onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const override;
47 skvm::Color onProgram(skvm::Builder*, skvm::Color, SkColorSpace*, skvm::Uniforms*,
48 SkArenaAlloc*) const override;
49
50protected:
51 void flatten(SkWriteBuffer&) const override;
52
53private:
54 SK_FLATTENABLE_HOOKS(SkHighContrast_Filter)
55
56 SkHighContrastConfig fConfig;
57
58 friend class SkHighContrastFilter;
59
60 typedef SkColorFilter INHERITED;
61};
62
63bool SkHighContrast_Filter::onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const {
64 SkRasterPipeline* p = rec.fPipeline;
65 SkArenaAlloc* alloc = rec.fAlloc;
66
67 if (!shaderIsOpaque) {
68 p->append(SkRasterPipeline::unpremul);
69 }
70
71 // Linearize before applying high-contrast filter.
72 auto tf = alloc->make<skcms_TransferFunction>();
73 if (rec.fDstCS) {
74 rec.fDstCS->transferFn(tf);
75 } else {
76 // Historically we approximate untagged destinations as gamma 2.
77 // TODO: sRGB?
78 *tf = {2,1, 0,0,0,0,0};
79 }
80 p->append_transfer_function(*tf);
81
82 if (fConfig.fGrayscale) {
83 float r = SK_LUM_COEFF_R;
84 float g = SK_LUM_COEFF_G;
85 float b = SK_LUM_COEFF_B;
86 float* matrix = alloc->makeArray<float>(12);
87 matrix[0] = matrix[1] = matrix[2] = r;
88 matrix[3] = matrix[4] = matrix[5] = g;
89 matrix[6] = matrix[7] = matrix[8] = b;
90 p->append(SkRasterPipeline::matrix_3x4, matrix);
91 }
92
93 if (fConfig.fInvertStyle == InvertStyle::kInvertBrightness) {
94 float* matrix = alloc->makeArray<float>(12);
95 matrix[0] = matrix[4] = matrix[8] = -1;
96 matrix[9] = matrix[10] = matrix[11] = 1;
97 p->append(SkRasterPipeline::matrix_3x4, matrix);
98 } else if (fConfig.fInvertStyle == InvertStyle::kInvertLightness) {
99 p->append(SkRasterPipeline::rgb_to_hsl);
100 float* matrix = alloc->makeArray<float>(12);
101 matrix[0] = matrix[4] = matrix[11] = 1;
102 matrix[8] = -1;
103 p->append(SkRasterPipeline::matrix_3x4, matrix);
104 p->append(SkRasterPipeline::hsl_to_rgb);
105 }
106
107 if (fConfig.fContrast != 0.0) {
108 float* matrix = alloc->makeArray<float>(12);
109 float c = fConfig.fContrast;
110 float m = (1 + c) / (1 - c);
111 float b = (-0.5f * m + 0.5f);
112 matrix[0] = matrix[4] = matrix[8] = m;
113 matrix[9] = matrix[10] = matrix[11] = b;
114 p->append(SkRasterPipeline::matrix_3x4, matrix);
115 }
116
117 p->append(SkRasterPipeline::clamp_0);
118 p->append(SkRasterPipeline::clamp_1);
119
120 // Re-encode back from linear.
121 auto invTF = alloc->make<skcms_TransferFunction>();
122 if (rec.fDstCS) {
123 rec.fDstCS->invTransferFn(invTF);
124 } else {
125 // See above... historically untagged == gamma 2 in this filter.
126 *invTF ={0.5f,1, 0,0,0,0,0};
127 }
128 p->append_transfer_function(*invTF);
129
130 if (!shaderIsOpaque) {
131 p->append(SkRasterPipeline::premul);
132 }
133 return true;
134}
135
136skvm::Color SkHighContrast_Filter::onProgram(skvm::Builder* p, skvm::Color c, SkColorSpace* dstCS,
137 skvm::Uniforms* uniforms, SkArenaAlloc* alloc) const {
138 c = p->unpremul(c);
139
140 // Linearize before applying high-contrast filter.
141 skcms_TransferFunction tf;
142 if (dstCS) {
143 dstCS->transferFn(&tf);
144 } else {
145 //sk_srgb_singleton()->transferFn(&tf);
146 tf = {2,1, 0,0,0,0,0};
147 }
148 c = sk_program_transfer_fn(p, uniforms, tf, c);
149
150 if (fConfig.fGrayscale) {
151 skvm::F32 gray = c.r * SK_LUM_COEFF_R
152 + c.g * SK_LUM_COEFF_G
153 + c.b * SK_LUM_COEFF_B;
154 c = {gray, gray, gray, c.a};
155 }
156
157 if (fConfig.fInvertStyle == InvertStyle::kInvertBrightness) {
158 c = {1-c.r, 1-c.g, 1-c.b, c.a};
159 } else if (fConfig.fInvertStyle == InvertStyle::kInvertLightness) {
160 auto [h, s, l, a] = p->to_hsla(c);
161 c = p->to_rgba({h, s, 1-l, a});
162 }
163
164 if (fConfig.fContrast != 0.0) {
165 const float m = (1 + fConfig.fContrast) / (1 - fConfig.fContrast);
166 const float b = (-0.5f * m + 0.5f);
167 skvm::F32 M = p->uniformF(uniforms->pushF(m));
168 skvm::F32 B = p->uniformF(uniforms->pushF(b));
169 c.r = c.r * M + B;
170 c.g = c.g * M + B;
171 c.b = c.b * M + B;
172 }
173
174 c.r = clamp01(c.r);
175 c.g = clamp01(c.g);
176 c.b = clamp01(c.b);
177
178 // Re-encode back from linear.
179 if (dstCS) {
180 dstCS->invTransferFn(&tf);
181 } else {
182 //sk_srgb_singleton()->invTransferFn(&tf);
183 tf = {0.5f,1, 0,0,0,0,0};
184 }
185 c = sk_program_transfer_fn(p, uniforms, tf, c);
186
187 return p->premul(c);
188}
189
190void SkHighContrast_Filter::flatten(SkWriteBuffer& buffer) const {
191 buffer.writeBool(fConfig.fGrayscale);
192 buffer.writeInt(static_cast<int>(fConfig.fInvertStyle));
193 buffer.writeScalar(fConfig.fContrast);
194}
195
196sk_sp<SkFlattenable> SkHighContrast_Filter::CreateProc(SkReadBuffer& buffer) {
197 SkHighContrastConfig config;
198 config.fGrayscale = buffer.readBool();
199 config.fInvertStyle = buffer.read32LE(InvertStyle::kLast);
200 config.fContrast = buffer.readScalar();
201
202 return SkHighContrastFilter::Make(config);
203}
204
205sk_sp<SkColorFilter> SkHighContrastFilter::Make(
206 const SkHighContrastConfig& config) {
207 if (!config.isValid()) {
208 return nullptr;
209 }
210 return sk_make_sp<SkHighContrast_Filter>(config);
211}
212
213void SkHighContrastFilter::RegisterFlattenables() {
214 SK_REGISTER_FLATTENABLE(SkHighContrast_Filter);
215}
216
217#if SK_SUPPORT_GPU
218GrFPResult SkHighContrast_Filter::asFragmentProcessor(std::unique_ptr<GrFragmentProcessor> inputFP,
219 GrRecordingContext*,
220 const GrColorInfo& csi) const {
221 bool linearize = !csi.isLinearlyBlended();
222 return GrFPSuccess(GrHighContrastFilterEffect::Make(std::move(inputFP), fConfig, linearize));
223}
224#endif
225