Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
SkGradientBaseShader.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2022 Google LLC
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
9
14#include "include/core/SkData.h"
26#include "src/base/SkVx.h"
37
38#include <algorithm>
39#include <cmath>
40#include <optional>
41#include <utility>
42
43using namespace skia_private;
44
46 // Bits 29:31 used for various boolean flags
47 kHasPosition_GSF = 0x80000000,
49 kHasColorSpace_GSF = 0x20000000,
50
51 // Bits 12:28 unused
52
53 // Bits 8:11 for fTileMode
56
57 // Bits 4:7 for fInterpolation.fColorSpace
60
61 // Bits 1:3 for fInterpolation.fHueMethod
64
65 // Bit 0 for fInterpolation.fInPremul
67};
68
74
76 uint32_t flags = 0;
77 if (fPositions) {
79 }
80 sk_sp<SkData> colorSpaceData = fColorSpace ? fColorSpace->serialize() : nullptr;
81 if (colorSpaceData) {
83 }
86 }
87 SkASSERT(static_cast<uint32_t>(fTileMode) <= kTileModeMask_GSF);
88 flags |= ((uint32_t)fTileMode << kTileModeShift_GSF);
93
94 buffer.writeUInt(flags);
95
96 // If we injected implicit first/last stops at construction time, omit those when serializing:
97 int colorCount = fColorCount;
98 const SkColor4f* colors = fColors;
99 const SkScalar* positions = fPositions;
101 colorCount--;
102 colors++;
103 if (positions) {
104 positions++;
105 }
106 }
108 colorCount--;
109 }
110
111 buffer.writeColor4fArray(colors, colorCount);
112 if (colorSpaceData) {
113 buffer.writeDataAsByteArray(colorSpaceData.get());
114 }
115 if (positions) {
116 buffer.writeScalarArray(positions, colorCount);
117 }
118}
119
120template <int N, typename T, bool MEM_MOVE>
122 if (!buffer.validateCanReadN<T>(count)) {
123 return false;
124 }
125
126 array->resize_back(count);
127 return true;
128}
129
131 SkMatrix* legacyLocalMatrix) {
132 // New gradient format. Includes floating point color, color space, densely packed flags
133 uint32_t flags = buffer.readUInt();
134
136
143
144 fColorCount = buffer.getArrayCount();
145
146 if (!(validate_array(buffer, fColorCount, &fColorStorage) &&
147 buffer.readColor4fArray(fColorStorage.begin(), fColorCount))) {
148 return false;
149 }
150 fColors = fColorStorage.begin();
151
153 sk_sp<SkData> data = buffer.readByteArrayAsData();
154 fColorSpace = data ? SkColorSpace::Deserialize(data->data(), data->size()) : nullptr;
155 } else {
156 fColorSpace = nullptr;
157 }
159 if (!(validate_array(buffer, fColorCount, &fPositionStorage) &&
160 buffer.readScalarArray(fPositionStorage.begin(), fColorCount))) {
161 return false;
162 }
163 fPositions = fPositionStorage.begin();
164 } else {
165 fPositions = nullptr;
166 }
169 buffer.readMatrix(legacyLocalMatrix);
170 } else {
171 *legacyLocalMatrix = SkMatrix::I();
172 }
173 return buffer.isValid();
174}
175
176////////////////////////////////////////////////////////////////////////////////////////////
177
179 : fPtsToUnit(ptsToUnit)
180 , fColorSpace(desc.fColorSpace ? desc.fColorSpace : SkColorSpace::MakeSRGB())
181 , fFirstStopIsImplicit(false)
182 , fLastStopIsImplicit(false)
183 , fColorsAreOpaque(true) {
184 fPtsToUnit.getType(); // Precache so reads are threadsafe.
185 SkASSERT(desc.fColorCount > 1);
186
187 fInterpolation = desc.fInterpolation;
188
189 SkASSERT((unsigned)desc.fTileMode < kSkTileModeCount);
190 fTileMode = desc.fTileMode;
191
192 /* Note: we let the caller skip the first and/or last position.
193 i.e. pos[0] = 0.3, pos[1] = 0.7
194 In these cases, we insert entries to ensure that the final data
195 will be bracketed by [0, 1].
196 i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1
197
198 Thus colorCount (the caller's value, and fColorCount (our value) may
199 differ by up to 2. In the above example:
200 colorCount = 2
201 fColorCount = 4
202 */
203 fColorCount = desc.fColorCount;
204 // check if we need to add in start and/or end position/colors
205 if (desc.fPositions) {
206 fFirstStopIsImplicit = desc.fPositions[0] != 0;
207 fLastStopIsImplicit = desc.fPositions[desc.fColorCount - 1] != SK_Scalar1;
209 }
210
211 size_t storageSize =
212 fColorCount * (sizeof(SkColor4f) + (desc.fPositions ? sizeof(SkScalar) : 0));
213 fColors = reinterpret_cast<SkColor4f*>(fStorage.reset(storageSize));
214 fPositions = desc.fPositions ? reinterpret_cast<SkScalar*>(fColors + fColorCount) : nullptr;
215
216 // Now copy over the colors, adding the duplicates at t=0 and t=1 as needed
217 SkColor4f* colors = fColors;
219 *colors++ = desc.fColors[0];
220 }
221 for (int i = 0; i < desc.fColorCount; ++i) {
222 colors[i] = desc.fColors[i];
223 fColorsAreOpaque = fColorsAreOpaque && (desc.fColors[i].fA == 1);
224 }
226 colors += desc.fColorCount;
227 *colors = desc.fColors[desc.fColorCount - 1];
228 }
229
230 if (desc.fPositions) {
231 SkScalar prev = 0;
232 SkScalar* positions = fPositions;
233 *positions++ = prev; // force the first pos to 0
234
235 int startIndex = fFirstStopIsImplicit ? 0 : 1;
236 int count = desc.fColorCount + fLastStopIsImplicit;
237
238 bool uniformStops = true;
239 const SkScalar uniformStep = desc.fPositions[startIndex] - prev;
240 for (int i = startIndex; i < count; i++) {
241 // Pin the last value to 1.0, and make sure pos is monotonic.
242 auto curr = (i == desc.fColorCount) ? 1 : SkTPin(desc.fPositions[i], prev, 1.0f);
243 uniformStops &= SkScalarNearlyEqual(uniformStep, curr - prev);
244
245 *positions++ = prev = curr;
246 }
247
248 // If the stops are uniform, treat them as implicit.
249 if (uniformStops) {
250 fPositions = nullptr;
251 }
252 }
253}
254
256
258 size_t stop,
259 SkPMColor4f Fs,
260 SkPMColor4f Bs) {
261 (ctx->fs[0])[stop] = Fs.fR;
262 (ctx->fs[1])[stop] = Fs.fG;
263 (ctx->fs[2])[stop] = Fs.fB;
264 (ctx->fs[3])[stop] = Fs.fA;
265
266 (ctx->bs[0])[stop] = Bs.fR;
267 (ctx->bs[1])[stop] = Bs.fG;
268 (ctx->bs[2])[stop] = Bs.fB;
269 (ctx->bs[3])[stop] = Bs.fA;
270}
271
273 add_stop_color(ctx, stop, {0, 0, 0, 0}, color);
274}
275
276// Calculate a factor F and a bias B so that color = F*t + B when t is in range of
277// the stop. Assume that the distance between stops is 1/gapCount.
279 float gapCount,
280 size_t stop,
281 SkPMColor4f c_l,
282 SkPMColor4f c_r) {
283 // Clankium's GCC 4.9 targeting ARMv7 is barfing when we use Sk4f math here, so go scalar...
284 SkPMColor4f Fs = {
285 (c_r.fR - c_l.fR) * gapCount,
286 (c_r.fG - c_l.fG) * gapCount,
287 (c_r.fB - c_l.fB) * gapCount,
288 (c_r.fA - c_l.fA) * gapCount,
289 };
290 SkPMColor4f Bs = {
291 c_l.fR - Fs.fR * (stop / gapCount),
292 c_l.fG - Fs.fG * (stop / gapCount),
293 c_l.fB - Fs.fB * (stop / gapCount),
294 c_l.fA - Fs.fA * (stop / gapCount),
295 };
296 add_stop_color(ctx, stop, Fs, Bs);
297}
298
299// For each stop we calculate a bias B and a scale factor F, such that
300// for any t between stops n and n+1, the color we want is B[n] + F[n]*t.
302 size_t stop,
303 float t_l,
304 float c_scale,
305 SkPMColor4f c_l,
306 SkPMColor4f c_r) {
307 // See note about Clankium's old compiler in init_stop_evenly().
308 SkPMColor4f Fs = {
309 (c_r.fR - c_l.fR) * c_scale,
310 (c_r.fG - c_l.fG) * c_scale,
311 (c_r.fB - c_l.fB) * c_scale,
312 (c_r.fA - c_l.fA) * c_scale,
313 };
314 SkPMColor4f Bs = {
315 c_l.fR - Fs.fR * t_l,
316 c_l.fG - Fs.fG * t_l,
317 c_l.fB - Fs.fB * t_l,
318 c_l.fA - Fs.fA * t_l,
319 };
320 ctx->ts[stop] = t_l;
321 add_stop_color(ctx, stop, Fs, Bs);
322}
323
325 SkArenaAlloc* alloc,
326 const SkPMColor4f* pmColors,
327 const SkScalar* positions,
328 int count) {
329 // The two-stop case with stops at 0 and 1.
330 if (count == 2 && positions == nullptr) {
331 const SkPMColor4f c_l = pmColors[0], c_r = pmColors[1];
332
333 // See F and B below.
335 (skvx::float4::Load(c_r.vec()) - skvx::float4::Load(c_l.vec())).store(ctx->f);
336 (skvx::float4::Load(c_l.vec())).store(ctx->b);
337
338 p->append(SkRasterPipelineOp::evenly_spaced_2_stop_gradient, ctx);
339 } else {
340 auto* ctx = alloc->make<SkRasterPipeline_GradientCtx>();
341
342 // Note: In order to handle clamps in search, the search assumes a stop conceptully placed
343 // at -inf. Therefore, the max number of stops is fColorCount+1.
344 for (int i = 0; i < 4; i++) {
345 // Allocate at least at for the AVX2 gather from a YMM register.
346 ctx->fs[i] = alloc->makeArray<float>(std::max(count + 1, 8));
347 ctx->bs[i] = alloc->makeArray<float>(std::max(count + 1, 8));
348 }
349
350 if (positions == nullptr) {
351 // Handle evenly distributed stops.
352
353 size_t stopCount = count;
354 float gapCount = stopCount - 1;
355
356 SkPMColor4f c_l = pmColors[0];
357 for (size_t i = 0; i < stopCount - 1; i++) {
358 SkPMColor4f c_r = pmColors[i + 1];
359 init_stop_evenly(ctx, gapCount, i, c_l, c_r);
360 c_l = c_r;
361 }
362 add_const_color(ctx, stopCount - 1, c_l);
363
364 ctx->stopCount = stopCount;
365 p->append(SkRasterPipelineOp::evenly_spaced_gradient, ctx);
366 } else {
367 // Handle arbitrary stops.
368
369 ctx->ts = alloc->makeArray<float>(count + 1);
370
371 // Remove the default stops inserted by SkGradientBaseShader::SkGradientBaseShader
372 // because they are naturally handled by the search method.
373 int firstStop;
374 int lastStop;
375 if (count > 2) {
376 firstStop = pmColors[0] != pmColors[1] ? 0 : 1;
377 lastStop = pmColors[count - 2] != pmColors[count - 1] ? count - 1 : count - 2;
378 } else {
379 firstStop = 0;
380 lastStop = 1;
381 }
382
383 size_t stopCount = 0;
384 float t_l = positions[firstStop];
385 SkPMColor4f c_l = pmColors[firstStop];
386 add_const_color(ctx, stopCount++, c_l);
387 // N.B. lastStop is the index of the last stop, not one after.
388 for (int i = firstStop; i < lastStop; i++) {
389 float t_r = positions[i + 1];
390 SkPMColor4f c_r = pmColors[i + 1];
391 SkASSERT(t_l <= t_r);
392 if (t_l < t_r) {
393 float c_scale = sk_ieee_float_divide(1, t_r - t_l);
394 if (SkIsFinite(c_scale)) {
395 init_stop_pos(ctx, stopCount, t_l, c_scale, c_l, c_r);
396 stopCount += 1;
397 }
398 }
399 t_l = t_r;
400 c_l = c_r;
401 }
402
403 ctx->ts[stopCount] = t_l;
404 add_const_color(ctx, stopCount++, c_l);
405
406 ctx->stopCount = stopCount;
407 p->append(SkRasterPipelineOp::gradient, ctx);
408 }
409 }
410}
411
413 SkArenaAlloc* alloc,
414 bool colorsAreOpaque,
415 const Interpolation& interpolation,
416 const SkColorSpace* intermediateColorSpace,
417 const SkColorSpace* dstColorSpace) {
418 using ColorSpace = Interpolation::ColorSpace;
419 bool colorIsPremul = static_cast<bool>(interpolation.fInPremul);
420
421 // If we interpolated premul colors in any of the special color spaces, we need to unpremul
422 if (colorIsPremul && !colorsAreOpaque) {
423 switch (interpolation.fColorSpace) {
424 case ColorSpace::kLab:
425 case ColorSpace::kOKLab:
426 case ColorSpace::kOKLabGamutMap:
427 p->append(SkRasterPipelineOp::unpremul);
428 colorIsPremul = false;
429 break;
430 case ColorSpace::kLCH:
431 case ColorSpace::kOKLCH:
432 case ColorSpace::kOKLCHGamutMap:
433 case ColorSpace::kHSL:
434 case ColorSpace::kHWB:
435 p->append(SkRasterPipelineOp::unpremul_polar);
436 colorIsPremul = false;
437 break;
438 default:
439 break;
440 }
441 }
442
443 // Convert colors in exotic spaces back to their intermediate SkColorSpace
444 switch (interpolation.fColorSpace) {
445 case ColorSpace::kLab: p->append(SkRasterPipelineOp::css_lab_to_xyz); break;
446 case ColorSpace::kOKLab: p->append(SkRasterPipelineOp::css_oklab_to_linear_srgb); break;
447 case ColorSpace::kOKLabGamutMap:
448 p->append(SkRasterPipelineOp::css_oklab_gamut_map_to_linear_srgb);
449 break;
450 case ColorSpace::kLCH: p->append(SkRasterPipelineOp::css_hcl_to_lab);
451 p->append(SkRasterPipelineOp::css_lab_to_xyz); break;
452 case ColorSpace::kOKLCH: p->append(SkRasterPipelineOp::css_hcl_to_lab);
453 p->append(SkRasterPipelineOp::css_oklab_to_linear_srgb); break;
454 case ColorSpace::kOKLCHGamutMap:
455 p->append(SkRasterPipelineOp::css_hcl_to_lab);
456 p->append(SkRasterPipelineOp::css_oklab_gamut_map_to_linear_srgb);
457 break;
458 case ColorSpace::kHSL: p->append(SkRasterPipelineOp::css_hsl_to_srgb); break;
459 case ColorSpace::kHWB: p->append(SkRasterPipelineOp::css_hwb_to_srgb); break;
460 default: break;
461 }
462
463 // Now transform from intermediate to destination color space.
464 // See comments in GrGradientShader.cpp about the decisions here.
465 if (!dstColorSpace) {
466 dstColorSpace = sk_srgb_singleton();
467 }
468 SkAlphaType intermediateAlphaType = colorIsPremul ? kPremul_SkAlphaType : kUnpremul_SkAlphaType;
469 // TODO(skia:13108): Get dst alpha type correctly
470 SkAlphaType dstAlphaType = kPremul_SkAlphaType;
471
472 if (colorsAreOpaque) {
473 intermediateAlphaType = dstAlphaType = kUnpremul_SkAlphaType;
474 }
475
477 intermediateColorSpace, intermediateAlphaType, dstColorSpace, dstAlphaType)
478 ->apply(p);
479}
480
482 const SkShaders::MatrixRec& mRec) const {
484 SkArenaAlloc* alloc = rec.fAlloc;
485 SkRasterPipeline_DecalTileCtx* decal_ctx = nullptr;
486
487 std::optional<SkShaders::MatrixRec> newMRec = mRec.apply(rec, fPtsToUnit);
488 if (!newMRec.has_value()) {
489 return false;
490 }
491
492 SkRasterPipeline_<256> postPipeline;
493
494 this->appendGradientStages(alloc, p, &postPipeline);
495
496 switch (fTileMode) {
498 p->append(SkRasterPipelineOp::mirror_x_1);
499 break;
501 p->append(SkRasterPipelineOp::repeat_x_1);
502 break;
504 decal_ctx = alloc->make<SkRasterPipeline_DecalTileCtx>();
505 decal_ctx->limit_x = SkBits2Float(SkFloat2Bits(1.0f) + 1);
506 // reuse mask + limit_x stage, or create a custom decal_1 that just stores the mask
507 p->append(SkRasterPipelineOp::decal_x, decal_ctx);
508 [[fallthrough]];
509
511 if (!fPositions) {
512 // We clamp only when the stops are evenly spaced.
513 // If not, there may be hard stops, and clamping ruins hard stops at 0 and/or 1.
514 // In that case, we must make sure we're using the general "gradient" stage,
515 // which is the only stage that will correctly handle unclamped t.
516 p->append(SkRasterPipelineOp::clamp_x_1);
517 }
518 break;
519 }
520
521 // Transform all of the colors to destination color space, possibly premultiplied
522 SkColor4fXformer xformedColors(this, rec.fDstCS);
524 xformedColors.fColors.begin(),
525 xformedColors.fPositions,
526 xformedColors.fColors.size());
527 AppendInterpolatedToDstStages(p, alloc, fColorsAreOpaque, fInterpolation,
528 xformedColors.fIntermediateColorSpace.get(), rec.fDstCS);
529
530 if (decal_ctx) {
531 p->append(SkRasterPipelineOp::check_decal_mask, decal_ctx);
532 }
533
534 p->extend(postPipeline);
535
536 return true;
537}
538
540 return fColorsAreOpaque && (this->getTileMode() != SkTileMode::kDecal);
541}
542
544 // We just compute an average color. There are several things we could do better:
545 // 1) We already have a different average_gradient_color helper later in this file, that weights
546 // contribution by the relative size of each band.
547 // 2) Colors should be converted to some standard color space! These could be in any space.
548 // 3) Do we want to average in the source space, sRGB, or some linear space?
549 SkColor4f color{0, 0, 0, 1};
550 for (int i = 0; i < fColorCount; ++i) {
551 color.fR += fColors[i].fR;
552 color.fG += fColors[i].fG;
553 color.fB += fColors[i].fB;
554 }
555 const float scale = 1.0f / fColorCount;
556 color.fR *= scale;
557 color.fG *= scale;
558 color.fB *= scale;
559 *lum = color;
560 return true;
561}
562
564 SkColorSpace* dst) {
566 switch (cs) {
567 case ColorSpace::kDestination:
568 return sk_ref_sp(dst);
569
570 // css-color-4 allows XYZD50 and XYZD65. For gradients, those are redundant. Interpolating
571 // in any linear RGB space, (regardless of white point), gives the same answer.
572 case ColorSpace::kSRGBLinear:
574
575 case ColorSpace::kSRGB:
576 case ColorSpace::kHSL:
577 case ColorSpace::kHWB:
578 return SkColorSpace::MakeSRGB();
579
580 case ColorSpace::kLab:
581 case ColorSpace::kLCH:
582 // Conversion to Lab (and LCH) starts with XYZD50
584
585 case ColorSpace::kOKLab:
586 case ColorSpace::kOKLabGamutMap:
587 case ColorSpace::kOKLCH:
588 case ColorSpace::kOKLCHGamutMap:
589 // The "standard" conversion to these spaces starts with XYZD65. That requires extra
590 // effort to conjure. The author also has reference code for going directly from linear
591 // sRGB, so we use that.
592 // TODO(skia:13108): Even better would be to have an LMS color space, because the first
593 // part of the conversion is a matrix multiply, which could be absorbed into the
594 // color space xform.
596 }
598}
599
602
603static SkPMColor4f srgb_to_hsl(SkPMColor4f rgb, bool* hueIsPowerless) {
604 float mx = std::max({rgb.fR, rgb.fG, rgb.fB});
605 float mn = std::min({rgb.fR, rgb.fG, rgb.fB});
606 float hue = 0, sat = 0, light = (mn + mx) / 2;
607 float d = mx - mn;
608
609 if (d != 0) {
610 sat = (light == 0 || light == 1) ? 0 : (mx - light) / std::min(light, 1 - light);
611 if (mx == rgb.fR) {
612 hue = (rgb.fG - rgb.fB) / d + (rgb.fG < rgb.fB ? 6 : 0);
613 } else if (mx == rgb.fG) {
614 hue = (rgb.fB - rgb.fR) / d + 2;
615 } else {
616 hue = (rgb.fR - rgb.fG) / d + 4;
617 }
618
619 hue *= 60;
620 }
621 if (sat == 0) {
622 *hueIsPowerless = true;
623 }
624 return {hue, sat * 100, light * 100, rgb.fA};
625}
626
627static SkPMColor4f srgb_to_hwb(SkPMColor4f rgb, bool* hueIsPowerless) {
628 SkPMColor4f hsl = srgb_to_hsl(rgb, hueIsPowerless);
629 float white = std::min({rgb.fR, rgb.fG, rgb.fB});
630 float black = 1 - std::max({rgb.fR, rgb.fG, rgb.fB});
631 return {hsl.fR, white * 100, black * 100, rgb.fA};
632}
633
634static SkPMColor4f xyzd50_to_lab(SkPMColor4f xyz, bool* /*hueIsPowerless*/) {
635 constexpr float D50[3] = {0.3457f / 0.3585f, 1.0f, (1.0f - 0.3457f - 0.3585f) / 0.3585f};
636
637 constexpr float e = 216.0f / 24389;
638 constexpr float k = 24389.0f / 27;
639
640 SkPMColor4f f;
641 for (int i = 0; i < 3; ++i) {
642 float v = xyz[i] / D50[i];
643 f[i] = (v > e) ? std::cbrtf(v) : (k * v + 16) / 116;
644 }
645
646 return {(116 * f[1]) - 16, 500 * (f[0] - f[1]), 200 * (f[1] - f[2]), xyz.fA};
647}
648
649// The color space is technically LCH, but we produce HCL, so that all polar spaces have hue in the
650// first component. This simplifies the hue handling for HueMethod and premul/unpremul.
651static SkPMColor4f xyzd50_to_hcl(SkPMColor4f xyz, bool* hueIsPowerless) {
652 SkPMColor4f Lab = xyzd50_to_lab(xyz, hueIsPowerless);
653 float hue = sk_float_radians_to_degrees(atan2f(Lab[2], Lab[1]));
654 float chroma = sqrtf(Lab[1] * Lab[1] + Lab[2] * Lab[2]);
655 // The LCH math produces small-ish (but not tiny) chroma values for achromatic colors:
656 constexpr float kMaxChromaForPowerlessHue = 1e-2f;
657 if (chroma <= kMaxChromaForPowerlessHue) {
658 *hueIsPowerless = true;
659 }
660 return {hue >= 0 ? hue : hue + 360, chroma, Lab[0], xyz.fA};
661}
662
663// https://bottosson.github.io/posts/oklab/#converting-from-linear-srgb-to-oklab
664static SkPMColor4f lin_srgb_to_oklab(SkPMColor4f rgb, bool* /*hueIsPowerless*/) {
665 float l = 0.4122214708f * rgb.fR + 0.5363325363f * rgb.fG + 0.0514459929f * rgb.fB;
666 float m = 0.2119034982f * rgb.fR + 0.6806995451f * rgb.fG + 0.1073969566f * rgb.fB;
667 float s = 0.0883024619f * rgb.fR + 0.2817188376f * rgb.fG + 0.6299787005f * rgb.fB;
668 l = std::cbrtf(l);
669 m = std::cbrtf(m);
670 s = std::cbrtf(s);
671 return {0.2104542553f * l + 0.7936177850f * m - 0.0040720468f * s,
672 1.9779984951f * l - 2.4285922050f * m + 0.4505937099f * s,
673 0.0259040371f * l + 0.7827717662f * m - 0.8086757660f * s,
674 rgb.fA};
675}
676
677// The color space is technically OkLCH, but we produce HCL, so that all polar spaces have hue in
678// the first component. This simplifies the hue handling for HueMethod and premul/unpremul.
679static SkPMColor4f lin_srgb_to_okhcl(SkPMColor4f rgb, bool* hueIsPowerless) {
680 SkPMColor4f OKLab = lin_srgb_to_oklab(rgb, hueIsPowerless);
681 float hue = sk_float_radians_to_degrees(atan2f(OKLab[2], OKLab[1]));
682 float chroma = sqrtf(OKLab[1] * OKLab[1] + OKLab[2] * OKLab[2]);
683 // The OKLCH math produces very small chroma values for achromatic colors:
684 constexpr float kMaxChromaForPowerlessHue = 1e-6f;
685 if (chroma <= kMaxChromaForPowerlessHue) {
686 *hueIsPowerless = true;
687 }
688 return {hue >= 0 ? hue : hue + 360, chroma, OKLab[0], rgb.fA};
689}
690
692 return {hsl.fR, hsl.fG * hsl.fA, hsl.fB * hsl.fA, hsl.fA};
693}
694
696 return {rgb.fR * rgb.fA, rgb.fG * rgb.fA, rgb.fB * rgb.fA, rgb.fA};
697}
698
701 switch (cs) {
702 case ColorSpace::kLCH:
703 case ColorSpace::kOKLCH:
704 case ColorSpace::kHSL:
705 case ColorSpace::kHWB:
706 return true;
707 default:
708 return false;
709 }
710}
711
712// Given `colors` in `src` color space, an interpolation space, and a `dst` color space,
713// we are doing several things. First, some definitions:
714//
715// The interpolation color space is "special" if it can't be represented as an SkColorSpace. This
716// applies to any color space that isn't an RGB space, like Lab or HSL. These need special handling
717// because we have to run bespoke code to do the conversion (before interpolation here, and after
718// interpolation in the backend shader/pipeline).
719//
720// The interpolation color space is "polar" if it involves hue (HSL, HWB, LCH, Oklch). These need
721// special handling, becuase hue is never premultiplied, and because HueMethod comes into play.
722//
723// 1) Pick an `intermediate` SkColorSpace. If the interpolation color space is not "special",
724// (kDestination, kSRGB, etc... ), then `intermediate` is exact. Otherwise, `intermediate` is the
725// RGB space that prepares us to do the final conversion. For example, conversion to Lab starts
726// with XYZD50, so `intermediate` will be XYZD50 if we're actually interpolating in Lab.
727// 2) Transform all colors to the `intermediate` color space, leaving them unpremultiplied.
728// 3) If the interpolation color space is "special", transform the colors to that space.
729// 4) If the interpolation color space is "polar", adjust the angles to respect HueMethod.
730// 5) If premul interpolation is requested, apply that. For "polar" interpolated colors, don't
731// premultiply hue, only the other two channels. Note that there are four polar spaces.
732// Two have hue as the first component, and two have it as the third component. To reduce
733// complexity, we always store hue in the first component, swapping it with luminance for
734// LCH and Oklch. The backend code (eg, shaders) needs to know about this.
736 SkColorSpace* dst,
737 bool forceExplicitPositions) {
740
741 int colorCount = shader->fColorCount;
742 const SkGradientShader::Interpolation interpolation = shader->fInterpolation;
743
744 // 0) Copy the shader's position pointer. Certain interpolation modes might force us to add
745 // new stops, in which case we'll allocate & edit the positions.
746 fPositions = shader->fPositions;
747
748 // 1) Determine the color space of our intermediate colors.
750
751 // 2) Convert all colors to the intermediate color space
753
754 auto dstInfo = info.makeColorSpace(fIntermediateColorSpace);
755 auto srcInfo = info.makeColorSpace(shader->fColorSpace);
756
757 fColors.reset(colorCount);
759 fColors.begin(),
760 info.minRowBytes(),
761 srcInfo,
762 shader->fColors,
763 info.minRowBytes()));
764
765 // 3) Transform to the interpolation color space (if it's special)
766 ConvertColorProc convertFn = nullptr;
767 switch (interpolation.fColorSpace) {
768 case ColorSpace::kHSL: convertFn = srgb_to_hsl; break;
769 case ColorSpace::kHWB: convertFn = srgb_to_hwb; break;
770 case ColorSpace::kLab: convertFn = xyzd50_to_lab; break;
771 case ColorSpace::kLCH: convertFn = xyzd50_to_hcl; break;
772 case ColorSpace::kOKLab: convertFn = lin_srgb_to_oklab; break;
773 case ColorSpace::kOKLabGamutMap: convertFn = lin_srgb_to_oklab; break;
774 case ColorSpace::kOKLCH: convertFn = lin_srgb_to_okhcl; break;
775 case ColorSpace::kOKLCHGamutMap: convertFn = lin_srgb_to_okhcl; break;
776 default: break;
777 }
778
779 skia_private::STArray<4, bool> hueIsPowerless;
780 bool anyPowerlessHue = false;
781 hueIsPowerless.push_back_n(colorCount, false);
782 if (convertFn) {
783 for (int i = 0; i < colorCount; ++i) {
784 fColors[i] = convertFn(fColors[i], hueIsPowerless.data() + i);
785 anyPowerlessHue = anyPowerlessHue || hueIsPowerless[i];
786 }
787 }
788
789 if (anyPowerlessHue) {
790 // In theory, if we knew we were just going to adjust the existing colors (without adding
791 // new ones), we could do it all in-place. To keep things simple, we always generate the
792 // new colors in separate storage.
793 ColorStorage newColors;
794 PositionStorage newPositions;
795
796 for (int i = 0; i < colorCount; ++i) {
797 const SkPMColor4f& curColor = fColors[i];
798 float curPos = shader->getPos(i);
799
800 if (!hueIsPowerless[i]) {
801 newColors.push_back(curColor);
802 newPositions.push_back(curPos);
803 continue;
804 }
805
806 auto colorWithHueFrom = [](const SkPMColor4f& color, const SkPMColor4f& hueColor) {
807 // If we have any powerless hue, then all colors are already in (some) polar space,
808 // and they all store their hue in the red channel.
809 return SkPMColor4f{hueColor.fR, color.fG, color.fB, color.fA};
810 };
811
812 // In each case, we might be copying a powerless (invalid) hue from the neighbor, but
813 // that should be fine, as it will match that neighbor perfectly, and any hue is ok.
814 if (i != 0) {
815 newPositions.push_back(curPos);
816 newColors.push_back(colorWithHueFrom(curColor, fColors[i - 1]));
817 }
818 if (i != colorCount - 1) {
819 newPositions.push_back(curPos);
820 newColors.push_back(colorWithHueFrom(curColor, fColors[i + 1]));
821 }
822 }
823
824 fColors.swap(newColors);
825 fPositionStorage.swap(newPositions);
827 colorCount = fColors.size();
828 }
829
830 // 4) For polar colors, adjust hue values to respect the hue method. We're using a trick here...
831 // The specification looks at adjacent colors, and adjusts one or the other. Because we store
832 // the stops in uniforms (and our backend conversions normalize the hue angle), we can
833 // instead always apply the adjustment to the *second* color. That lets us keep a running
834 // total, and do a single pass across all the colors to respect the requested hue method,
835 // without needing to do any extra work per-pixel.
836 if (color_space_is_polar(interpolation.fColorSpace)) {
837 float delta = 0;
838 for (int i = 0; i < colorCount - 1; ++i) {
839 float h1 = fColors[i].fR;
840 float& h2 = fColors[i + 1].fR;
841 h2 += delta;
842 switch (interpolation.fHueMethod) {
843 case HueMethod::kShorter:
844 if (h2 - h1 > 180) {
845 h2 -= 360; // i.e. h1 += 360
846 delta -= 360;
847 } else if (h2 - h1 < -180) {
848 h2 += 360;
849 delta += 360;
850 }
851 break;
852 case HueMethod::kLonger:
853 if ((i == 0 && shader->fFirstStopIsImplicit) ||
854 (i == colorCount - 2 && shader->fLastStopIsImplicit)) {
855 // Do nothing. We don't want to introduce a full revolution for these stops
856 // Full rationale at skbug.com/13941
857 } else if (0 < h2 - h1 && h2 - h1 < 180) {
858 h2 -= 360; // i.e. h1 += 360
859 delta -= 360;
860 } else if (-180 < h2 - h1 && h2 - h1 <= 0) {
861 h2 += 360;
862 delta += 360;
863 }
864 break;
865 case HueMethod::kIncreasing:
866 if (h2 < h1) {
867 h2 += 360;
868 delta += 360;
869 }
870 break;
871 case HueMethod::kDecreasing:
872 if (h1 < h2) {
873 h2 -= 360; // i.e. h1 += 360;
874 delta -= 360;
875 }
876 break;
877 }
878 }
879 }
880
881 // 5) Apply premultiplication
882 PremulColorProc premulFn = nullptr;
883 if (static_cast<bool>(interpolation.fInPremul)) {
884 switch (interpolation.fColorSpace) {
885 case ColorSpace::kHSL:
886 case ColorSpace::kHWB:
887 case ColorSpace::kLCH:
888 case ColorSpace::kOKLCH:
889 premulFn = premul_polar;
890 break;
891 default:
892 premulFn = premul_rgb;
893 break;
894 }
895 }
896
897 if (premulFn) {
898 for (int i = 0; i < colorCount; ++i) {
899 fColors[i] = premulFn(fColors[i]);
900 }
901 }
902
903 // Ganesh requires that the positions be explicit (rather than implicitly evenly spaced)
904 if (forceExplicitPositions && !fPositions) {
906 float posScale = 1.0f / (colorCount - 1);
907 for (int i = 0; i < colorCount; i++) {
908 fPositionStorage.push_back(i * posScale);
909 }
911 }
912}
913
915 const float ONE_OVER_255 = 1.f / 255;
916 for (int i = 0; i < count; ++i) {
917 fColors4f.push_back({SkColorGetR(colors[i]) * ONE_OVER_255,
918 SkColorGetG(colors[i]) * ONE_OVER_255,
919 SkColorGetB(colors[i]) * ONE_OVER_255,
920 SkColorGetA(colors[i]) * ONE_OVER_255});
921 }
922}
923
925 if (info) {
926 if (info->fColorCount >= fColorCount) {
927 if (info->fColors) {
928 for (int i = 0; i < fColorCount; ++i) {
929 info->fColors[i] = this->getLegacyColor(i);
930 }
931 }
932 if (info->fColorOffsets) {
933 for (int i = 0; i < fColorCount; ++i) {
934 info->fColorOffsets[i] = this->getPos(i);
935 }
936 }
937 }
938 info->fColorCount = fColorCount;
939 info->fTileMode = fTileMode;
940
941 info->fGradientFlags =
943 }
944}
945
946// Return true if these parameters are valid/legal/safe to construct a gradient
947//
949 int count,
950 SkTileMode tileMode,
951 const Interpolation& interpolation) {
952 return nullptr != colors && count >= 1 && (unsigned)tileMode < kSkTileModeCount &&
953 (unsigned)interpolation.fColorSpace < Interpolation::kColorSpaceCount &&
954 (unsigned)interpolation.fHueMethod < Interpolation::kHueMethodCount;
955}
956
958 sk_sp<SkColorSpace> colorSpace,
959 const SkScalar positions[],
960 int colorCount,
961 SkTileMode mode,
962 const Interpolation& interpolation)
963 : fColors(colors)
964 , fColorSpace(std::move(colorSpace))
965 , fPositions(positions)
966 , fColorCount(colorCount)
967 , fTileMode(mode)
968 , fInterpolation(interpolation) {
970}
971
973 const SkScalar pos[],
974 int colorCount) {
975 // The gradient is a piecewise linear interpolation between colors. For a given interval,
976 // the integral between the two endpoints is 0.5 * (ci + cj) * (pj - pi), which provides that
977 // intervals average color. The overall average color is thus the sum of each piece. The thing
978 // to keep in mind is that the provided gradient definition may implicitly use p=0 and p=1.
979 skvx::float4 blend(0.0f);
980 for (int i = 0; i < colorCount - 1; ++i) {
981 // Calculate the average color for the interval between pos(i) and pos(i+1)
982 auto c0 = skvx::float4::Load(&colors[i]);
983 auto c1 = skvx::float4::Load(&colors[i + 1]);
984
985 // when pos == null, there are colorCount uniformly distributed stops, going from 0 to 1,
986 // so pos[i + 1] - pos[i] = 1/(colorCount-1)
987 SkScalar w;
988 if (pos) {
989 // Match position fixing in SkGradientShader's constructor, clamping positions outside
990 // [0, 1] and forcing the sequence to be monotonic
991 SkScalar p0 = SkTPin(pos[i], 0.f, 1.f);
992 SkScalar p1 = SkTPin(pos[i + 1], p0, 1.f);
993 w = p1 - p0;
994
995 // And account for any implicit intervals at the start or end of the positions
996 if (i == 0) {
997 if (p0 > 0.0f) {
998 // The first color is fixed between p = 0 to pos[0], so 0.5*(ci + cj)*(pj - pi)
999 // becomes 0.5*(c + c)*(pj - 0) = c * pj
1000 auto c = skvx::float4::Load(&colors[0]);
1001 blend += p0 * c;
1002 }
1003 }
1004 if (i == colorCount - 2) {
1005 if (p1 < 1.f) {
1006 // The last color is fixed between pos[n-1] to p = 1, so 0.5*(ci + cj)*(pj - pi)
1007 // becomes 0.5*(c + c)*(1 - pi) = c * (1 - pi)
1008 auto c = skvx::float4::Load(&colors[colorCount - 1]);
1009 blend += (1.f - p1) * c;
1010 }
1011 }
1012 } else {
1013 w = 1.f / (colorCount - 1);
1014 }
1015
1016 blend += 0.5f * w * (c1 + c0);
1017 }
1018
1019 SkColor4f avg;
1020 blend.store(&avg);
1021 return avg;
1022}
1023
1024// Except for special circumstances of clamped gradients, every gradient shape--when degenerate--
1025// can be mapped to the same fallbacks. The specific shape factories must account for special
1026// clamped conditions separately, this will always return the last color for clamped gradients.
1028 const SkScalar pos[],
1029 int colorCount,
1030 sk_sp<SkColorSpace> colorSpace,
1031 SkTileMode mode) {
1032 switch (mode) {
1033 case SkTileMode::kDecal:
1034 // normally this would reject the area outside of the interpolation region, so since
1035 // inside region is empty when the radii are equal, the entire draw region is empty
1036 return SkShaders::Empty();
1039 // repeat and mirror are treated the same: the border colors are never visible,
1040 // but approximate the final color as infinite repetitions of the colors, so
1041 // it can be represented as the average color of the gradient.
1042 return SkShaders::Color(average_gradient_color(colors, pos, colorCount),
1043 std::move(colorSpace));
1044 case SkTileMode::kClamp:
1045 // Depending on how the gradient shape degenerates, there may be a more specialized
1046 // fallback representation for the factories to use, but this is a reasonable default.
1047 return SkShaders::Color(colors[colorCount - 1], std::move(colorSpace));
1048 }
1049 SkDEBUGFAIL("Should not be reached");
1050 return nullptr;
1051}
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition DM.cpp:213
float c_scale
int count
SkPoint pos
SkColor4f color
static float prev(float f)
kUnpremul_SkAlphaType
SkAlphaType
Definition SkAlphaType.h:26
@ kPremul_SkAlphaType
pixel components are premultiplied by alpha
Definition SkAlphaType.h:29
#define SkAssertResult(cond)
Definition SkAssert.h:123
#define SkUNREACHABLE
Definition SkAssert.h:135
#define SkDEBUGFAIL(message)
Definition SkAssert.h:118
#define SkASSERT(cond)
Definition SkAssert.h:116
SkRGBA4f< kPremul_SkAlphaType > SkPMColor4f
SkColorSpace * sk_srgb_singleton()
@ kRGBA_F32_SkColorType
pixel using C float for red, green, blue, alpha; in 128-bit word
Definition SkColorType.h:40
#define SkColorGetR(color)
Definition SkColor.h:65
#define SkColorGetG(color)
Definition SkColor.h:69
uint32_t SkColor
Definition SkColor.h:37
#define SkColorGetA(color)
Definition SkColor.h:61
#define SkColorGetB(color)
Definition SkColor.h:73
bool SkConvertPixels(const SkImageInfo &dstInfo, void *dstPixels, size_t dstRB, const SkImageInfo &srcInfo, const void *srcPixels, size_t srcRB)
static float SkBits2Float(uint32_t bits)
Definition SkFloatBits.h:48
static uint32_t SkFloat2Bits(float value)
Definition SkFloatBits.h:41
static bool SkIsFinite(T x, Pack... values)
static constexpr float sk_ieee_float_divide(float numer, float denom)
static constexpr float sk_float_radians_to_degrees(float radians)
static void add_const_color(SkRasterPipeline_GradientCtx *ctx, size_t stop, SkPMColor4f color)
static void init_stop_evenly(SkRasterPipeline_GradientCtx *ctx, float gapCount, size_t stop, SkPMColor4f c_l, SkPMColor4f c_r)
static void add_stop_color(SkRasterPipeline_GradientCtx *ctx, size_t stop, SkPMColor4f Fs, SkPMColor4f Bs)
static SkPMColor4f srgb_to_hwb(SkPMColor4f rgb, bool *hueIsPowerless)
static SkPMColor4f lin_srgb_to_okhcl(SkPMColor4f rgb, bool *hueIsPowerless)
static SkPMColor4f xyzd50_to_lab(SkPMColor4f xyz, bool *)
SkPMColor4f(*)(SkPMColor4f, bool *) ConvertColorProc
static SkPMColor4f srgb_to_hsl(SkPMColor4f rgb, bool *hueIsPowerless)
static bool color_space_is_polar(SkGradientShader::Interpolation::ColorSpace cs)
GradientSerializationFlags
@ kHasColorSpace_GSF
@ kTileModeShift_GSF
@ kInterpolationColorSpaceMask_GSF
@ kHasLegacyLocalMatrix_GSF
@ kInterpolationColorSpaceShift_GSF
@ kInterpolationHueMethodShift_GSF
@ kHasPosition_GSF
@ kInterpolationInPremul_GSF
@ kInterpolationHueMethodMask_GSF
@ kTileModeMask_GSF
SkPMColor4f(*)(SkPMColor4f) PremulColorProc
static bool validate_array(SkReadBuffer &buffer, size_t count, STArray< N, T, MEM_MOVE > *array)
static SkColor4f average_gradient_color(const SkColor4f colors[], const SkScalar pos[], int colorCount)
static SkPMColor4f xyzd50_to_hcl(SkPMColor4f xyz, bool *hueIsPowerless)
static void init_stop_pos(SkRasterPipeline_GradientCtx *ctx, size_t stop, float t_l, float c_scale, SkPMColor4f c_l, SkPMColor4f c_r)
static SkPMColor4f premul_polar(SkPMColor4f hsl)
static SkPMColor4f premul_rgb(SkPMColor4f rgb)
static SkPMColor4f lin_srgb_to_oklab(SkPMColor4f rgb, bool *)
static sk_sp< SkColorSpace > intermediate_color_space(SkGradientShader::Interpolation::ColorSpace cs, SkColorSpace *dst)
static void sk_bzero(void *buffer, size_t size)
Definition SkMalloc.h:105
static bool apply(Pass *pass, SkRecord *record)
sk_sp< T > sk_ref_sp(T *obj)
Definition SkRefCnt.h:381
static bool SkScalarNearlyEqual(SkScalar x, SkScalar y, SkScalar tolerance=SK_ScalarNearlyZero)
Definition SkScalar.h:107
#define SK_Scalar1
Definition SkScalar.h:18
static constexpr const T & SkTPin(const T &x, const T &lo, const T &hi)
Definition SkTPin.h:19
SkTileMode
Definition SkTileMode.h:13
static constexpr int kSkTileModeCount
Definition SkTileMode.h:39
static constexpr bool SkToBool(const T &x)
Definition SkTo.h:35
SI void store(P *ptr, const T &val)
T * makeArray(size_t count)
auto make(Ctor &&ctor) -> decltype(ctor(nullptr))
static sk_sp< SkColorSpace > MakeSRGB()
static sk_sp< SkColorSpace > Deserialize(const void *data, size_t length)
static sk_sp< SkColorSpace > MakeRGB(const skcms_TransferFunction &transferFn, const skcms_Matrix3x3 &toXYZ)
sk_sp< SkData > serialize() const
static sk_sp< SkColorSpace > MakeSRGBLinear()
bool unflatten(SkReadBuffer &, SkMatrix *legacyLocalMatrix)
static bool ValidGradient(const SkColor4f colors[], int count, SkTileMode tileMode, const Interpolation &interpolation)
SkColor getLegacyColor(int i) const
bool isOpaque() const override
SkGradientBaseShader(const Descriptor &desc, const SkMatrix &ptsToUnit)
virtual void appendGradientStages(SkArenaAlloc *alloc, SkRasterPipeline *tPipeline, SkRasterPipeline *postPipeline) const =0
bool onAsLuminanceColor(SkColor4f *) const override
SkScalar getPos(int i) const
static sk_sp< SkShader > MakeDegenerateGradient(const SkColor4f colors[], const SkScalar pos[], int colorCount, sk_sp< SkColorSpace > colorSpace, SkTileMode mode)
void flatten(SkWriteBuffer &) const override
bool appendStages(const SkStageRec &, const SkShaders::MatrixRec &) const override
void commonAsAGradient(GradientInfo *) const
sk_sp< SkColorSpace > fColorSpace
static void AppendGradientFillStages(SkRasterPipeline *p, SkArenaAlloc *alloc, const SkPMColor4f *colors, const SkScalar *positions, int count)
static void AppendInterpolatedToDstStages(SkRasterPipeline *p, SkArenaAlloc *alloc, bool colorsAreOpaque, const Interpolation &interpolation, const SkColorSpace *intermediateColorSpace, const SkColorSpace *dstColorSpace)
SkTileMode getTileMode() const
static const SkMatrix & I()
TypeMask getType() const
Definition SkMatrix.h:207
std::optional< MatrixRec > apply(const SkStageRec &rec, const SkMatrix &postInv={}) const
T * get() const
Definition SkRefCnt.h:303
T * reset(size_t count)
T * push_back_n(int n)
Definition SkTArray.h:262
void resize_back(int newCount)
Definition SkTArray.h:338
void reset(int n)
Definition SkTArray.h:139
int size() const
Definition SkTArray.h:416
void swap(TArray &that)
Definition SkTArray.h:353
void reserve_exact(int n)
Definition SkTArray.h:176
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE auto & d
Definition main.cc:19
float SkScalar
Definition extension.cpp:12
struct MyStruct s
FlutterSemanticsFlag flags
static const uint8_t buffer[]
static SkColor blend(SkColor dst, SkColor src, void(*mode)(float, float, float, float *, float *, float *))
Definition hsl.cpp:142
static float sat(float r, float g, float b)
Definition hsl.cpp:51
static void hue(float dr, float dg, float db, float *sr, float *sg, float *sb)
Definition hsl.cpp:92
static float lum(float r, float g, float b)
Definition hsl.cpp:52
static constexpr skcms_Matrix3x3 kXYZ
static constexpr skcms_TransferFunction kLinear
Definition ref_ptr.h:256
SkScalar w
#define T
const Scalar scale
SkColor4fXformer(const SkGradientBaseShader *shader, SkColorSpace *dst, bool forceExplicitPositions=false)
PositionStorage fPositionStorage
sk_sp< SkColorSpace > fIntermediateColorSpace
SkColorConverter(const SkColor *colors, int count)
skia_private::STArray< 2, SkColor4f > fColors4f
static constexpr int kColorSpaceCount
static constexpr int kHueMethodCount
static SkImageInfo Make(int width, int height, SkColorType ct, SkAlphaType at)
const float * vec() const
Definition SkColor.h:308
float fB
blue component
Definition SkColor.h:265
float fR
red component
Definition SkColor.h:263
float fG
green component
Definition SkColor.h:264
float fA
alpha component
Definition SkColor.h:266
SkRasterPipeline * fPipeline
SkColorSpace * fDstCS
SkArenaAlloc * fAlloc
static SKVX_ALWAYS_INLINE Vec Load(const void *ptr)
Definition SkVx.h:109