| 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/SkColorSpacePriv.h" |
| 13 | #include "src/core/SkEffectPriv.h" |
| 14 | #include "src/core/SkRasterPipeline.h" |
| 15 | #include "src/core/SkReadBuffer.h" |
| 16 | #include "src/core/SkVM.h" |
| 17 | #include "src/core/SkWriteBuffer.h" |
| 18 | |
| 19 | #if SK_SUPPORT_GPU |
| 20 | #include "include/gpu/GrContext.h" |
| 21 | #include "src/gpu/GrColorInfo.h" |
| 22 | #include "src/gpu/glsl/GrGLSLFragmentProcessor.h" |
| 23 | #include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" |
| 24 | #endif |
| 25 | |
| 26 | using InvertStyle = SkHighContrastConfig::InvertStyle; |
| 27 | |
| 28 | class SkHighContrast_Filter : public SkColorFilter { |
| 29 | public: |
| 30 | SkHighContrast_Filter(const SkHighContrastConfig& config) { |
| 31 | fConfig = config; |
| 32 | // Clamp contrast to just inside -1 to 1 to avoid division by zero. |
| 33 | fConfig.fContrast = SkTPin(fConfig.fContrast, |
| 34 | -1.0f + FLT_EPSILON, |
| 35 | 1.0f - FLT_EPSILON); |
| 36 | } |
| 37 | |
| 38 | ~SkHighContrast_Filter() override {} |
| 39 | |
| 40 | #if SK_SUPPORT_GPU |
| 41 | std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(GrRecordingContext*, |
| 42 | const GrColorInfo&) const override; |
| 43 | #endif |
| 44 | |
| 45 | bool onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const override; |
| 46 | skvm::Color onProgram(skvm::Builder*, skvm::Color, SkColorSpace*, skvm::Uniforms*, |
| 47 | SkArenaAlloc*) const override; |
| 48 | |
| 49 | protected: |
| 50 | void flatten(SkWriteBuffer&) const override; |
| 51 | |
| 52 | private: |
| 53 | SK_FLATTENABLE_HOOKS(SkHighContrast_Filter) |
| 54 | |
| 55 | SkHighContrastConfig fConfig; |
| 56 | |
| 57 | friend class SkHighContrastFilter; |
| 58 | |
| 59 | typedef SkColorFilter INHERITED; |
| 60 | }; |
| 61 | |
| 62 | bool SkHighContrast_Filter::onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const { |
| 63 | SkRasterPipeline* p = rec.fPipeline; |
| 64 | SkArenaAlloc* alloc = rec.fAlloc; |
| 65 | |
| 66 | if (!shaderIsOpaque) { |
| 67 | p->append(SkRasterPipeline::unpremul); |
| 68 | } |
| 69 | |
| 70 | // Linearize before applying high-contrast filter. |
| 71 | auto tf = alloc->make<skcms_TransferFunction>(); |
| 72 | if (rec.fDstCS) { |
| 73 | rec.fDstCS->transferFn(tf); |
| 74 | } else { |
| 75 | // Historically we approximate untagged destinations as gamma 2. |
| 76 | // TODO: sRGB? |
| 77 | *tf = {2,1, 0,0,0,0,0}; |
| 78 | } |
| 79 | p->append_transfer_function(*tf); |
| 80 | |
| 81 | if (fConfig.fGrayscale) { |
| 82 | float r = SK_LUM_COEFF_R; |
| 83 | float g = SK_LUM_COEFF_G; |
| 84 | float b = SK_LUM_COEFF_B; |
| 85 | float* matrix = alloc->makeArray<float>(12); |
| 86 | matrix[0] = matrix[1] = matrix[2] = r; |
| 87 | matrix[3] = matrix[4] = matrix[5] = g; |
| 88 | matrix[6] = matrix[7] = matrix[8] = b; |
| 89 | p->append(SkRasterPipeline::matrix_3x4, matrix); |
| 90 | } |
| 91 | |
| 92 | if (fConfig.fInvertStyle == InvertStyle::kInvertBrightness) { |
| 93 | float* matrix = alloc->makeArray<float>(12); |
| 94 | matrix[0] = matrix[4] = matrix[8] = -1; |
| 95 | matrix[9] = matrix[10] = matrix[11] = 1; |
| 96 | p->append(SkRasterPipeline::matrix_3x4, matrix); |
| 97 | } else if (fConfig.fInvertStyle == InvertStyle::kInvertLightness) { |
| 98 | p->append(SkRasterPipeline::rgb_to_hsl); |
| 99 | float* matrix = alloc->makeArray<float>(12); |
| 100 | matrix[0] = matrix[4] = matrix[11] = 1; |
| 101 | matrix[8] = -1; |
| 102 | p->append(SkRasterPipeline::matrix_3x4, matrix); |
| 103 | p->append(SkRasterPipeline::hsl_to_rgb); |
| 104 | } |
| 105 | |
| 106 | if (fConfig.fContrast != 0.0) { |
| 107 | float* matrix = alloc->makeArray<float>(12); |
| 108 | float c = fConfig.fContrast; |
| 109 | float m = (1 + c) / (1 - c); |
| 110 | float b = (-0.5f * m + 0.5f); |
| 111 | matrix[0] = matrix[4] = matrix[8] = m; |
| 112 | matrix[9] = matrix[10] = matrix[11] = b; |
| 113 | p->append(SkRasterPipeline::matrix_3x4, matrix); |
| 114 | } |
| 115 | |
| 116 | p->append(SkRasterPipeline::clamp_0); |
| 117 | p->append(SkRasterPipeline::clamp_1); |
| 118 | |
| 119 | // Re-encode back from linear. |
| 120 | auto invTF = alloc->make<skcms_TransferFunction>(); |
| 121 | if (rec.fDstCS) { |
| 122 | rec.fDstCS->invTransferFn(invTF); |
| 123 | } else { |
| 124 | // See above... historically untagged == gamma 2 in this filter. |
| 125 | *invTF ={0.5f,1, 0,0,0,0,0}; |
| 126 | } |
| 127 | p->append_transfer_function(*invTF); |
| 128 | |
| 129 | if (!shaderIsOpaque) { |
| 130 | p->append(SkRasterPipeline::premul); |
| 131 | } |
| 132 | return true; |
| 133 | } |
| 134 | |
| 135 | skvm::Color SkHighContrast_Filter::onProgram(skvm::Builder* p, skvm::Color c, SkColorSpace* dstCS, |
| 136 | skvm::Uniforms* uniforms, SkArenaAlloc* alloc) const { |
| 137 | c = p->unpremul(c); |
| 138 | |
| 139 | // Linearize before applying high-contrast filter. |
| 140 | skcms_TransferFunction tf; |
| 141 | if (dstCS) { |
| 142 | dstCS->transferFn(&tf); |
| 143 | } else { |
| 144 | //sk_srgb_singleton()->transferFn(&tf); |
| 145 | tf = {2,1, 0,0,0,0,0}; |
| 146 | } |
| 147 | c = sk_program_transfer_fn(p, uniforms, tf, c); |
| 148 | |
| 149 | if (fConfig.fGrayscale) { |
| 150 | skvm::F32 gray = c.r * SK_LUM_COEFF_R |
| 151 | + c.g * SK_LUM_COEFF_G |
| 152 | + c.b * SK_LUM_COEFF_B; |
| 153 | c = {gray, gray, gray, c.a}; |
| 154 | } |
| 155 | |
| 156 | if (fConfig.fInvertStyle == InvertStyle::kInvertBrightness) { |
| 157 | c = {1-c.r, 1-c.g, 1-c.b, c.a}; |
| 158 | } else if (fConfig.fInvertStyle == InvertStyle::kInvertLightness) { |
| 159 | auto [h, s, l, a] = p->to_hsla(c); |
| 160 | c = p->to_rgba({h, s, 1-l, a}); |
| 161 | } |
| 162 | |
| 163 | if (fConfig.fContrast != 0.0) { |
| 164 | const float m = (1 + fConfig.fContrast) / (1 - fConfig.fContrast); |
| 165 | const float b = (-0.5f * m + 0.5f); |
| 166 | skvm::F32 M = p->uniformF(uniforms->pushF(m)); |
| 167 | skvm::F32 B = p->uniformF(uniforms->pushF(b)); |
| 168 | c.r = c.r * M + B; |
| 169 | c.g = c.g * M + B; |
| 170 | c.b = c.b * M + B; |
| 171 | } |
| 172 | |
| 173 | c.r = clamp01(c.r); |
| 174 | c.g = clamp01(c.g); |
| 175 | c.b = clamp01(c.b); |
| 176 | |
| 177 | // Re-encode back from linear. |
| 178 | if (dstCS) { |
| 179 | dstCS->invTransferFn(&tf); |
| 180 | } else { |
| 181 | //sk_srgb_singleton()->invTransferFn(&tf); |
| 182 | tf = {0.5f,1, 0,0,0,0,0}; |
| 183 | } |
| 184 | c = sk_program_transfer_fn(p, uniforms, tf, c); |
| 185 | |
| 186 | return p->premul(c); |
| 187 | } |
| 188 | |
| 189 | void SkHighContrast_Filter::flatten(SkWriteBuffer& buffer) const { |
| 190 | buffer.writeBool(fConfig.fGrayscale); |
| 191 | buffer.writeInt(static_cast<int>(fConfig.fInvertStyle)); |
| 192 | buffer.writeScalar(fConfig.fContrast); |
| 193 | } |
| 194 | |
| 195 | sk_sp<SkFlattenable> SkHighContrast_Filter::CreateProc(SkReadBuffer& buffer) { |
| 196 | SkHighContrastConfig config; |
| 197 | config.fGrayscale = buffer.readBool(); |
| 198 | config.fInvertStyle = buffer.read32LE(InvertStyle::kLast); |
| 199 | config.fContrast = buffer.readScalar(); |
| 200 | |
| 201 | return SkHighContrastFilter::Make(config); |
| 202 | } |
| 203 | |
| 204 | sk_sp<SkColorFilter> SkHighContrastFilter::Make( |
| 205 | const SkHighContrastConfig& config) { |
| 206 | if (!config.isValid()) { |
| 207 | return nullptr; |
| 208 | } |
| 209 | return sk_make_sp<SkHighContrast_Filter>(config); |
| 210 | } |
| 211 | |
| 212 | void SkHighContrastFilter::RegisterFlattenables() { |
| 213 | SK_REGISTER_FLATTENABLE(SkHighContrast_Filter); |
| 214 | } |
| 215 | |
| 216 | #if SK_SUPPORT_GPU |
| 217 | class HighContrastFilterEffect : public GrFragmentProcessor { |
| 218 | public: |
| 219 | static std::unique_ptr<GrFragmentProcessor> Make(const SkHighContrastConfig& config, |
| 220 | bool linearize) { |
| 221 | return std::unique_ptr<GrFragmentProcessor>(new HighContrastFilterEffect(config, |
| 222 | linearize)); |
| 223 | } |
| 224 | |
| 225 | const char* name() const override { return "HighContrastFilter" ; } |
| 226 | |
| 227 | const SkHighContrastConfig& config() const { return fConfig; } |
| 228 | bool linearize() const { return fLinearize; } |
| 229 | |
| 230 | std::unique_ptr<GrFragmentProcessor> clone() const override { |
| 231 | return Make(fConfig, fLinearize); |
| 232 | } |
| 233 | |
| 234 | private: |
| 235 | HighContrastFilterEffect(const SkHighContrastConfig& config, bool linearize) |
| 236 | : INHERITED(kHighContrastFilterEffect_ClassID, kNone_OptimizationFlags) |
| 237 | , fConfig(config) |
| 238 | , fLinearize(linearize) {} |
| 239 | |
| 240 | GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; |
| 241 | |
| 242 | virtual void onGetGLSLProcessorKey(const GrShaderCaps& caps, |
| 243 | GrProcessorKeyBuilder* b) const override; |
| 244 | |
| 245 | bool onIsEqual(const GrFragmentProcessor& other) const override { |
| 246 | const HighContrastFilterEffect& that = other.cast<HighContrastFilterEffect>(); |
| 247 | return fConfig.fGrayscale == that.fConfig.fGrayscale && |
| 248 | fConfig.fInvertStyle == that.fConfig.fInvertStyle && |
| 249 | fConfig.fContrast == that.fConfig.fContrast && |
| 250 | fLinearize == that.fLinearize; |
| 251 | } |
| 252 | |
| 253 | SkHighContrastConfig fConfig; |
| 254 | bool fLinearize; |
| 255 | |
| 256 | typedef GrFragmentProcessor INHERITED; |
| 257 | }; |
| 258 | |
| 259 | class GLHighContrastFilterEffect : public GrGLSLFragmentProcessor { |
| 260 | public: |
| 261 | static void GenKey(const GrProcessor&, const GrShaderCaps&, GrProcessorKeyBuilder*); |
| 262 | |
| 263 | protected: |
| 264 | void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; |
| 265 | void emitCode(EmitArgs& args) override; |
| 266 | |
| 267 | private: |
| 268 | UniformHandle fContrastUni; |
| 269 | |
| 270 | typedef GrGLSLFragmentProcessor INHERITED; |
| 271 | }; |
| 272 | |
| 273 | GrGLSLFragmentProcessor* HighContrastFilterEffect::onCreateGLSLInstance() const { |
| 274 | return new GLHighContrastFilterEffect(); |
| 275 | } |
| 276 | |
| 277 | void HighContrastFilterEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, |
| 278 | GrProcessorKeyBuilder* b) const { |
| 279 | GLHighContrastFilterEffect::GenKey(*this, caps, b); |
| 280 | } |
| 281 | |
| 282 | void GLHighContrastFilterEffect::onSetData(const GrGLSLProgramDataManager& pdm, |
| 283 | const GrFragmentProcessor& proc) { |
| 284 | const HighContrastFilterEffect& hcfe = proc.cast<HighContrastFilterEffect>(); |
| 285 | pdm.set1f(fContrastUni, hcfe.config().fContrast); |
| 286 | } |
| 287 | |
| 288 | void GLHighContrastFilterEffect::GenKey( |
| 289 | const GrProcessor& proc, const GrShaderCaps&, GrProcessorKeyBuilder* b) { |
| 290 | const HighContrastFilterEffect& hcfe = proc.cast<HighContrastFilterEffect>(); |
| 291 | b->add32(static_cast<uint32_t>(hcfe.config().fGrayscale)); |
| 292 | b->add32(static_cast<uint32_t>(hcfe.config().fInvertStyle)); |
| 293 | b->add32(hcfe.linearize() ? 1 : 0); |
| 294 | } |
| 295 | |
| 296 | void GLHighContrastFilterEffect::emitCode(EmitArgs& args) { |
| 297 | const HighContrastFilterEffect& hcfe = args.fFp.cast<HighContrastFilterEffect>(); |
| 298 | const SkHighContrastConfig& config = hcfe.config(); |
| 299 | |
| 300 | const char* contrast; |
| 301 | fContrastUni = args.fUniformHandler->addUniform(&hcfe, kFragment_GrShaderFlag, kHalf_GrSLType, |
| 302 | "contrast" , &contrast); |
| 303 | |
| 304 | GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; |
| 305 | |
| 306 | fragBuilder->codeAppendf("half4 color = %s;" , args.fInputColor); |
| 307 | |
| 308 | // Unpremultiply. The max() is to guard against 0 / 0. |
| 309 | fragBuilder->codeAppendf("half nonZeroAlpha = max(color.a, 0.0001);" ); |
| 310 | fragBuilder->codeAppendf("color = half4(color.rgb / nonZeroAlpha, nonZeroAlpha);" ); |
| 311 | |
| 312 | if (hcfe.linearize()) { |
| 313 | fragBuilder->codeAppend("color.rgb = color.rgb * color.rgb;" ); |
| 314 | } |
| 315 | |
| 316 | // Grayscale. |
| 317 | if (config.fGrayscale) { |
| 318 | fragBuilder->codeAppendf("half luma = dot(color, half4(%f, %f, %f, 0));" , |
| 319 | SK_LUM_COEFF_R, SK_LUM_COEFF_G, SK_LUM_COEFF_B); |
| 320 | fragBuilder->codeAppendf("color = half4(luma, luma, luma, 0);" ); |
| 321 | } |
| 322 | |
| 323 | if (config.fInvertStyle == InvertStyle::kInvertBrightness) { |
| 324 | fragBuilder->codeAppendf("color = half4(1, 1, 1, 1) - color;" ); |
| 325 | } |
| 326 | |
| 327 | if (config.fInvertStyle == InvertStyle::kInvertLightness) { |
| 328 | // Convert from RGB to HSL. |
| 329 | fragBuilder->codeAppendf("half fmax = max(color.r, max(color.g, color.b));" ); |
| 330 | fragBuilder->codeAppendf("half fmin = min(color.r, min(color.g, color.b));" ); |
| 331 | fragBuilder->codeAppendf("half l = (fmax + fmin) / 2;" ); |
| 332 | |
| 333 | fragBuilder->codeAppendf("half h;" ); |
| 334 | fragBuilder->codeAppendf("half s;" ); |
| 335 | |
| 336 | fragBuilder->codeAppendf("if (fmax == fmin) {" ); |
| 337 | fragBuilder->codeAppendf(" h = 0;" ); |
| 338 | fragBuilder->codeAppendf(" s = 0;" ); |
| 339 | fragBuilder->codeAppendf("} else {" ); |
| 340 | fragBuilder->codeAppendf(" half d = fmax - fmin;" ); |
| 341 | fragBuilder->codeAppendf(" s = l > 0.5 ?" ); |
| 342 | fragBuilder->codeAppendf(" d / (2 - fmax - fmin) :" ); |
| 343 | fragBuilder->codeAppendf(" d / (fmax + fmin);" ); |
| 344 | // We'd like to just write "if (color.r == fmax) { ... }". On many GPUs, running the |
| 345 | // angle_d3d9_es2 config, that failed. It seems that max(x, y) is not necessarily equal |
| 346 | // to either x or y. Tried several ways to fix it, but this was the only reasonable fix. |
| 347 | fragBuilder->codeAppendf(" if (color.r >= color.g && color.r >= color.b) {" ); |
| 348 | fragBuilder->codeAppendf(" h = (color.g - color.b) / d + " ); |
| 349 | fragBuilder->codeAppendf(" (color.g < color.b ? 6 : 0);" ); |
| 350 | fragBuilder->codeAppendf(" } else if (color.g >= color.b) {" ); |
| 351 | fragBuilder->codeAppendf(" h = (color.b - color.r) / d + 2;" ); |
| 352 | fragBuilder->codeAppendf(" } else {" ); |
| 353 | fragBuilder->codeAppendf(" h = (color.r - color.g) / d + 4;" ); |
| 354 | fragBuilder->codeAppendf(" }" ); |
| 355 | fragBuilder->codeAppendf("}" ); |
| 356 | fragBuilder->codeAppendf("h /= 6;" ); |
| 357 | fragBuilder->codeAppendf("l = 1.0 - l;" ); |
| 358 | // Convert back from HSL to RGB. |
| 359 | SkString hue2rgbFuncName; |
| 360 | const GrShaderVar gHue2rgbArgs[] = { |
| 361 | GrShaderVar("p" , kHalf_GrSLType), |
| 362 | GrShaderVar("q" , kHalf_GrSLType), |
| 363 | GrShaderVar("t" , kHalf_GrSLType), |
| 364 | }; |
| 365 | fragBuilder->emitFunction(kHalf_GrSLType, |
| 366 | "hue2rgb" , |
| 367 | SK_ARRAY_COUNT(gHue2rgbArgs), |
| 368 | gHue2rgbArgs, |
| 369 | "if (t < 0)" |
| 370 | " t += 1;" |
| 371 | "if (t > 1)" |
| 372 | " t -= 1;" |
| 373 | "if (t < 1/6.)" |
| 374 | " return p + (q - p) * 6 * t;" |
| 375 | "if (t < 1/2.)" |
| 376 | " return q;" |
| 377 | "if (t < 2/3.)" |
| 378 | " return p + (q - p) * (2/3. - t) * 6;" |
| 379 | "return p;" , |
| 380 | &hue2rgbFuncName); |
| 381 | fragBuilder->codeAppendf("if (s == 0) {" ); |
| 382 | fragBuilder->codeAppendf(" color = half4(l, l, l, 0);" ); |
| 383 | fragBuilder->codeAppendf("} else {" ); |
| 384 | fragBuilder->codeAppendf(" half q = l < 0.5 ? l * (1 + s) : l + s - l * s;" ); |
| 385 | fragBuilder->codeAppendf(" half p = 2 * l - q;" ); |
| 386 | fragBuilder->codeAppendf(" color.r = %s(p, q, h + 1/3.);" , hue2rgbFuncName.c_str()); |
| 387 | fragBuilder->codeAppendf(" color.g = %s(p, q, h);" , hue2rgbFuncName.c_str()); |
| 388 | fragBuilder->codeAppendf(" color.b = %s(p, q, h - 1/3.);" , hue2rgbFuncName.c_str()); |
| 389 | fragBuilder->codeAppendf("}" ); |
| 390 | } |
| 391 | |
| 392 | // Contrast. |
| 393 | fragBuilder->codeAppendf("if (%s != 0) {" , contrast); |
| 394 | fragBuilder->codeAppendf(" half m = (1 + %s) / (1 - %s);" , contrast, contrast); |
| 395 | fragBuilder->codeAppendf(" half off = (-0.5 * m + 0.5);" ); |
| 396 | fragBuilder->codeAppendf(" color = m * color + off;" ); |
| 397 | fragBuilder->codeAppendf("}" ); |
| 398 | |
| 399 | // Clamp. |
| 400 | fragBuilder->codeAppendf("color = saturate(color);" ); |
| 401 | |
| 402 | if (hcfe.linearize()) { |
| 403 | fragBuilder->codeAppend("color.rgb = sqrt(color.rgb);" ); |
| 404 | } |
| 405 | |
| 406 | // Restore the original alpha and premultiply. |
| 407 | fragBuilder->codeAppendf("color.a = %s.a;" , args.fInputColor); |
| 408 | fragBuilder->codeAppendf("color.rgb *= color.a;" ); |
| 409 | |
| 410 | // Copy to the output color. |
| 411 | fragBuilder->codeAppendf("%s = color;" , args.fOutputColor); |
| 412 | } |
| 413 | |
| 414 | std::unique_ptr<GrFragmentProcessor> SkHighContrast_Filter::asFragmentProcessor( |
| 415 | GrRecordingContext*, const GrColorInfo& csi) const { |
| 416 | bool linearize = !csi.isLinearlyBlended(); |
| 417 | return HighContrastFilterEffect::Make(fConfig, linearize); |
| 418 | } |
| 419 | #endif |
| 420 | |