1 /* 2 * Copyright 2016 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8 #include "Sk4fLinearGradient.h" 9 #include "Sk4x4f.h" 10 #include "SkPaint.h" 11 12 #include <cmath> 13 14 namespace { 15 16 template<typename dstType, ApplyPremul premul> 17 void ramp(const Sk4f& c, const Sk4f& dc, dstType dst[], int n, 18 const Sk4f& bias0, const Sk4f& bias1) { 19 SkASSERT(n > 0); 20 21 const Sk4f dc2 = dc + dc, 22 dc4 = dc2 + dc2; 23 24 Sk4f c0 = c + DstTraits<dstType, premul>::pre_lerp_bias(bias0), 25 c1 = c + dc + DstTraits<dstType, premul>::pre_lerp_bias(bias1), 26 c2 = c0 + dc2, 27 c3 = c1 + dc2; 28 29 while (n >= 4) { 30 DstTraits<dstType, premul>::store4x(c0, c1, c2, c3, dst, bias0, bias1); 31 dst += 4; 32 33 c0 = c0 + dc4; 34 c1 = c1 + dc4; 35 c2 = c2 + dc4; 36 c3 = c3 + dc4; 37 n -= 4; 38 } 39 if (n & 2) { 40 DstTraits<dstType, premul>::store(c0, dst++, bias0); 41 DstTraits<dstType, premul>::store(c1, dst++, bias1); 42 c0 = c0 + dc2; 43 } 44 if (n & 1) { 45 DstTraits<dstType, premul>::store(c0, dst, bias0); 46 } 47 } 48 49 template<SkShader::TileMode> 50 SkScalar pinFx(SkScalar); 51 52 template<> 53 SkScalar pinFx<SkShader::kClamp_TileMode>(SkScalar fx) { 54 return fx; 55 } 56 57 template<> 58 SkScalar pinFx<SkShader::kRepeat_TileMode>(SkScalar fx) { 59 SkScalar f = SkScalarFraction(fx); 60 if (f < 0) { 61 f = SkTMin(f + 1, nextafterf(1, 0)); 62 } 63 SkASSERT(f >= 0); 64 SkASSERT(f < 1.0f); 65 return f; 66 } 67 68 template<> 69 SkScalar pinFx<SkShader::kMirror_TileMode>(SkScalar fx) { 70 SkScalar f = SkScalarMod(fx, 2.0f); 71 if (f < 0) { 72 f = SkTMin(f + 2, nextafterf(2, 0)); 73 } 74 SkASSERT(f >= 0); 75 SkASSERT(f < 2.0f); 76 return f; 77 } 78 79 // true when x is in [k1,k2], or [k2, k1] when the interval is reversed. 80 // TODO(fmalita): hoist the reversed interval check out of this helper. 81 bool in_range(SkScalar x, SkScalar k1, SkScalar k2) { 82 SkASSERT(k1 != k2); 83 return (k1 < k2) 84 ? (x >= k1 && x <= k2) 85 : (x >= k2 && x <= k1); 86 } 87 88 } // anonymous namespace 89 90 SkLinearGradient:: 91 LinearGradient4fContext::LinearGradient4fContext(const SkLinearGradient& shader, 92 const ContextRec& rec) 93 : INHERITED(shader, rec) { 94 95 // Our fast path expects interval points to be monotonically increasing in x. 96 const bool reverseIntervals = std::signbit(fDstToPos.getScaleX()); 97 fIntervals.init(shader, rec.fDstColorSpace, shader.fTileMode, 98 fColorsArePremul, rec.fPaint->getAlpha() * (1.0f / 255), reverseIntervals); 99 100 SkASSERT(fIntervals->count() > 0); 101 fCachedInterval = fIntervals->begin(); 102 } 103 104 const Sk4fGradientInterval* 105 SkLinearGradient::LinearGradient4fContext::findInterval(SkScalar fx) const { 106 SkASSERT(in_range(fx, fIntervals->front().fT0, fIntervals->back().fT1)); 107 108 if (1) { 109 // Linear search, using the last scanline interval as a starting point. 110 SkASSERT(fCachedInterval >= fIntervals->begin()); 111 SkASSERT(fCachedInterval < fIntervals->end()); 112 const int search_dir = fDstToPos.getScaleX() >= 0 ? 1 : -1; 113 while (!in_range(fx, fCachedInterval->fT0, fCachedInterval->fT1)) { 114 fCachedInterval += search_dir; 115 if (fCachedInterval >= fIntervals->end()) { 116 fCachedInterval = fIntervals->begin(); 117 } else if (fCachedInterval < fIntervals->begin()) { 118 fCachedInterval = fIntervals->end() - 1; 119 } 120 } 121 return fCachedInterval; 122 } else { 123 // Binary search. Seems less effective than linear + caching. 124 const auto* i0 = fIntervals->begin(); 125 const auto* i1 = fIntervals->end() - 1; 126 127 while (i0 != i1) { 128 SkASSERT(i0 < i1); 129 SkASSERT(in_range(fx, i0->fT0, i1->fT1)); 130 131 const auto* i = i0 + ((i1 - i0) >> 1); 132 133 if (in_range(fx, i0->fT0, i->fT1)) { 134 i1 = i; 135 } else { 136 SkASSERT(in_range(fx, i->fT1, i1->fT1)); 137 i0 = i + 1; 138 } 139 } 140 141 SkASSERT(in_range(fx, i0->fT0, i0->fT1)); 142 return i0; 143 } 144 } 145 146 147 void SkLinearGradient:: 148 LinearGradient4fContext::shadeSpan(int x, int y, SkPMColor dst[], int count) { 149 SkASSERT(count > 0); 150 151 float bias0 = 0, 152 bias1 = 0; 153 154 if (fDither) { 155 static constexpr float dither_cell[] = { 156 -3/8.0f, 1/8.0f, 157 3/8.0f, -1/8.0f, 158 }; 159 160 const int rowIndex = (y & 1) << 1; 161 bias0 = dither_cell[rowIndex + 0]; 162 bias1 = dither_cell[rowIndex + 1]; 163 164 if (x & 1) { 165 SkTSwap(bias0, bias1); 166 } 167 } 168 169 if (fColorsArePremul) { 170 // In premul interpolation mode, components are pre-scaled by 255 and the store 171 // op is truncating. We pre-bias here to achieve rounding. 172 bias0 += 0.5f; 173 bias1 += 0.5f; 174 175 this->shadePremulSpan<SkPMColor, ApplyPremul::False>(x, y, dst, count, bias0, bias1); 176 } else { 177 // In unpremul interpolation mode, Components are not pre-scaled. 178 bias0 *= 1/255.0f; 179 bias1 *= 1/255.0f; 180 181 this->shadePremulSpan<SkPMColor, ApplyPremul::True >(x, y, dst, count, bias0, bias1); 182 } 183 } 184 185 void SkLinearGradient:: 186 LinearGradient4fContext::shadeSpan4f(int x, int y, SkPM4f dst[], int count) { 187 SkASSERT(count > 0); 188 189 // 4f dests are dithered at a later stage, if needed. 190 static constexpr float bias0 = 0, 191 bias1 = 0; 192 if (fColorsArePremul) { 193 this->shadePremulSpan<SkPM4f, ApplyPremul::False>(x, y, dst, count, bias0, bias1); 194 } else { 195 this->shadePremulSpan<SkPM4f, ApplyPremul::True >(x, y, dst, count, bias0, bias1); 196 } 197 } 198 199 template<typename dstType, ApplyPremul premul> 200 void SkLinearGradient:: 201 LinearGradient4fContext::shadePremulSpan(int x, int y, dstType dst[], int count, 202 float bias0, float bias1) const { 203 const SkLinearGradient& shader = static_cast<const SkLinearGradient&>(fShader); 204 switch (shader.fTileMode) { 205 case kDecal_TileMode: 206 SkASSERT(false); // decal only supported via stages 207 // fall-through 208 case kClamp_TileMode: 209 this->shadeSpanInternal<dstType, premul, kClamp_TileMode >(x, y, dst, count, bias0, bias1); 210 break; 211 case kRepeat_TileMode: 212 this->shadeSpanInternal<dstType, premul, kRepeat_TileMode>(x, y, dst, count, bias0, bias1); 213 break; 214 case kMirror_TileMode: 215 this->shadeSpanInternal<dstType, premul, kMirror_TileMode>(x, y, dst, count, bias0, bias1); 216 break; 217 } 218 } 219 220 template<typename dstType, ApplyPremul premul, SkShader::TileMode tileMode> 221 void SkLinearGradient:: 222 LinearGradient4fContext::shadeSpanInternal(int x, int y, dstType dst[], int count, 223 float bias0, float bias1) const { 224 SkPoint pt; 225 fDstToPosProc(fDstToPos, 226 x + SK_ScalarHalf, 227 y + SK_ScalarHalf, 228 &pt); 229 const SkScalar fx = pinFx<tileMode>(pt.x()); 230 const SkScalar dx = fDstToPos.getScaleX(); 231 LinearIntervalProcessor<dstType, premul, tileMode> proc(fIntervals->begin(), 232 fIntervals->end() - 1, 233 this->findInterval(fx), 234 fx, 235 dx, 236 SkScalarNearlyZero(dx * count)); 237 Sk4f bias4f0(bias0), 238 bias4f1(bias1); 239 240 while (count > 0) { 241 // What we really want here is SkTPin(advance, 1, count) 242 // but that's a significant perf hit for >> stops; investigate. 243 const int n = SkScalarTruncToInt( 244 SkTMin<SkScalar>(proc.currentAdvance() + 1, SkIntToScalar(count))); 245 246 // The current interval advance can be +inf (e.g. when reaching 247 // the clamp mode end intervals) - when that happens, we expect to 248 // a) consume all remaining count in one swoop 249 // b) return a zero color gradient 250 SkASSERT(SkScalarIsFinite(proc.currentAdvance()) 251 || (n == count && proc.currentRampIsZero())); 252 253 if (proc.currentRampIsZero()) { 254 DstTraits<dstType, premul>::store(proc.currentColor(), dst, n); 255 } else { 256 ramp<dstType, premul>(proc.currentColor(), proc.currentColorGrad(), dst, n, 257 bias4f0, bias4f1); 258 } 259 260 proc.advance(SkIntToScalar(n)); 261 count -= n; 262 dst += n; 263 264 if (n & 1) { 265 SkTSwap(bias4f0, bias4f1); 266 } 267 } 268 } 269 270 template<typename dstType, ApplyPremul premul, SkShader::TileMode tileMode> 271 class SkLinearGradient:: 272 LinearGradient4fContext::LinearIntervalProcessor { 273 public: 274 LinearIntervalProcessor(const Sk4fGradientInterval* firstInterval, 275 const Sk4fGradientInterval* lastInterval, 276 const Sk4fGradientInterval* i, 277 SkScalar fx, 278 SkScalar dx, 279 bool is_vertical) 280 : fAdvX(is_vertical ? SK_ScalarInfinity : (i->fT1 - fx) / dx) 281 , fFirstInterval(firstInterval) 282 , fLastInterval(lastInterval) 283 , fInterval(i) 284 , fDx(dx) 285 , fIsVertical(is_vertical) 286 { 287 SkASSERT(fAdvX >= 0); 288 SkASSERT(firstInterval <= lastInterval); 289 290 if (tileMode != kClamp_TileMode && !is_vertical) { 291 const auto spanX = (lastInterval->fT1 - firstInterval->fT0) / dx; 292 SkASSERT(spanX >= 0); 293 294 // If we're in a repeating tile mode and the whole gradient is compressed into a 295 // fraction of a pixel, we just use the average color in zero-ramp mode. 296 // This also avoids cases where we make no progress due to interval advances being 297 // close to zero. 298 static constexpr SkScalar kMinSpanX = .25f; 299 if (spanX < kMinSpanX) { 300 this->init_average_props(); 301 return; 302 } 303 } 304 305 this->compute_interval_props(fx); 306 } 307 308 SkScalar currentAdvance() const { 309 SkASSERT(fAdvX >= 0); 310 SkASSERT(fAdvX <= (fInterval->fT1 - fInterval->fT0) / fDx || !std::isfinite(fAdvX)); 311 return fAdvX; 312 } 313 314 bool currentRampIsZero() const { return fZeroRamp; } 315 const Sk4f& currentColor() const { return fCc; } 316 const Sk4f& currentColorGrad() const { return fDcDx; } 317 318 void advance(SkScalar advX) { 319 SkASSERT(advX > 0); 320 SkASSERT(fAdvX >= 0); 321 322 if (advX >= fAdvX) { 323 advX = this->advance_interval(advX); 324 } 325 SkASSERT(advX < fAdvX); 326 327 fCc = fCc + fDcDx * Sk4f(advX); 328 fAdvX -= advX; 329 } 330 331 private: 332 void compute_interval_props(SkScalar t) { 333 SkASSERT(in_range(t, fInterval->fT0, fInterval->fT1)); 334 335 const Sk4f dc = DstTraits<dstType, premul>::load(fInterval->fCg); 336 fCc = DstTraits<dstType, premul>::load(fInterval->fCb) + dc * Sk4f(t); 337 fDcDx = dc * fDx; 338 fZeroRamp = fIsVertical || (dc == 0).allTrue(); 339 } 340 341 void init_average_props() { 342 fAdvX = SK_ScalarInfinity; 343 fZeroRamp = true; 344 fDcDx = 0; 345 fCc = Sk4f(0); 346 347 // TODO: precompute the average at interval setup time? 348 for (const auto* i = fFirstInterval; i <= fLastInterval; ++i) { 349 // Each interval contributes its average color to the total/weighted average: 350 // 351 // C = (c0 + c1) / 2 = (Cb + Cg * t0 + Cb + Cg * t1) / 2 = Cb + Cg *(t0 + t1) / 2 352 // 353 // Avg += C * (t1 - t0) 354 // 355 const auto c = DstTraits<dstType, premul>::load(i->fCb) 356 + DstTraits<dstType, premul>::load(i->fCg) * (i->fT0 + i->fT1) * 0.5f; 357 fCc = fCc + c * (i->fT1 - i->fT0); 358 } 359 } 360 361 const Sk4fGradientInterval* next_interval(const Sk4fGradientInterval* i) const { 362 SkASSERT(i >= fFirstInterval); 363 SkASSERT(i <= fLastInterval); 364 i++; 365 366 if (tileMode == kClamp_TileMode) { 367 SkASSERT(i <= fLastInterval); 368 return i; 369 } 370 371 return (i <= fLastInterval) ? i : fFirstInterval; 372 } 373 374 SkScalar advance_interval(SkScalar advX) { 375 SkASSERT(advX >= fAdvX); 376 377 do { 378 advX -= fAdvX; 379 fInterval = this->next_interval(fInterval); 380 fAdvX = (fInterval->fT1 - fInterval->fT0) / fDx; 381 SkASSERT(fAdvX > 0); 382 } while (advX >= fAdvX); 383 384 compute_interval_props(fInterval->fT0); 385 386 SkASSERT(advX >= 0); 387 return advX; 388 } 389 390 // Current interval properties. 391 Sk4f fDcDx; // dst color gradient (dc/dx) 392 Sk4f fCc; // current color, interpolated in dst 393 SkScalar fAdvX; // remaining interval advance in dst 394 bool fZeroRamp; // current interval color grad is 0 395 396 const Sk4fGradientInterval* fFirstInterval; 397 const Sk4fGradientInterval* fLastInterval; 398 const Sk4fGradientInterval* fInterval; // current interval 399 const SkScalar fDx; // 'dx' for consistency with other impls; actually dt/dx 400 const bool fIsVertical; 401 }; 402