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