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