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 kClamp_TileMode: 206 this->shadeSpanInternal<dstType, premul, kClamp_TileMode >(x, y, dst, count, bias0, bias1); 207 break; 208 case kRepeat_TileMode: 209 this->shadeSpanInternal<dstType, premul, kRepeat_TileMode>(x, y, dst, count, bias0, bias1); 210 break; 211 case kMirror_TileMode: 212 this->shadeSpanInternal<dstType, premul, kMirror_TileMode>(x, y, dst, count, bias0, bias1); 213 break; 214 } 215 } 216 217 template<typename dstType, ApplyPremul premul, SkShader::TileMode tileMode> 218 void SkLinearGradient:: 219 LinearGradient4fContext::shadeSpanInternal(int x, int y, dstType dst[], int count, 220 float bias0, float bias1) const { 221 SkPoint pt; 222 fDstToPosProc(fDstToPos, 223 x + SK_ScalarHalf, 224 y + SK_ScalarHalf, 225 &pt); 226 const SkScalar fx = pinFx<tileMode>(pt.x()); 227 const SkScalar dx = fDstToPos.getScaleX(); 228 LinearIntervalProcessor<dstType, premul, tileMode> proc(fIntervals->begin(), 229 fIntervals->end() - 1, 230 this->findInterval(fx), 231 fx, 232 dx, 233 SkScalarNearlyZero(dx * count)); 234 Sk4f bias4f0(bias0), 235 bias4f1(bias1); 236 237 while (count > 0) { 238 // What we really want here is SkTPin(advance, 1, count) 239 // but that's a significant perf hit for >> stops; investigate. 240 const int n = SkScalarTruncToInt( 241 SkTMin<SkScalar>(proc.currentAdvance() + 1, SkIntToScalar(count))); 242 243 // The current interval advance can be +inf (e.g. when reaching 244 // the clamp mode end intervals) - when that happens, we expect to 245 // a) consume all remaining count in one swoop 246 // b) return a zero color gradient 247 SkASSERT(SkScalarIsFinite(proc.currentAdvance()) 248 || (n == count && proc.currentRampIsZero())); 249 250 if (proc.currentRampIsZero()) { 251 DstTraits<dstType, premul>::store(proc.currentColor(), dst, n); 252 } else { 253 ramp<dstType, premul>(proc.currentColor(), proc.currentColorGrad(), dst, n, 254 bias4f0, bias4f1); 255 } 256 257 proc.advance(SkIntToScalar(n)); 258 count -= n; 259 dst += n; 260 261 if (n & 1) { 262 SkTSwap(bias4f0, bias4f1); 263 } 264 } 265 } 266 267 template<typename dstType, ApplyPremul premul, SkShader::TileMode tileMode> 268 class SkLinearGradient:: 269 LinearGradient4fContext::LinearIntervalProcessor { 270 public: 271 LinearIntervalProcessor(const Sk4fGradientInterval* firstInterval, 272 const Sk4fGradientInterval* lastInterval, 273 const Sk4fGradientInterval* i, 274 SkScalar fx, 275 SkScalar dx, 276 bool is_vertical) 277 : fAdvX(is_vertical ? SK_ScalarInfinity : (i->fT1 - fx) / dx) 278 , fFirstInterval(firstInterval) 279 , fLastInterval(lastInterval) 280 , fInterval(i) 281 , fDx(dx) 282 , fIsVertical(is_vertical) 283 { 284 SkASSERT(fAdvX >= 0); 285 SkASSERT(firstInterval <= lastInterval); 286 287 if (tileMode != kClamp_TileMode && !is_vertical) { 288 const auto spanX = (lastInterval->fT1 - firstInterval->fT0) / dx; 289 SkASSERT(spanX >= 0); 290 291 // If we're in a repeating tile mode and the whole gradient is compressed into a 292 // fraction of a pixel, we just use the average color in zero-ramp mode. 293 // This also avoids cases where we make no progress due to interval advances being 294 // close to zero. 295 static constexpr SkScalar kMinSpanX = .25f; 296 if (spanX < kMinSpanX) { 297 this->init_average_props(); 298 return; 299 } 300 } 301 302 this->compute_interval_props(fx); 303 } 304 305 SkScalar currentAdvance() const { 306 SkASSERT(fAdvX >= 0); 307 SkASSERT(fAdvX <= (fInterval->fT1 - fInterval->fT0) / fDx || !std::isfinite(fAdvX)); 308 return fAdvX; 309 } 310 311 bool currentRampIsZero() const { return fZeroRamp; } 312 const Sk4f& currentColor() const { return fCc; } 313 const Sk4f& currentColorGrad() const { return fDcDx; } 314 315 void advance(SkScalar advX) { 316 SkASSERT(advX > 0); 317 SkASSERT(fAdvX >= 0); 318 319 if (advX >= fAdvX) { 320 advX = this->advance_interval(advX); 321 } 322 SkASSERT(advX < fAdvX); 323 324 fCc = fCc + fDcDx * Sk4f(advX); 325 fAdvX -= advX; 326 } 327 328 private: 329 void compute_interval_props(SkScalar t) { 330 SkASSERT(in_range(t, fInterval->fT0, fInterval->fT1)); 331 332 const Sk4f dc = DstTraits<dstType, premul>::load(fInterval->fCg); 333 fCc = DstTraits<dstType, premul>::load(fInterval->fCb) + dc * Sk4f(t); 334 fDcDx = dc * fDx; 335 fZeroRamp = fIsVertical || (dc == 0).allTrue(); 336 } 337 338 void init_average_props() { 339 fAdvX = SK_ScalarInfinity; 340 fZeroRamp = true; 341 fDcDx = 0; 342 fCc = Sk4f(0); 343 344 // TODO: precompute the average at interval setup time? 345 for (const auto* i = fFirstInterval; i <= fLastInterval; ++i) { 346 // Each interval contributes its average color to the total/weighted average: 347 // 348 // C = (c0 + c1) / 2 = (Cb + Cg * t0 + Cb + Cg * t1) / 2 = Cb + Cg *(t0 + t1) / 2 349 // 350 // Avg += C * (t1 - t0) 351 // 352 const auto c = DstTraits<dstType, premul>::load(i->fCb) 353 + DstTraits<dstType, premul>::load(i->fCg) * (i->fT0 + i->fT1) * 0.5f; 354 fCc = fCc + c * (i->fT1 - i->fT0); 355 } 356 } 357 358 const Sk4fGradientInterval* next_interval(const Sk4fGradientInterval* i) const { 359 SkASSERT(i >= fFirstInterval); 360 SkASSERT(i <= fLastInterval); 361 i++; 362 363 if (tileMode == kClamp_TileMode) { 364 SkASSERT(i <= fLastInterval); 365 return i; 366 } 367 368 return (i <= fLastInterval) ? i : fFirstInterval; 369 } 370 371 SkScalar advance_interval(SkScalar advX) { 372 SkASSERT(advX >= fAdvX); 373 374 do { 375 advX -= fAdvX; 376 fInterval = this->next_interval(fInterval); 377 fAdvX = (fInterval->fT1 - fInterval->fT0) / fDx; 378 SkASSERT(fAdvX > 0); 379 } while (advX >= fAdvX); 380 381 compute_interval_props(fInterval->fT0); 382 383 SkASSERT(advX >= 0); 384 return advX; 385 } 386 387 // Current interval properties. 388 Sk4f fDcDx; // dst color gradient (dc/dx) 389 Sk4f fCc; // current color, interpolated in dst 390 SkScalar fAdvX; // remaining interval advance in dst 391 bool fZeroRamp; // current interval color grad is 0 392 393 const Sk4fGradientInterval* fFirstInterval; 394 const Sk4fGradientInterval* fLastInterval; 395 const Sk4fGradientInterval* fInterval; // current interval 396 const SkScalar fDx; // 'dx' for consistency with other impls; actually dt/dx 397 const bool fIsVertical; 398 }; 399