Sk4fGradientBase.cpp revision aa0ce825b877871f73532beb6fde6bf2f80e99dd
1dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes/* 2dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes * Copyright 2016 Google Inc. 3dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes * 4dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes * Use of this source code is governed by a BSD-style license that can be 5dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes * found in the LICENSE file. 6dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes */ 7dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes 8dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes#include "Sk4fGradientBase.h" 9dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes 10dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes#include <functional> 11dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes 12dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughesnamespace { 13dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes 14dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott HughesSk4f pack_color(const SkColor4f& c4f, bool premul, const Sk4f& component_scale) { 15dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes const Sk4f pm4f = premul 16e5fea3d504609d22337a5311d3ce0e72314bceeeNarayan Kamath ? c4f.premul().to4f() 17dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes : Sk4f{c4f.fR, c4f.fG, c4f.fB, c4f.fA}; 18dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes 19dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes return pm4f * component_scale; 20dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes} 21dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes 22dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughesclass IntervalIterator { 23dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughespublic: 24dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes IntervalIterator(const SkGradientShaderBase& shader, SkColorSpace* dstCS, bool reverse) 25dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes : fShader(shader) 26dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes , fDstCS(dstCS) 27dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes , fFirstPos(reverse ? SK_Scalar1 : 0) 28dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes , fBegin(reverse ? shader.fColorCount - 1 : 0) 29dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes , fAdvance(reverse ? -1 : 1) { 30dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes SkASSERT(shader.fColorCount > 0); 31dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes } 32dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes 33dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes void iterate(std::function<void(const SkColor4f&, const SkColor4f&, 34dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes SkScalar, SkScalar)> func) const { 35dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes if (!fShader.fOrigPos) { 36dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes this->iterateImplicitPos(func); 37dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes return; 38dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes } 39dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes 40dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes const int end = fBegin + fAdvance * (fShader.fColorCount - 1); 41dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes const SkScalar lastPos = 1 - fFirstPos; 42dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes int prev = fBegin; 43dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes SkScalar prevPos = fFirstPos; 44dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes 45dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes do { 46dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes const int curr = prev + fAdvance; 47dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes SkASSERT(curr >= 0 && curr < fShader.fColorCount); 48dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes 49dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes // TODO: this sanitization should be done in SkGradientShaderBase 50dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes const SkScalar currPos = (fAdvance > 0) 51dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes ? SkTPin(fShader.fOrigPos[curr], prevPos, lastPos) 52dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes : SkTPin(fShader.fOrigPos[curr], lastPos, prevPos); 53dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes 54dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes if (currPos != prevPos) { 55dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes SkASSERT((currPos - prevPos > 0) == (fAdvance > 0)); 56dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes func(fShader.getXformedColor(prev, fDstCS), fShader.getXformedColor(curr, fDstCS), 57dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes prevPos, currPos); 58dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes } 59dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes 60dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes prev = curr; 61dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes prevPos = currPos; 62dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes } while (prev != end); 63dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes } 64dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes 65dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughesprivate: 66dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes void iterateImplicitPos(std::function<void(const SkColor4f&, const SkColor4f&, 67dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes SkScalar, SkScalar)> func) const { 68dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes // When clients don't provide explicit color stop positions (fPos == nullptr), 69dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes // the color stops are distributed evenly across the unit interval 70dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes // (implicit positioning). 71dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes const SkScalar dt = fAdvance * SK_Scalar1 / (fShader.fColorCount - 1); 72dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes const int end = fBegin + fAdvance * (fShader.fColorCount - 2); 73dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes int prev = fBegin; 74dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes SkScalar prevPos = fFirstPos; 75dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes 76dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes while (prev != end) { 77dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes const int curr = prev + fAdvance; 78dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes SkASSERT(curr >= 0 && curr < fShader.fColorCount); 79dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes 80dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes const SkScalar currPos = prevPos + dt; 81dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes func(fShader.getXformedColor(prev, fDstCS), 82dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes fShader.getXformedColor(curr, fDstCS), 83dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes prevPos, currPos); 84dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes prev = curr; 85dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes prevPos = currPos; 86dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes } 87dce2b2fa9d6b26414a8d5a55918e4d7ca2ab1baaElliott Hughes 88 // emit the last interval with a pinned end position, to avoid precision issues 89 func(fShader.getXformedColor(prev, fDstCS), 90 fShader.getXformedColor(prev + fAdvance, fDstCS), 91 prevPos, 1 - fFirstPos); 92 } 93 94 const SkGradientShaderBase& fShader; 95 SkColorSpace* fDstCS; 96 const SkScalar fFirstPos; 97 const int fBegin; 98 const int fAdvance; 99}; 100 101void addMirrorIntervals(const SkGradientShaderBase& shader, 102 SkColorSpace* dstCS, 103 const Sk4f& componentScale, 104 bool premulColors, bool reverse, 105 Sk4fGradientIntervalBuffer::BufferType* buffer) { 106 const IntervalIterator iter(shader, dstCS, reverse); 107 iter.iterate([&] (const SkColor4f& c0, const SkColor4f& c1, SkScalar t0, SkScalar t1) { 108 SkASSERT(buffer->empty() || buffer->back().fT1 == 2 - t0); 109 110 const auto mirror_t0 = 2 - t0; 111 const auto mirror_t1 = 2 - t1; 112 // mirror_p1 & mirror_p1 may collapse for very small values - recheck to avoid 113 // triggering Interval asserts. 114 if (mirror_t0 != mirror_t1) { 115 buffer->emplace_back(pack_color(c0, premulColors, componentScale), mirror_t0, 116 pack_color(c1, premulColors, componentScale), mirror_t1); 117 } 118 }); 119} 120 121} // anonymous namespace 122 123Sk4fGradientInterval::Sk4fGradientInterval(const Sk4f& c0, SkScalar t0, 124 const Sk4f& c1, SkScalar t1) 125 : fT0(t0) 126 , fT1(t1) { 127 SkASSERT(t0 != t1); 128 // Either p0 or p1 can be (-)inf for synthetic clamp edge intervals. 129 SkASSERT(SkScalarIsFinite(t0) || SkScalarIsFinite(t1)); 130 131 const auto dt = t1 - t0; 132 133 // Clamp edge intervals are always zero-ramp. 134 SkASSERT(SkScalarIsFinite(dt) || (c0 == c1).allTrue()); 135 SkASSERT(SkScalarIsFinite(t0) || (c0 == c1).allTrue()); 136 const Sk4f dc = SkScalarIsFinite(dt) ? (c1 - c0) / dt : 0; 137 const Sk4f bias = c0 - (SkScalarIsFinite(t0) ? t0 * dc : 0); 138 139 bias.store(&fCb.fVec); 140 dc.store(&fCg.fVec); 141} 142 143void Sk4fGradientIntervalBuffer::init(const SkGradientShaderBase& shader, SkColorSpace* dstCS, 144 SkShader::TileMode tileMode, bool premulColors, 145 SkScalar alpha, bool reverse) { 146 // The main job here is to build a specialized interval list: a different 147 // representation of the color stops data, optimized for efficient scan line 148 // access during shading. 149 // 150 // [{P0,C0} , {P1,C1}) [{P1,C2} , {P2,c3}) ... [{Pn,C2n} , {Pn+1,C2n+1}) 151 // 152 // The list may be inverted when requested (such that e.g. points are sorted 153 // in increasing x order when dx < 0). 154 // 155 // Note: the current representation duplicates pos data; we could refactor to 156 // avoid this if interval storage size becomes a concern. 157 // 158 // Aside from reordering, we also perform two more pre-processing steps at 159 // this stage: 160 // 161 // 1) scale the color components depending on paint alpha and the requested 162 // interpolation space (note: the interval color storage is SkPM4f, but 163 // that doesn't necessarily mean the colors are premultiplied; that 164 // property is tracked in fColorsArePremul) 165 // 166 // 2) inject synthetic intervals to support tiling. 167 // 168 // * for kRepeat, no extra intervals are needed - the iterator just 169 // wraps around at the end: 170 // 171 // ->[P0,P1)->..[Pn-1,Pn)-> 172 // 173 // * for kClamp, we add two "infinite" intervals before/after: 174 // 175 // [-/+inf , P0)->[P0 , P1)->..[Pn-1 , Pn)->[Pn , +/-inf) 176 // 177 // (the iterator should never run off the end in this mode) 178 // 179 // * for kMirror, we extend the range to [0..2] and add a flipped 180 // interval series - then the iterator operates just as in the 181 // kRepeat case: 182 // 183 // ->[P0,P1)->..[Pn-1,Pn)->[2 - Pn,2 - Pn-1)->..[2 - P1,2 - P0)-> 184 // 185 // TODO: investigate collapsing intervals << 1px. 186 187 const auto count = shader.fColorCount; 188 189 SkASSERT(count > 0); 190 191 fIntervals.reset(); 192 193 const Sk4f componentScale = premulColors 194 ? Sk4f(alpha) 195 : Sk4f(1.0f, 1.0f, 1.0f, alpha); 196 const int first_index = reverse ? count - 1 : 0; 197 const int last_index = count - 1 - first_index; 198 const SkScalar first_pos = reverse ? SK_Scalar1 : 0; 199 const SkScalar last_pos = SK_Scalar1 - first_pos; 200 201 if (tileMode == SkShader::kClamp_TileMode) { 202 // synthetic edge interval: -/+inf .. P0 203 const Sk4f clamp_color = pack_color(shader.getXformedColor(first_index, dstCS), 204 premulColors, componentScale); 205 const SkScalar clamp_pos = reverse ? SK_ScalarInfinity : SK_ScalarNegativeInfinity; 206 fIntervals.emplace_back(clamp_color, clamp_pos, 207 clamp_color, first_pos); 208 } else if (tileMode == SkShader::kMirror_TileMode && reverse) { 209 // synthetic mirror intervals injected before main intervals: (2 .. 1] 210 addMirrorIntervals(shader, dstCS, componentScale, premulColors, false, &fIntervals); 211 } 212 213 const IntervalIterator iter(shader, dstCS, reverse); 214 iter.iterate([&] (const SkColor4f& c0, const SkColor4f& c1, SkScalar t0, SkScalar t1) { 215 SkASSERT(fIntervals.empty() || fIntervals.back().fT1 == t0); 216 217 fIntervals.emplace_back(pack_color(c0, premulColors, componentScale), t0, 218 pack_color(c1, premulColors, componentScale), t1); 219 }); 220 221 if (tileMode == SkShader::kClamp_TileMode) { 222 // synthetic edge interval: Pn .. +/-inf 223 const Sk4f clamp_color = pack_color(shader.getXformedColor(last_index, dstCS), 224 premulColors, componentScale); 225 const SkScalar clamp_pos = reverse ? SK_ScalarNegativeInfinity : SK_ScalarInfinity; 226 fIntervals.emplace_back(clamp_color, last_pos, 227 clamp_color, clamp_pos); 228 } else if (tileMode == SkShader::kMirror_TileMode && !reverse) { 229 // synthetic mirror intervals injected after main intervals: [1 .. 2) 230 addMirrorIntervals(shader, dstCS, componentScale, premulColors, true, &fIntervals); 231 } 232} 233 234const Sk4fGradientInterval* Sk4fGradientIntervalBuffer::find(SkScalar t) const { 235 // Binary search. 236 const auto* i0 = fIntervals.begin(); 237 const auto* i1 = fIntervals.end() - 1; 238 239 while (i0 != i1) { 240 SkASSERT(i0 < i1); 241 SkASSERT(t >= i0->fT0 && t <= i1->fT1); 242 243 const auto* i = i0 + ((i1 - i0) >> 1); 244 245 if (t > i->fT1) { 246 i0 = i + 1; 247 } else { 248 i1 = i; 249 } 250 } 251 252 SkASSERT(i0->contains(t)); 253 return i0; 254} 255 256const Sk4fGradientInterval* Sk4fGradientIntervalBuffer::findNext( 257 SkScalar t, const Sk4fGradientInterval* prev, bool increasing) const { 258 259 SkASSERT(!prev->contains(t)); 260 SkASSERT(prev >= fIntervals.begin() && prev < fIntervals.end()); 261 SkASSERT(t >= fIntervals.front().fT0 && t <= fIntervals.back().fT1); 262 263 const auto* i = prev; 264 265 // Use the |increasing| signal to figure which direction we should search for 266 // the next interval, then perform a linear search. 267 if (increasing) { 268 do { 269 i += 1; 270 if (i >= fIntervals.end()) { 271 i = fIntervals.begin(); 272 } 273 } while (!i->contains(t)); 274 } else { 275 do { 276 i -= 1; 277 if (i < fIntervals.begin()) { 278 i = fIntervals.end() - 1; 279 } 280 } while (!i->contains(t)); 281 } 282 283 return i; 284} 285 286SkGradientShaderBase:: 287GradientShaderBase4fContext::GradientShaderBase4fContext(const SkGradientShaderBase& shader, 288 const ContextRec& rec) 289 : INHERITED(shader, rec) 290 , fFlags(this->INHERITED::getFlags()) 291 , fDither(rec.fPaint->isDither()) 292{ 293 const SkMatrix& inverse = this->getTotalInverse(); 294 fDstToPos.setConcat(shader.fPtsToUnit, inverse); 295 SkASSERT(!fDstToPos.hasPerspective()); 296 fDstToPosProc = fDstToPos.getMapXYProc(); 297 298 if (shader.fColorsAreOpaque && this->getPaintAlpha() == SK_AlphaOPAQUE) { 299 fFlags |= kOpaqueAlpha_Flag; 300 } 301 302 fColorsArePremul = 303 (shader.fGradFlags & SkGradientShader::kInterpolateColorsInPremul_Flag) 304 || shader.fColorsAreOpaque; 305} 306 307bool SkGradientShaderBase:: 308GradientShaderBase4fContext::isValid() const { 309 return fDstToPos.isFinite(); 310} 311