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 "SkPaint.h" 10 11 #include <cmath> 12 #include <utility> 13 14 namespace { 15 16 template<ApplyPremul premul> 17 void ramp(const Sk4f& c, const Sk4f& dc, SkPMColor 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<premul>::pre_lerp_bias(bias0), 25 c1 = c + dc + DstTraits<premul>::pre_lerp_bias(bias1), 26 c2 = c0 + dc2, 27 c3 = c1 + dc2; 28 29 while (n >= 4) { 30 DstTraits<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<premul>::store(c0, dst++, bias0); 41 DstTraits<premul>::store(c1, dst++, bias1); 42 c0 = c0 + dc2; 43 } 44 if (n & 1) { 45 DstTraits<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 = SkScalarIsFinite(fx) ? SkScalarFraction(fx) : 0; 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 = SkScalarIsFinite(fx) ? SkScalarMod(fx, 2.0f) : 0; 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 using std::swap; 166 swap(bias0, bias1); 167 } 168 } 169 170 if (fColorsArePremul) { 171 // In premul interpolation mode, components are pre-scaled by 255 and the store 172 // op is truncating. We pre-bias here to achieve rounding. 173 bias0 += 0.5f; 174 bias1 += 0.5f; 175 176 this->shadePremulSpan<ApplyPremul::False>(x, y, dst, count, bias0, bias1); 177 } else { 178 // In unpremul interpolation mode, Components are not pre-scaled. 179 bias0 *= 1/255.0f; 180 bias1 *= 1/255.0f; 181 182 this->shadePremulSpan<ApplyPremul::True >(x, y, dst, count, bias0, bias1); 183 } 184 } 185 186 template<ApplyPremul premul> 187 void SkLinearGradient:: 188 LinearGradient4fContext::shadePremulSpan(int x, int y, SkPMColor dst[], int count, 189 float bias0, float bias1) const { 190 const SkLinearGradient& shader = static_cast<const SkLinearGradient&>(fShader); 191 switch (shader.fTileMode) { 192 case kDecal_TileMode: 193 SkASSERT(false); // decal only supported via stages 194 // fall-through 195 case kClamp_TileMode: 196 this->shadeSpanInternal<premul, kClamp_TileMode >(x, y, dst, count, bias0, bias1); 197 break; 198 case kRepeat_TileMode: 199 this->shadeSpanInternal<premul, kRepeat_TileMode>(x, y, dst, count, bias0, bias1); 200 break; 201 case kMirror_TileMode: 202 this->shadeSpanInternal<premul, kMirror_TileMode>(x, y, dst, count, bias0, bias1); 203 break; 204 } 205 } 206 207 template<ApplyPremul premul, SkShader::TileMode tileMode> 208 void SkLinearGradient:: 209 LinearGradient4fContext::shadeSpanInternal(int x, int y, SkPMColor dst[], int count, 210 float bias0, float bias1) const { 211 SkPoint pt; 212 fDstToPosProc(fDstToPos, 213 x + SK_ScalarHalf, 214 y + SK_ScalarHalf, 215 &pt); 216 const SkScalar fx = pinFx<tileMode>(pt.x()); 217 const SkScalar dx = fDstToPos.getScaleX(); 218 LinearIntervalProcessor<premul, tileMode> proc(fIntervals->begin(), 219 fIntervals->end() - 1, 220 this->findInterval(fx), 221 fx, 222 dx, 223 SkScalarNearlyZero(dx * count)); 224 Sk4f bias4f0(bias0), 225 bias4f1(bias1); 226 227 while (count > 0) { 228 // What we really want here is SkTPin(advance, 1, count) 229 // but that's a significant perf hit for >> stops; investigate. 230 const int n = SkTMin(SkScalarTruncToInt(proc.currentAdvance() + 1), count); 231 232 // The current interval advance can be +inf (e.g. when reaching 233 // the clamp mode end intervals) - when that happens, we expect to 234 // a) consume all remaining count in one swoop 235 // b) return a zero color gradient 236 SkASSERT(SkScalarIsFinite(proc.currentAdvance()) 237 || (n == count && proc.currentRampIsZero())); 238 239 if (proc.currentRampIsZero()) { 240 DstTraits<premul>::store(proc.currentColor(), dst, n); 241 } else { 242 ramp<premul>(proc.currentColor(), proc.currentColorGrad(), dst, n, 243 bias4f0, bias4f1); 244 } 245 246 proc.advance(SkIntToScalar(n)); 247 count -= n; 248 dst += n; 249 250 if (n & 1) { 251 using std::swap; 252 swap(bias4f0, bias4f1); 253 } 254 } 255 } 256 257 template<ApplyPremul premul, SkShader::TileMode tileMode> 258 class SkLinearGradient:: 259 LinearGradient4fContext::LinearIntervalProcessor { 260 public: 261 LinearIntervalProcessor(const Sk4fGradientInterval* firstInterval, 262 const Sk4fGradientInterval* lastInterval, 263 const Sk4fGradientInterval* i, 264 SkScalar fx, 265 SkScalar dx, 266 bool is_vertical) 267 : fAdvX(is_vertical ? SK_ScalarInfinity : (i->fT1 - fx) / dx) 268 , fFirstInterval(firstInterval) 269 , fLastInterval(lastInterval) 270 , fInterval(i) 271 , fDx(dx) 272 , fIsVertical(is_vertical) 273 { 274 SkASSERT(fAdvX >= 0); 275 SkASSERT(firstInterval <= lastInterval); 276 277 if (tileMode != kClamp_TileMode && !is_vertical) { 278 const auto spanX = (lastInterval->fT1 - firstInterval->fT0) / dx; 279 SkASSERT(spanX >= 0); 280 281 // If we're in a repeating tile mode and the whole gradient is compressed into a 282 // fraction of a pixel, we just use the average color in zero-ramp mode. 283 // This also avoids cases where we make no progress due to interval advances being 284 // close to zero. 285 static constexpr SkScalar kMinSpanX = .25f; 286 if (spanX < kMinSpanX) { 287 this->init_average_props(); 288 return; 289 } 290 } 291 292 this->compute_interval_props(fx); 293 } 294 295 SkScalar currentAdvance() const { 296 SkASSERT(fAdvX >= 0); 297 SkASSERT(!std::isfinite(fAdvX) || fAdvX <= (fInterval->fT1 - fInterval->fT0) / fDx); 298 return fAdvX; 299 } 300 301 bool currentRampIsZero() const { return fZeroRamp; } 302 const Sk4f& currentColor() const { return fCc; } 303 const Sk4f& currentColorGrad() const { return fDcDx; } 304 305 void advance(SkScalar advX) { 306 SkASSERT(advX > 0); 307 SkASSERT(fAdvX >= 0); 308 309 if (advX >= fAdvX) { 310 advX = this->advance_interval(advX); 311 } 312 SkASSERT(advX < fAdvX); 313 314 fCc = fCc + fDcDx * Sk4f(advX); 315 fAdvX -= advX; 316 } 317 318 private: 319 void compute_interval_props(SkScalar t) { 320 SkASSERT(in_range(t, fInterval->fT0, fInterval->fT1)); 321 322 const Sk4f dc = DstTraits<premul>::load(fInterval->fCg); 323 fCc = DstTraits<premul>::load(fInterval->fCb) + dc * Sk4f(t); 324 fDcDx = dc * fDx; 325 fZeroRamp = fIsVertical || (dc == 0).allTrue(); 326 } 327 328 void init_average_props() { 329 fAdvX = SK_ScalarInfinity; 330 fZeroRamp = true; 331 fDcDx = 0; 332 fCc = Sk4f(0); 333 334 // TODO: precompute the average at interval setup time? 335 for (const auto* i = fFirstInterval; i <= fLastInterval; ++i) { 336 // Each interval contributes its average color to the total/weighted average: 337 // 338 // C = (c0 + c1) / 2 = (Cb + Cg * t0 + Cb + Cg * t1) / 2 = Cb + Cg *(t0 + t1) / 2 339 // 340 // Avg += C * (t1 - t0) 341 // 342 const auto c = DstTraits<premul>::load(i->fCb) 343 + DstTraits<premul>::load(i->fCg) * (i->fT0 + i->fT1) * 0.5f; 344 fCc = fCc + c * (i->fT1 - i->fT0); 345 } 346 } 347 348 const Sk4fGradientInterval* next_interval(const Sk4fGradientInterval* i) const { 349 SkASSERT(i >= fFirstInterval); 350 SkASSERT(i <= fLastInterval); 351 i++; 352 353 if (tileMode == kClamp_TileMode) { 354 SkASSERT(i <= fLastInterval); 355 return i; 356 } 357 358 return (i <= fLastInterval) ? i : fFirstInterval; 359 } 360 361 SkScalar advance_interval(SkScalar advX) { 362 SkASSERT(advX >= fAdvX); 363 364 do { 365 advX -= fAdvX; 366 fInterval = this->next_interval(fInterval); 367 fAdvX = (fInterval->fT1 - fInterval->fT0) / fDx; 368 SkASSERT(fAdvX > 0); 369 } while (advX >= fAdvX); 370 371 compute_interval_props(fInterval->fT0); 372 373 SkASSERT(advX >= 0); 374 return advX; 375 } 376 377 // Current interval properties. 378 Sk4f fDcDx; // dst color gradient (dc/dx) 379 Sk4f fCc; // current color, interpolated in dst 380 SkScalar fAdvX; // remaining interval advance in dst 381 bool fZeroRamp; // current interval color grad is 0 382 383 const Sk4fGradientInterval* fFirstInterval; 384 const Sk4fGradientInterval* fLastInterval; 385 const Sk4fGradientInterval* fInterval; // current interval 386 const SkScalar fDx; // 'dx' for consistency with other impls; actually dt/dx 387 const bool fIsVertical; 388 }; 389