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