onProgram for HighContrastColorFilter

Expose sk_program_transfer_fn helper.

Change-Id: I871b77ecb6733d73c8f900f0bce9f4f3d29b26ec
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/279258
Commit-Queue: Mike Reed <reed@google.com>
Reviewed-by: Mike Klein <mtklein@google.com>
diff --git a/src/core/SkColorSpacePriv.h b/src/core/SkColorSpacePriv.h
index 2e8728c..bbd0d00 100644
--- a/src/core/SkColorSpacePriv.h
+++ b/src/core/SkColorSpacePriv.h
@@ -11,6 +11,7 @@
 
 #include "include/core/SkColorSpace.h"
 #include "include/private/SkFixed.h"
+#include "src/core/SkVM_fwd.h"
 
 #define SkColorSpacePrintf(...)
 
@@ -100,6 +101,9 @@
     return linearExp || linearFn;
 }
 
+skvm::Color sk_program_transfer_fn(skvm::Builder*, skvm::Uniforms*,
+                                   const skcms_TransferFunction&, skvm::Color);
+
 // Return raw pointers to commonly used SkColorSpaces.
 // No need to ref/unref these, but if you do, do it in pairs.
 SkColorSpace* sk_srgb_singleton();
diff --git a/src/core/SkColorSpaceXformSteps.cpp b/src/core/SkColorSpaceXformSteps.cpp
index 7bc23c1..67cb62d 100644
--- a/src/core/SkColorSpaceXformSteps.cpp
+++ b/src/core/SkColorSpaceXformSteps.cpp
@@ -156,8 +156,8 @@
     if (flags.premul) { p->append(SkRasterPipeline::premul); }
 }
 
