1 /*
2  * Copyright 2019 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 "gm/gm.h"
9 #include "include/core/SkCanvas.h"
10 #include "include/core/SkColor.h"
11 #include "include/core/SkColorFilter.h"
12 #include "include/core/SkImage.h"
13 #include "include/core/SkMatrix.h"
14 #include "include/core/SkPaint.h"
15 #include "include/core/SkPoint.h"
16 #include "include/core/SkRect.h"
17 #include "include/core/SkRefCnt.h"
18 #include "include/core/SkScalar.h"
19 #include "include/core/SkShader.h"
20 #include "include/core/SkSize.h"
21 #include "include/core/SkString.h"
22 #include "include/core/SkTileMode.h"
23 #include "include/core/SkTypes.h"
24 #include "include/effects/SkGradientShader.h"
25 #include "include/effects/SkLumaColorFilter.h"
26 #include "include/effects/SkRuntimeEffect.h"
27 #include "tools/Resources.h"
28 
29 #include <math.h>
30 
31 // A tint filter maps colors to a given range (gradient), based on the input luminance:
32 //
33 //   c' = lerp(lo, hi, luma(c))
34 //
35 // TODO: move to public headers/API?
36 //
MakeTintColorFilter(SkColor lo,SkColor hi)37 static sk_sp<SkColorFilter> MakeTintColorFilter(SkColor lo, SkColor hi) {
38     const auto r_lo = SkColorGetR(lo),
39     g_lo = SkColorGetG(lo),
40     b_lo = SkColorGetB(lo),
41     a_lo = SkColorGetA(lo),
42     r_hi = SkColorGetR(hi),
43     g_hi = SkColorGetG(hi),
44     b_hi = SkColorGetB(hi),
45     a_hi = SkColorGetA(hi);
46 
47     // We map component-wise:
48     //
49     //   r' = lo.r + (hi.r - lo.r) * luma
50     //   g' = lo.g + (hi.g - lo.g) * luma
51     //   b' = lo.b + (hi.b - lo.b) * luma
52     //   a' = lo.a + (hi.a - lo.a) * luma
53     //
54     // The input luminance is stored in the alpha channel
55     // (and RGB are cleared -- see SkLumaColorFilter). Thus:
56     const float tint_matrix[] = {
57         0, 0, 0, (r_hi - r_lo) / 255.0f, SkIntToScalar(r_lo) / 255.0f,
58         0, 0, 0, (g_hi - g_lo) / 255.0f, SkIntToScalar(g_lo) / 255.0f,
59         0, 0, 0, (b_hi - b_lo) / 255.0f, SkIntToScalar(b_lo) / 255.0f,
60         0, 0, 0, (a_hi - a_lo) / 255.0f, SkIntToScalar(a_lo) / 255.0f,
61     };
62 
63     return SkColorFilters::Matrix(tint_matrix)->makeComposed(SkLumaColorFilter::Make());
64 }
65 
66 namespace {
67 
68 class MixerCFGM final : public skiagm::GM {
69 public:
MixerCFGM(const SkSize & tileSize,size_t tileCount,bool runtime)70     MixerCFGM(const SkSize& tileSize, size_t tileCount, bool runtime)
71         : fTileSize(tileSize)
72         , fTileCount(tileCount)
73         , fRuntime(runtime) {}
74 
75 protected:
onShortName()76     SkString onShortName() override {
77         return fRuntime ? SkString("mixerCF_runtime")
78                         : SkString("mixerCF");
79     }
80 
onISize()81     SkISize onISize() override {
82         return SkISize::Make(fTileSize.width()  * 1.2f * fTileCount,
83                              fTileSize.height() * 1.2f * 3);         // 3 rows
84     }
85 
onDraw(SkCanvas * canvas)86     void onDraw(SkCanvas* canvas) override {
87         SkPaint paint;
88 
89         const SkColor gradient_colors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, SK_ColorRED };
90         paint.setShader(SkGradientShader::MakeSweep(fTileSize.width()  / 2,
91                                                     fTileSize.height() / 2,
92                                                     gradient_colors, nullptr,
93                                                     SK_ARRAY_COUNT(gradient_colors)));
94 
95         auto cf0 = MakeTintColorFilter(0xff300000, 0xffa00000);  // red tint
96         auto cf1 = MakeTintColorFilter(0xff003000, 0xff00a000);  // green tint
97 
98         this->mixRow(canvas, paint, nullptr,     cf1);
99         this->mixRow(canvas, paint,     cf0, nullptr);
100         this->mixRow(canvas, paint,     cf0,     cf1);
101     }
102 
103 private:
104     const SkSize fTileSize;
105     const size_t fTileCount;
106     const bool   fRuntime;
107 
mixRow(SkCanvas * canvas,SkPaint & paint,sk_sp<SkColorFilter> cf0,sk_sp<SkColorFilter> cf1)108     void mixRow(SkCanvas* canvas, SkPaint& paint,
109                 sk_sp<SkColorFilter> cf0, sk_sp<SkColorFilter> cf1) {
110         sk_sp<SkRuntimeEffect> effect;
111         if (fRuntime) {
112             const char* sksl = R"(
113                 uniform shader cf0;
114                 uniform shader cf1;
115                 uniform half t;
116                 half4 main() {
117                     return mix(sample(cf0), sample(cf1), t);
118                 }
119             )";
120             effect = std::get<0>(SkRuntimeEffect::Make(SkString(sksl)));
121             SkASSERT(effect);
122         }
123 
124         // We cycle through paint colors on each row, to test how the paint color flows through
125         // the color-filter network
126         const SkColor4f paintColors[] = {
127             { 1.0f, 1.0f, 1.0f, 1.0f },  // Opaque white
128             { 1.0f, 1.0f, 1.0f, 0.5f },  // Translucent white
129             { 0.5f, 0.5f, 1.0f, 1.0f },  // Opaque pale blue
130             { 0.5f, 0.5f, 1.0f, 0.5f },  // Translucent pale blue
131         };
132 
133         canvas->translate(0, fTileSize.height() * 0.1f);
134         {
135             SkAutoCanvasRestore arc(canvas, true);
136             for (size_t i = 0; i < fTileCount; ++i) {
137                 paint.setColor4f(paintColors[i % SK_ARRAY_COUNT(paintColors)]);
138                 float t = static_cast<float>(i) / (fTileCount - 1);
139                 if (fRuntime) {
140                     sk_sp<SkColorFilter> children[] = { cf0, cf1 };
141                     sk_sp<SkData> inputs = SkData::MakeWithCopy(&t, sizeof(t));
142                     paint.setColorFilter(effect->makeColorFilter(inputs, children, 2));
143                 } else {
144                     paint.setColorFilter(SkColorFilters::Lerp(t, cf0, cf1));
145                 }
146                 canvas->translate(fTileSize.width() * 0.1f, 0);
147                 canvas->drawRect(SkRect::MakeWH(fTileSize.width(), fTileSize.height()), paint);
148                 canvas->translate(fTileSize.width() * 1.1f, 0);
149             }
150         }
151         canvas->translate(0, fTileSize.height() * 1.1f);
152     }
153 
154     using INHERITED = skiagm::GM;
155 };
156 
157 } // namespace
158 
159 DEF_GM( return new MixerCFGM(SkSize::Make(200, 250), 5, false); )
160 DEF_GM( return new MixerCFGM(SkSize::Make(200, 250), 5, true); )
161