SkColorfilters::HSLAMatrix

Introduce an SkColorFilter_Matrix flavor operating in HSLA space.

(CPU-only for now)

Change-Id: If081de062b9e920c3365bd7b281e45bb069c3d1a
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/231259
Commit-Queue: Florin Malita <fmalita@chromium.org>
Reviewed-by: Mike Reed <reed@google.com>
Reviewed-by: Mike Klein <mtklein@google.com>
diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt
index ec97a87..7e5f871 100644
--- a/RELEASE_NOTES.txt
+++ b/RELEASE_NOTES.txt
@@ -8,9 +8,11 @@
 
  * Added RELEASE_NOTES.txt file
 
-SkDrawLooper is no longer supported in SkPaint or SkCanvas.
+ * SkDrawLooper is no longer supported in SkPaint or SkCanvas.
 
-SkImageFilter API refactor started:
- - Consolidated enum types to use SkTileMode and SkColorChannel
- - Hide filter implementation classes
- - Bumps SkPicture version number
+ * SkImageFilter API refactor started:
+   - Consolidated enum types to use SkTileMode and SkColorChannel
+   - Hide filter implementation classes
+   - Bumps SkPicture version number
+
+ * SkColorFilters::HSLAMatrix - new matrix color filter operating in HSLA space.
diff --git a/gm/colorfilters.cpp b/gm/colorfilters.cpp
index e2b4b23..0c547a1 100644
--- a/gm/colorfilters.cpp
+++ b/gm/colorfilters.cpp
@@ -20,6 +20,10 @@
 #include "include/core/SkTypes.h"
 #include "include/effects/SkColorMatrixFilter.h"
 #include "include/effects/SkGradientShader.h"