-static skvm::Color apply_transfer_function(skvm::Builder* p, skvm::Uniforms* uniforms,
-                                           const skcms_TransferFunction& tf, skvm::Color c) {
+skvm::Color sk_program_transfer_fn(skvm::Builder* p, skvm::Uniforms* uniforms,
+                                   const skcms_TransferFunction& tf, skvm::Color c) {
     skvm::F32 G = p->uniformF(uniforms->pushF(tf.g)),
               A = p->uniformF(uniforms->pushF(tf.a)),
               B = p->uniformF(uniforms->pushF(tf.b)),
@@ -211,7 +211,7 @@
         c = p->unpremul(c);
     }
     if (flags.linearize) {
-        c = apply_transfer_function(p, uniforms, srcTF, c);
+        c = sk_program_transfer_fn(p, uniforms, srcTF, c);
     }
     if (flags.gamut_transform) {
         skvm::F32 m[9];
@@ -224,7 +224,7 @@
         c = {R, G, B, c.a};
     }
     if (flags.encode) {
-        c = apply_transfer_function(p, uniforms, dstTFInv, c);
+        c = sk_program_transfer_fn(p, uniforms, dstTFInv, c);
     }
     if (flags.premul) {
         c = p->premul(c);
diff --git a/src/core/SkVM.h b/src/core/SkVM.h
index 7564550..b45890d2 100644
--- a/src/core/SkVM.h
+++ b/src/core/SkVM.h
@@ -465,15 +465,18 @@
             return approx_pow2(mul(splat(1.4426950408889634074f), x));
         }
 
-        F32 negate(F32 x) {
-            return sub(splat(0.0f), x);
-        }
+        F32 inv(F32 x)    { return sub(splat(1.0f), x); }
+        F32 negate(F32 x) { return sub(splat(0.0f), x); }
+
         F32 lerp(F32 lo, F32 hi, F32 t) {
             return mad(sub(hi,lo), t, lo);
         }
         F32 clamp(F32 x, F32 lo, F32 hi) {
             return max(lo, min(x, hi));
         }
+        F32 clamp01(F32 x) {
+            return clamp(x, splat(0.0f), splat(1.0f));
+        }
         F32 abs(F32 x) {
             return bit_cast(bit_and(bit_cast(x),
                                     splat(0x7fffffff)));
diff --git a/src/effects/SkHighContrastFilter.cpp b/src/effects/SkHighContrastFilter.cpp
index 5922cf5..03e547c 100644
--- a/src/effects/SkHighContrastFilter.cpp
+++ b/src/effects/SkHighContrastFilter.cpp
@@ -9,9 +9,11 @@
 #include "include/effects/SkHighContrastFilter.h"
 #include "include/private/SkColorData.h"
 #include "src/core/SkArenaAlloc.h"
+#include "src/core/SkColorSpacePriv.h"
 #include "src/core/SkEffectPriv.h"
 #include "src/core/SkRasterPipeline.h"
 #include "src/core/SkReadBuffer.h"
+#include "src/core/SkVM.h"
 #include "src/core/SkWriteBuffer.h"
 
 #if SK_SUPPORT_GPU
@@ -41,6 +43,8 @@
 #endif
 
     bool onAppendStages(const SkStageRec& rec, bool shaderIsOpaque) const override;
+    skvm::Color onProgram(skvm::Builder*, skvm::Color, SkColorSpace*, skvm::Uniforms*,
+                          SkArenaAlloc*) const override;
 
 protected:
     void flatten(SkWriteBuffer&) const override;
@@ -128,6 +132,108 @@
     return true;
 }
 
+namespace skvm {
+    struct HSLA { F32 h,s,l,a; };
+}
+
+static skvm::HSLA rgb_to_hsl(skvm::Builder* p, skvm::Color c) {
+    auto mx = p->max(p->max(c.r,c.g),c.b),
+         mn = p->min(p->min(c.r,c.g),c.b),
+          d = p->sub(mx,mn),
+      d_rcp = p->div(p->splat(1.0f),d),
+     g_lt_b = p->select(p->lt(c.g,c.b), p->splat(6.0f), p->splat(0.0f));
+
+    auto diffm = [&](auto a, auto b) { return p->mul(p->sub(a,b), d_rcp); };
+
+    auto h = p->mul(p->splat(1/6.0f),
+                    p->select(p->eq(mx,mn),   p->splat(0.0f),
+                    p->select(p->eq(mx, c.r), p->add(diffm(c.g,c.b), g_lt_b),
+                    p->select(p->eq(mx, c.g), p->add(diffm(c.b,c.r), p->splat(2.0f)),
+                                              p->add(diffm(c.r,c.g), p->splat(4.0f))))));
+
+    auto sum = p->add(mx,mn);
+    auto   l = p->mul(sum, p->splat(0.5f));
+    auto   s = p->select(p->eq(mx,mn), p->splat(0.0f),
+                                       p->div(d,
+                                              p->select(p->gt(l,p->splat(0.5f)), p->sub(p->splat(2.0f),sum),
+                                                                                 sum)));
+    return {h, s, l, c.a};
+}
+
+static skvm::Color hsl_to_rgb(skvm::Builder* p, skvm::HSLA c) {
+    // See GrRGBToHSLFilterEffect.fp
+
+    auto h = c.h,
+         s = c.s,
+         l = c.l,
+         x = p->mul(p->sub(p->splat(1.0f), p->abs(p->sub(p->add(l,l),
+                                                         p->splat(1.0f)))),
+                    s);
+
+    auto hue_to_rgb = [&](auto hue) {
+        auto q = p->sub(p->abs(p->mad(p->fract(hue),p->splat(6.0f), p->splat(-3.0f))), p->splat(1.0f));
+        return p->mad(p->sub(p->clamp01(q), p->splat(0.5f)), x, l);
+    };
+
+    return {
+        hue_to_rgb(p->add(h, p->splat(0.0f/3.0f))),
+        hue_to_rgb(p->add(h, p->splat(2.0f/3.0f))),
+        hue_to_rgb(p->add(h, p->splat(1.0f/3.0f))),
+        c.a
+    };
+}
+
+skvm::Color SkHighContrast_Filter::onProgram(skvm::Builder* p, skvm::Color c, SkColorSpace* dstCS,
+                                             skvm::Uniforms* uniforms, SkArenaAlloc* alloc) const {
+    c = p->unpremul(c);
+
+    // Linearize before applying high-contrast filter.
+    skcms_TransferFunction tf;
+    if (dstCS) {
+        dstCS->transferFn(&tf);
+    } else {
+        //sk_srgb_singleton()->transferFn(&tf);
+        tf = {2,1, 0,0,0,0,0};
+    }
+    c = sk_program_transfer_fn(p, uniforms, tf, c);
+
+    if (fConfig.fGrayscale) {
+        skvm::F32 gray = p->mad(p->splat(SK_LUM_COEFF_R),c.r,
+                         p->mad(p->splat(SK_LUM_COEFF_G),c.g,
+                         p->mul(p->splat(SK_LUM_COEFF_B),c.b)));
+        c = {gray, gray, gray, c.a};
+    }
+
+    if (fConfig.fInvertStyle == InvertStyle::kInvertBrightness) {
+        c = {p->inv(c.r), p->inv(c.g), p->inv(c.b), c.a};
+    } else if (fConfig.fInvertStyle == InvertStyle::kInvertLightness) {
+        skvm::HSLA hsla = rgb_to_hsl(p, c);
+        hsla.l = p->inv(hsla.l);
+        c = hsl_to_rgb(p, hsla);
+    }
+
+    if (fConfig.fContrast != 0.0) {
+        const float m = (1 + fConfig.fContrast) / (1 - fConfig.fContrast);
+        const float b = (-0.5f * m + 0.5f);
+        skvm::F32   M = p->uniformF(uniforms->pushF(m));
+        skvm::F32   B = p->uniformF(uniforms->pushF(b));
+        c = {p->mad(M,c.r, B), p->mad(M,c.g, B), p->mad(M,c.b, B), c.a};
+    }
+
+    c = {p->clamp01(c.r), p->clamp01(c.g), p->clamp01(c.b), c.a};
+
+    // Re-encode back from linear.
+    if (dstCS) {
+        dstCS->invTransferFn(&tf);
+    } else {
+        //sk_srgb_singleton()->invTransferFn(&tf);
+        tf = {0.5f,1, 0,0,0,0,0};
+    }
+    c = sk_program_transfer_fn(p, uniforms, tf, c);
+
+    return p->premul(c);
+}
+
 void SkHighContrast_Filter::flatten(SkWriteBuffer& buffer) const {
     buffer.writeBool(fConfig.fGrayscale);
     buffer.writeInt(static_cast<int>(fConfig.fInvertStyle));