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