+#include "tools/Resources.h"
+
+#include <vector>
+#include <tuple>
 
 static sk_sp<SkShader> make_shader(const SkRect& bounds) {
     const SkPoint pts[] = {
@@ -78,3 +82,104 @@
 };
 
 DEF_GM(return new ColorFiltersGM;)
+
+class HSLColorFilterGM : public skiagm::GM {
+protected:
+    SkString onShortName() override { return SkString("hslcolorfilter"); }
+
+    SkISize onISize() override { return { 840, 1100 }; }
+
+    void onOnceBeforeDraw() override {
+        sk_sp<SkImage> mandrill = GetResourceAsImage("images/mandrill_256.png");
+        const auto lm = SkMatrix::MakeRectToRect(SkRect::MakeWH(mandrill->width(),
+                                                                mandrill->height()),
+                                                 SkRect::MakeWH(kWheelSize, kWheelSize),
+                                                 SkMatrix::kFill_ScaleToFit);
+        fShaders.push_back(mandrill->makeShader(&lm));
+
+        static constexpr SkColor gGrads[][4] = {
+            { 0xffff0000, 0xff00ff00, 0xff0000ff, 0xffff0000 },
+            { 0xdfc08040, 0xdf8040c0, 0xdf40c080, 0xdfc08040 },
+        };
+
+        for (const auto& cols : gGrads) {
+            fShaders.push_back(SkGradientShader::MakeSweep(kWheelSize / 2, kWheelSize / 2,
+                                                           cols, nullptr, SK_ARRAY_COUNT(cols),
+                                                           SkTileMode::kRepeat, -90, 270, 0,
+                                                           nullptr));
+        }
+    }
+
+    void onDraw(SkCanvas* canvas) override {
+        using std::make_tuple;
+
+        static constexpr struct {
+            std::tuple<float, float> h, s, l;
+        } gTests[] = {
+            { make_tuple(-0.5f, 0.5f), make_tuple( 0.0f, 0.0f), make_tuple( 0.0f, 0.0f) },
+            { make_tuple( 0.0f, 0.0f), make_tuple(-1.0f, 1.0f), make_tuple( 0.0f, 0.0f) },
+            { make_tuple( 0.0f, 0.0f), make_tuple( 0.0f, 0.0f), make_tuple(-1.0f, 1.0f) },
+        };
+
+        const auto rect = SkRect::MakeWH(kWheelSize, kWheelSize);
+
+        canvas->drawColor(0xffcccccc);
+        SkPaint paint;
+
+        for (const auto& shader : fShaders) {
+            paint.setShader(shader);
+
+            for (const auto& tst: gTests) {
+                canvas->translate(0, kWheelSize * 0.1f);
+
+                const auto dh = (std::get<1>(tst.h) - std::get<0>(tst.h)) / (kSteps - 1),
+                           ds = (std::get<1>(tst.s) - std::get<0>(tst.s)) / (kSteps - 1),
+                           dl = (std::get<1>(tst.l) - std::get<0>(tst.l)) / (kSteps - 1);
+                auto h = std::get<0>(tst.h),
+                     s = std::get<0>(tst.s),
+                     l = std::get<0>(tst.l);
+                {
+                    SkAutoCanvasRestore acr(canvas, true);
+                    for (size_t i = 0; i < kSteps; ++i) {
+                        paint.setColorFilter(make_filter(h, s, l));
+                        canvas->translate(kWheelSize * 0.1f, 0);
+                        canvas->drawRect(rect, paint);
+                        canvas->translate(kWheelSize * 1.1f, 0);
+                        h += dh;
+                        s += ds;
+                        l += dl;
+                    }
+                }
+                canvas->translate(0, kWheelSize * 1.1f);
+            }
+            canvas->translate(0, kWheelSize * 0.1f);
+        }
+    }
+
+private:
+    static constexpr SkScalar kWheelSize  = 100;
+    static constexpr size_t   kSteps = 7;
+
+    static sk_sp<SkColorFilter> make_filter(float h, float s, float l) {
+        // These are roughly AE semantics.
+        const auto h_bias  = h,
+                   h_scale = 1.0f,
+                   s_bias  = std::max(s, 0.0f),
+                   s_scale = 1 - std::abs(s),
+                   l_bias  = std::max(l, 0.0f),
+                   l_scale = 1 - std::abs(l);
+
+        const float cm[20] = {
+            h_scale,       0,       0, 0, h_bias,
+                  0, s_scale,       0, 0, s_bias,
+                  0,       0, l_scale, 0, l_bias,
+                  0,       0,       0, 1,      0,
+        };
+
+        return SkColorFilters::HSLAMatrix(cm);
+    }
+
+    std::vector<sk_sp<SkShader>> fShaders;
+};
+
+DEF_GM(return new HSLColorFilterGM;)
diff --git a/include/core/SkColorFilter.h b/include/core/SkColorFilter.h
index 5d73774..ee01f62 100644
--- a/include/core/SkColorFilter.h
+++ b/include/core/SkColorFilter.h
@@ -149,6 +149,10 @@
     static sk_sp<SkColorFilter> Matrix(const SkColorMatrix&);
     static sk_sp<SkColorFilter> Matrix(const float rowMajor[20]);
 
+    // A version of Matrix which operates in HSLA space instead of RGBA.
+    // I.e. HSLA-to-RGBA(Matrix(RGBA-to-HSLA(input))).
+    static sk_sp<SkColorFilter> HSLAMatrix(const float rowMajor[20]);
+
     static sk_sp<SkColorFilter> LinearToSRGBGamma();
     static sk_sp<SkColorFilter> SRGBToLinearGamma();
     static sk_sp<SkColorFilter> Lerp(float t, sk_sp<SkColorFilter> dst, sk_sp<SkColorFilter> src);
diff --git a/src/core/SkColorFilter_Matrix.cpp b/src/core/SkColorFilter_Matrix.cpp
index 1e331bc..52d4b75 100644
--- a/src/core/SkColorFilter_Matrix.cpp
+++ b/src/core/SkColorFilter_Matrix.cpp
@@ -16,15 +16,21 @@
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkWriteBuffer.h"
 
-void SkColorFilter_Matrix::initState() {
-    const float* srcA = fMatrix + 15;
-    fFlags = (srcA[0] == 0 && srcA[1] == 0 && srcA[2] == 0 && srcA[3] == 1 && srcA[4] == 0)
-           ? kAlphaUnchanged_Flag : 0;
+static uint16_t ComputeFlags(const float matrix[20]) {
+    const float* srcA = matrix + 15;
+
+    return SkScalarNearlyZero (srcA[0])
+        && SkScalarNearlyZero (srcA[1])
+        && SkScalarNearlyZero (srcA[2])
+        && SkScalarNearlyEqual(srcA[3], 1)
+        && SkScalarNearlyZero (srcA[4])
+            ? SkColorFilter::kAlphaUnchanged_Flag : 0;
 }
 
-SkColorFilter_Matrix::SkColorFilter_Matrix(const float array[20]) {
+SkColorFilter_Matrix::SkColorFilter_Matrix(const float array[20], Domain domain)
+    : fFlags(ComputeFlags(array))
+    , fDomain(domain) {
     memcpy(fMatrix, array, 20 * sizeof(float));
-    this->initState();
 }
 
 uint32_t SkColorFilter_Matrix::getFlags() const {
@@ -34,14 +40,21 @@
 void SkColorFilter_Matrix::flatten(SkWriteBuffer& buffer) const {
     SkASSERT(sizeof(fMatrix)/sizeof(float) == 20);
     buffer.writeScalarArray(fMatrix, 20);
+
+    // RGBA flag
+    buffer.writeBool(fDomain == Domain::kRGBA);
 }
 
 sk_sp<SkFlattenable> SkColorFilter_Matrix::CreateProc(SkReadBuffer& buffer) {
     float matrix[20];
-    if (buffer.readScalarArray(matrix, 20)) {
-        return SkColorFilters::Matrix(matrix);
+    if (!buffer.readScalarArray(matrix, 20)) {
+        return nullptr;
     }
-    return nullptr;
+
+    auto   is_rgba = buffer.isVersionLT(SkPicturePriv::kMatrixColorFilterDomain_Version) ||
+                     buffer.readBool();
+    return is_rgba ? SkColorFilters::Matrix(matrix)
+                   : SkColorFilters::HSLAMatrix(matrix);
 }
 
 bool SkColorFilter_Matrix::onAsAColorMatrix(float matrix[20]) const {
@@ -53,10 +66,14 @@
 
 bool SkColorFilter_Matrix::onAppendStages(const SkStageRec& rec,
                                           bool /*shaderIsOpaque*/) const {
+    const bool hsla = fDomain == Domain::kHSLA;
+
     SkRasterPipeline* p = rec.fPipeline;
-    p->append(SkRasterPipeline::matrix_4x5, fMatrix);
-    p->append(SkRasterPipeline::clamp_0);
-    p->append(SkRasterPipeline::clamp_1);
+    if (hsla) { p->append(SkRasterPipeline::rgb_to_hsl); }
+                p->append(SkRasterPipeline::matrix_4x5, fMatrix);
+    if (hsla) { p->append(SkRasterPipeline::hsl_to_rgb); }
+                p->append(SkRasterPipeline::clamp_0);
+                p->append(SkRasterPipeline::clamp_1);
     return true;
 }
 
@@ -64,6 +81,10 @@
 #include "src/gpu/effects/generated/GrColorMatrixFragmentProcessor.h"
 std::unique_ptr<GrFragmentProcessor> SkColorFilter_Matrix::asFragmentProcessor(
         GrRecordingContext*, const GrColorSpaceInfo&) const {
+    if (fDomain == Domain::kHSLA) {
+        // TODO
+        return nullptr;
+    }
     return GrColorMatrixFragmentProcessor::Make(fMatrix,
                                                 /* premulInput = */ true,
                                                 /* clampRGBOutput = */ true,
@@ -74,15 +95,23 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
+static sk_sp<SkColorFilter> MakeMatrix(const float array[20],
+                                       SkColorFilter_Matrix::Domain domain) {
+    return sk_floats_are_finite(array, 20)
+        ? sk_make_sp<SkColorFilter_Matrix>(array, domain)
+        : nullptr;
+}
+
 sk_sp<SkColorFilter> SkColorFilters::Matrix(const float array[20]) {
-    if (!sk_floats_are_finite(array, 20)) {
-        return nullptr;
-    }
-    return sk_sp<SkColorFilter>(new SkColorFilter_Matrix(array));
+    return MakeMatrix(array, SkColorFilter_Matrix::Domain::kRGBA);
 }
 
 sk_sp<SkColorFilter> SkColorFilters::Matrix(const SkColorMatrix& cm) {
-    return Matrix(cm.fMat);
+    return MakeMatrix(cm.fMat, SkColorFilter_Matrix::Domain::kRGBA);
+}
+
+sk_sp<SkColorFilter> SkColorFilters::HSLAMatrix(const float array[20]) {
+    return MakeMatrix(array, SkColorFilter_Matrix::Domain::kHSLA);
 }
 
 void SkColorFilter_Matrix::RegisterFlattenables() {
diff --git a/src/core/SkColorFilter_Matrix.h b/src/core/SkColorFilter_Matrix.h
index bc2d1b4..bab111e 100644
--- a/src/core/SkColorFilter_Matrix.h
+++ b/src/core/SkColorFilter_Matrix.h
@@ -13,8 +13,9 @@
 
 class SkColorFilter_Matrix : public SkColorFilter {
 public:
-    SkColorFilter_Matrix() {}
-    explicit SkColorFilter_Matrix(const float array[20]);
+    enum class Domain : uint8_t { kRGBA, kHSLA };
+
+    explicit SkColorFilter_Matrix(const float array[20], Domain);
 
     uint32_t getFlags() const override;
 
@@ -34,9 +35,8 @@
     SkAlphaType onAlphaType() const override { return kUnpremul_SkAlphaType; }
 
     float       fMatrix[20];
-    uint32_t    fFlags;
-
-    void initState();
+    uint16_t    fFlags;
+    Domain      fDomain;
 
     typedef SkColorFilter INHERITED;
 };
diff --git a/src/core/SkPicturePriv.h b/src/core/SkPicturePriv.h
index c4c9e59..c14751c 100644
--- a/src/core/SkPicturePriv.h
+++ b/src/core/SkPicturePriv.h
@@ -72,27 +72,29 @@
     // V69: Clean up duplicated and redundant SkImageFilter related enums
     // V70: Image filters definitions hidden, registered names updated to include "Impl"
     // V71: Unify erode and dilate image filters
+    // V72: SkColorFilter_Matrix domain (rgba vs. hsla)
     enum Version {
-        kTileModeInBlurImageFilter_Version = 56,
-        kTileInfoInSweepGradient_Version   = 57,
-        k2PtConicalNoFlip_Version          = 58,
+        kTileModeInBlurImageFilter_Version  = 56,
+        kTileInfoInSweepGradient_Version    = 57,
+        k2PtConicalNoFlip_Version           = 58,
         kRemovePictureImageFilterLocalSpace = 59,
-        kRemoveHeaderFlags_Version         = 60,
-        kTwoColorDrawShadow_Version        = 61,
-        kDontNegateImageSize_Version       = 62,
-        kStoreImageBounds_Version          = 63,
-        kRemoveOccluderFromBlurMaskFilter  = 64,
-        kFloat4PaintColor_Version          = 65,
-        kSaveBehind_Version                = 66,
-        kSerializeFonts_Version            = 67,
-        kPaintDoesntSerializeFonts_Version = 68,
-        kCleanupImageFilterEnums_Version   = 69,
-        kHideImageFilterImpls_Version      = 70,
-        kUnifyErodeDilateImpls_Version     = 71,
+        kRemoveHeaderFlags_Version          = 60,
+        kTwoColorDrawShadow_Version         = 61,
+        kDontNegateImageSize_Version        = 62,
+        kStoreImageBounds_Version           = 63,
+        kRemoveOccluderFromBlurMaskFilter   = 64,
+        kFloat4PaintColor_Version           = 65,
+        kSaveBehind_Version                 = 66,
+        kSerializeFonts_Version             = 67,
+        kPaintDoesntSerializeFonts_Version  = 68,
+        kCleanupImageFilterEnums_Version    = 69,
+        kHideImageFilterImpls_Version       = 70,
+        kUnifyErodeDilateImpls_Version      = 71,
+        kMatrixColorFilterDomain_Version    = 72,
 
         // Only SKPs within the min/current picture version range (inclusive) can be read.
         kMin_Version     = kTileModeInBlurImageFilter_Version,
-        kCurrent_Version = kUnifyErodeDilateImpls_Version
+        kCurrent_Version = kMatrixColorFilterDomain_Version
     };
 
     static_assert(kMin_Version <= 62, "Remove kFontAxes_bad from SkFontDescriptor.cpp");