Flutter Engine
The Flutter Engine
hsl.cpp
Go to the documentation of this file.
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 "gm/gm.h"
12#include "include/core/SkFont.h"
16#include "tools/DecodeUtils.h"
17#include "tools/Resources.h"
18#include "tools/ToolUtils.h"
20
21// Hue, Saturation, Color, and Luminosity blend modes are oddballs.
22// They nominally convert their inputs to unpremul, then to HSL, then
23// mix-and-match H,S,and L from Src and Dst, then convert back, then premul.
24//
25// In practice that's slow, so instead we pick the color with the correct
26// Hue, and then (approximately) apply the other's Saturation and/or Luminosity.
27// This isn't just an optimization... it's how the modes are specified.
28//
29// Each mode's name describes the Src H,S,L components to keep, taking the
30// others from Dst, where Color == Hue + Saturation. Color and Luminosity
31// are each other's complements; Hue and Saturation have no complement.
32//
33// All these modes were originally designed to operate on gamma-encoded values,
34// and that's what everyone's used to seeing. It's unclear wehther they make
35// any sense in a gamma-correct world. They certainly won't look at all similar.
36//
37// We have had many inconsistent implementations of these modes.
38// This GM tries to demonstrate unambigously how they should work.
39//
40// To go along with our inconsistent implementations, there are two slightly
41// inconsistent specifications of how to perform these blends,
42// web: https://www.w3.org/TR/compositing-1/#blendingnonseparable
43// KHR: https://www.khronos.org/registry/OpenGL/extensions/KHR/KHR_blend_equation_advanced.txt
44// It looks like these are meant to be identical, but they disagree on at least ClipColor().
45//
46// I think the KHR version is just wrong... it produces values >1. So we use the web version.
47
48static float min(float r, float g, float b) { return std::min(r, std::min(g, b)); }
49static float max(float r, float g, float b) { return std::max(r, std::max(g, b)); }
50
51static float sat(float r, float g, float b) { return max(r,g,b) - min(r,g,b); }
52static float lum(float r, float g, float b) { return r*0.30f + g*0.59f + b*0.11f; }
53
54// The two SetSat() routines in the specs look different, but they're logically equivalent.
55// Both map the minimum channel to 0, maximum to s, and scale the middle proportionately.
56// The KHR version has done a better job at simplifying its math, so we use it here.
57static void set_sat(float* r, float* g, float* b, float s) {
58 float mn = min(*r,*g,*b),
59 mx = max(*r,*g,*b);
60 auto channel = [=](float c) {
61 return mx == mn ? 0
62 : (c - mn) * s / (mx - mn);
63 };
64 *r = channel(*r);
65 *g = channel(*g);
66 *b = channel(*b);
67}
68static void clip_color(float* r, float* g, float* b) {
69 float l = lum(*r,*g,*b),
70 mn = min(*r,*g,*b),
71 mx = max(*r,*g,*b);
72 auto clip = [=](float c) {
73 if (mn < 0) { c = l + (c - l) * ( l) / (l - mn); }
74 if (mx > 1) { c = l + (c - l) * (1 - l) / (mx - l); }
75 SkASSERT(-0.0001f < c); // This may end up very slightly negative...
76 SkASSERT( c <= 1);
77 return c;
78 };
79 *r = clip(*r);
80 *g = clip(*g);
81 *b = clip(*b);
82}
83static void set_lum(float* r, float* g, float* b, float l) {
84 float diff = l - lum(*r,*g,*b);
85 *r += diff;
86 *g += diff;
87 *b += diff;
88 clip_color(r,g,b);
89}
90
91
92static void hue(float dr, float dg, float db,
93 float* sr, float* sg, float* sb) {
94 // Hue of Src, Saturation and Luminosity of Dst.
95 float R = *sr,
96 G = *sg,
97 B = *sb;
98 set_sat(&R,&G,&B, sat(dr,dg,db));
99 set_lum(&R,&G,&B, lum(dr,dg,db));
100 *sr = R;
101 *sg = G;
102 *sb = B;
103}
104
105static void saturation(float dr, float dg, float db,
106 float* sr, float* sg, float* sb) {
107 // Saturation of Src, Hue and Luminosity of Dst
108 float R = dr,
109 G = dg,
110 B = db;
111 set_sat(&R,&G,&B, sat(*sr,*sg,*sb));
112 set_lum(&R,&G,&B, lum( dr, dg, db)); // This may seem redundant, but it is not.
113 *sr = R;
114 *sg = G;
115 *sb = B;
116}
117
118static void color(float dr, float dg, float db,
119 float* sr, float* sg, float* sb) {
120 // Hue and Saturation of Src, Luminosity of Dst.
121 float R = *sr,
122 G = *sg,
123 B = *sb;
124 set_lum(&R,&G,&B, lum(dr,dg,db));
125 *sr = R;
126 *sg = G;
127 *sb = B;
128}
129
130static void luminosity(float dr, float dg, float db,
131 float* sr, float* sg, float* sb) {
132 // Luminosity of Src, Hue and Saturation of Dst.
133 float R = dr,
134 G = dg,
135 B = db;
136 set_lum(&R,&G,&B, lum(*sr,*sg,*sb));
137 *sr = R;
138 *sg = G;
139 *sb = B;
140}
141
143 void (*mode)(float,float,float, float*,float*,float*)) {
144
145 SkASSERT(SkColorGetA(dst) == 0xff
146 && SkColorGetA(src) == 0xff); // Not fundamental, just simplifying for this GM.
147
148 SkColor4f d = SkColor4f::FromColor(dst),
149 s = SkColor4f::FromColor(src);
150
151 mode( d.fR, d.fG, d.fB,
152 &s.fR, &s.fG, &s.fB);
153
154 return s.toSkColor();
155}
156
157DEF_SIMPLE_GM(hsl, canvas, 600, 100) {
160
161 const char* comment = "HSL blend modes are correct when you see no circles in the squares.";
162 canvas->drawString(comment, 10,10, font, paint);
163
164 // Just to keep things reaaaal simple, we'll only use opaque colors.
165 SkPaint bg, fg;
166 bg.setColor(0xff00ff00); // Fully-saturated bright green, H = 120°, S = 100%, L = 50%.
167 fg.setColor(0xff7f3f7f); // Partly-saturated dim magenta, H = 300°, S = ~33%, L = ~37%.
168
169 struct {
171 void (*reference)(float,float,float, float*,float*,float*);
172 } tests[] = {
173 { SkBlendMode::kSrc, nullptr },
174 { SkBlendMode::kDst, nullptr },
179 };
180 for (auto test : tests) {
181 canvas->drawRect({20,20,80,80}, bg);
182
183 fg.setBlendMode(test.mode);
184 canvas->drawRect({20,20,80,80}, fg);
185
186 if (test.reference) {
187 SkPaint ref;
188 ref.setColor(blend(bg.getColor(), fg.getColor(), test.reference));
189 canvas->drawCircle(50,50, 20, ref);
190 }
191
192 canvas->drawString(SkBlendMode_Name(test.mode), 20, 90, font, paint);
193
194 canvas->translate(100,0);
195 }
196}
197
199
200// Trying to match sample images on https://www.w3.org/TR/compositing-1/#blendingnonseparable
201//
203 SkColor colors[] = {
204 0xFF00CCCC, 0xFF0000CC, 0xFFCC00CC, 0xFFCC0000, 0xFFCCCC00, 0xFF00CC00,
205 };
206 SkPoint pts[] = {{0, 0}, {width, 0}};
207
210}
211
212DEF_SIMPLE_GM(HSL_duck, canvas, 1110, 620) {
213 auto src = ToolUtils::GetResourceAsImage("images/ducky.png");
214 auto dst = make_grad(src->width());
215 SkRect r = SkRect::MakeIWH(src->width(), src->height());
216
217 canvas->translate(10, 50);
218 canvas->scale(0.5f, 0.5f);
219
220 const struct {
222 const char* fName;
223 } recs[] = {
224 { SkBlendMode::kHue, "Hue" },
225 { SkBlendMode::kSaturation, "Saturation" },
226 { SkBlendMode::kColor, "Color" },
227 { SkBlendMode::kLuminosity, "Luminosity" },
228 };
229
231 font.setSize(40);
233
234 canvas->save();
235 for (auto [_, name] : recs) {
236 canvas->drawSimpleText(name, strlen(name), SkTextEncoding::kUTF8, 150, -20,
237 font, SkPaint());
238 canvas->translate(r.width() + 10, 0);
239 }
240 canvas->restore();
241
242 for (SkScalar src_a : {1.0f, 0.5f}) {
243 canvas->save();
244 for (auto [mode, _] : recs) {
245 SkPaint p;
246 p.setShader(dst);
247 canvas->drawRect(r, p); // bg
248
249 p.setShader(nullptr);
250 p.setBlendMode(mode);
251 p.setAlphaf(src_a);
252 canvas->drawImageRect(src, r, SkSamplingOptions(), &p);
253
254 canvas->translate(r.width() + 10, 0);
255 }
256 SkString str;
257 str.printf("alpha %g", src_a);
258 canvas->drawSimpleText(str.c_str(), str.size(), SkTextEncoding::kUTF8, 10, r.height()/2,
259 font, SkPaint());
260
261 canvas->restore();
262 canvas->translate(0, r.height() + 10);
263 }
264}
static BlurTest tests[]
Definition: BlurTest.cpp:84
const char * fName
#define SkASSERT(cond)
Definition: SkAssert.h:116
SK_API const char * SkBlendMode_Name(SkBlendMode blendMode)
SkBlendMode
Definition: SkBlendMode.h:38
@ kSaturation
saturation of source with hue and luminosity of destination
@ kHue
hue of source with saturation and luminosity of destination
@ kLuminosity
luminosity of source with hue and saturation of destination
@ kColor
hue and saturation of source with luminosity of destination
uint32_t SkColor
Definition: SkColor.h:37
#define SkColorGetA(color)
Definition: SkColor.h:61
@ kUTF8
uses bytes to represent UTF-8 or ASCII
static SkPath clip(const SkPath &path, const SkHalfPlane &plane)
Definition: SkPath.cpp:3892
Definition: SkFont.h:35
@ kAntiAlias
may have transparent pixels on glyph edges
static sk_sp< SkShader > MakeLinear(const SkPoint pts[2], const SkColor colors[], const SkScalar pos[], int count, SkTileMode mode, uint32_t flags=0, const SkMatrix *localMatrix=nullptr)
void setColor(SkColor color)
Definition: SkPaint.cpp:119
SkColor getColor() const
Definition: SkPaint.h:225
void setBlendMode(SkBlendMode mode)
Definition: SkPaint.cpp:151
void printf(const char format[],...) SK_PRINTF_LIKE(2
Definition: SkString.cpp:534
size_t size() const
Definition: SkString.h:131
const char * c_str() const
Definition: SkString.h:133
const Paint & paint
Definition: color_source.cc:38
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE auto & d
Definition: main.cc:19
float SkScalar
Definition: extension.cpp:12
static bool b
struct MyStruct s
static void set_sat(float *r, float *g, float *b, float s)
Definition: hsl.cpp:57
static SkColor blend(SkColor dst, SkColor src, void(*mode)(float, float, float, float *, float *, float *))
Definition: hsl.cpp:142
static void color(float dr, float dg, float db, float *sr, float *sg, float *sb)
Definition: hsl.cpp:118
static float sat(float r, float g, float b)
Definition: hsl.cpp:51
static void luminosity(float dr, float dg, float db, float *sr, float *sg, float *sb)
Definition: hsl.cpp:130
static void hue(float dr, float dg, float db, float *sr, float *sg, float *sb)
Definition: hsl.cpp:92
static void saturation(float dr, float dg, float db, float *sr, float *sg, float *sb)
Definition: hsl.cpp:105
static float max(float r, float g, float b)
Definition: hsl.cpp:49
DEF_SIMPLE_GM(hsl, canvas, 600, 100)
Definition: hsl.cpp:157
static void clip_color(float *r, float *g, float *b)
Definition: hsl.cpp:68
static sk_sp< SkShader > make_grad(SkScalar width)
Definition: hsl.cpp:202
static float lum(float r, float g, float b)
Definition: hsl.cpp:52
static float min(float r, float g, float b)
Definition: hsl.cpp:48
static void set_lum(float *r, float *g, float *b, float l)
Definition: hsl.cpp:83
#define R(r)
#define B
PODArray< SkColor > colors
Definition: SkRecords.h:276
SkFont DefaultPortableFont()
sk_sp< SkImage > GetResourceAsImage(const char *resource)
Definition: DecodeUtils.h:25
DEF_SWITCHES_START aot vmservice shared library name
Definition: switches.h:32
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive mode
Definition: switches.h:228
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
font
Font Metadata and Metrics.
dst
Definition: cp.py:12
SkSamplingOptions(SkFilterMode::kLinear))
int32_t width
Definition: SkMD5.cpp:125
static SkRect MakeIWH(int w, int h)
Definition: SkRect.h:623
constexpr float height() const
Definition: SkRect.h:769
constexpr float width() const
Definition: SkRect.h:762
SkBlendMode fMode
Definition: xfermodes.cpp:52