Index: src/effects/gradients/SkLinearGradient.cpp |
diff --git a/src/effects/gradients/SkLinearGradient.cpp b/src/effects/gradients/SkLinearGradient.cpp |
index ada9d70b6a19af49520d1eaaebd9fad6678704f0..fdc016c0d2cf601714d556380f97b691e8610d88 100644 |
--- a/src/effects/gradients/SkLinearGradient.cpp |
+++ b/src/effects/gradients/SkLinearGradient.cpp |
@@ -7,6 +7,8 @@ |
#include "SkLinearGradient.h" |
+static const float kInv255Float = 1.0f / 255; |
+ |
static inline int repeat_bits(int x, const int bits) { |
return x & ((1 << bits) - 1); |
} |
@@ -86,20 +88,86 @@ SkShader::Context* SkLinearGradient::onCreateContext(const ContextRec& rec, void |
return new (storage) LinearGradientContext(*this, rec); |
} |
+// This swizzles SkColor into the same component order as SkPMColor, but does not actually |
+// "pre" multiply the color components. |
+// |
+// This allows us to map directly to Sk4f, and eventually scale down to bytes to output a |
+// SkPMColor from the floats, without having to swizzle each time. |
+// |
+static uint32_t SkSwizzle_Color_to_PMColor(SkColor c) { |
+ return SkPackARGB32NoCheck(SkColorGetA(c), SkColorGetR(c), SkColorGetG(c), SkColorGetB(c)); |
+} |
+ |
SkLinearGradient::LinearGradientContext::LinearGradientContext( |
- const SkLinearGradient& shader, const ContextRec& rec) |
- : INHERITED(shader, rec) |
+ const SkLinearGradient& shader, const ContextRec& ctx) |
+ : INHERITED(shader, ctx) |
{ |
unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask; |
if ((fDstToIndex.getType() & ~mask) == 0) { |
// when we dither, we are (usually) not const-in-Y |
- if ((fFlags & SkShader::kHasSpan16_Flag) && !rec.fPaint->isDither()) { |
+ if ((fFlags & SkShader::kHasSpan16_Flag) && !ctx.fPaint->isDither()) { |
// only claim this if we do have a 16bit mode (i.e. none of our |
// colors have alpha), and if we are not dithering (which obviously |
// is not const in Y). |
fFlags |= SkShader::kConstInY16_Flag; |
} |
} |
+ |
+ // setup for Sk4f |
+ int count = shader.fColorCount; |
+ fRecs.setCount(count); |
+ Rec* rec = fRecs.begin(); |
+ if (shader.fOrigPos) { |
+ rec[0].fPos = 0; |
+ SkDEBUGCODE(rec[0].fPosScale = SK_FloatNaN;) // should never get used |
+ for (int i = 1; i < count; ++i) { |
+ rec[i].fPos = SkTPin(shader.fOrigPos[i], rec[i - 1].fPos, 1.0f); |
+ rec[i].fPosScale = 1.0f / (rec[i].fPos - rec[i - 1].fPos); |
+ } |
+ rec[count - 1].fPos = 1; // overwrite the last value just to be sure we end at 1.0 |
+ } else { |
+ // no pos specified, so we compute evenly spaced values |
+ const float scale = float(count - 1); |
+ float invScale = 1.0f / scale; |
+ for (int i = 0; i < count; ++i) { |
+ rec[i].fPos = i * invScale; |
+ rec[i].fPosScale = scale; |
+ } |
+ } |
+ |
+ fApplyAlphaAfterInterp = true; |
+ if ((shader.getGradFlags() & SkGradientShader::kInterpolateColorsInPremul_Flag) || |
+ shader.colorsAreOpaque()) |
+ { |
+ fApplyAlphaAfterInterp = false; |
+ } |
+ |
+ if (fApplyAlphaAfterInterp) { |
+ // Our fColor values are in PMColor order, but are still unpremultiplied, allowing us to |
+ // interpolate in unpremultiplied space first, and then scale by alpha right before we |
+ // convert to SkPMColor bytes. |
+ const float paintAlpha = ctx.fPaint->getAlpha() * kInv255Float; |
+ const Sk4f scale(1, 1, 1, paintAlpha); |
+ for (int i = 0; i < count; ++i) { |
+ uint32_t c = SkSwizzle_Color_to_PMColor(shader.fOrigColors[i]); |
+ rec[i].fColor = Sk4f::FromBytes((const uint8_t*)&c) * scale; |
+ if (i > 0) { |
+ SkASSERT(rec[i - 1].fPos <= rec[i].fPos); |
+ } |
+ } |
+ } else { |
+ // Our fColor values are premultiplied, so converting to SkPMColor is just a matter |
+ // of converting the floats down to bytes. |
+ unsigned alphaScale = ctx.fPaint->getAlpha() + (ctx.fPaint->getAlpha() >> 7); |
+ for (int i = 0; i < count; ++i) { |
+ SkPMColor pmc = SkPreMultiplyColor(shader.fOrigColors[i]); |
+ pmc = SkAlphaMulQ(pmc, alphaScale); |
+ rec[i].fColor = Sk4f::FromBytes((const uint8_t*)&pmc); |
+ if (i > 0) { |
+ SkASSERT(rec[i - 1].fPos <= rec[i].fPos); |
+ } |
+ } |
+ } |
} |
#define NO_CHECK_ITER \ |
@@ -212,9 +280,17 @@ void shadeSpan_linear_repeat(TileProc proc, SkGradFixed dx, SkGradFixed fx, |
void SkLinearGradient::LinearGradientContext::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, |
int count) { |
SkASSERT(count > 0); |
- |
const SkLinearGradient& linearGradient = static_cast<const SkLinearGradient&>(fShader); |
+#ifndef SK_SUPPORT_LEGACY_LINEAR_GRADIENT_TABLE |
+ if (SkShader::kClamp_TileMode == linearGradient.fTileMode && |
+ kLinear_MatrixClass == fDstToIndexClass) |
+ { |
+ this->shade4_clamp(x, y, dstC, count); |
+ return; |
+ } |
+#endif |
+ |
SkPoint srcPt; |
SkMatrix::MapXYProc dstProc = fDstToIndexProc; |
TileProc proc = linearGradient.fTileProc; |
@@ -576,3 +652,264 @@ void SkLinearGradient::toString(SkString* str) const { |
str->append(")"); |
} |
#endif |
+ |
+/////////////////////////////////////////////////////////////////////////////////////////////////// |
+ |
+#include "SkNx.h" |
+ |
+static const SkLinearGradient::LinearGradientContext::Rec* |
+find_forward(const SkLinearGradient::LinearGradientContext::Rec rec[], float tiledX) { |
+ SkASSERT(tiledX >= 0 && tiledX <= 1); |
+ |
+ SkASSERT(rec[0].fPos >= 0 && rec[0].fPos <= 1); |
+ SkASSERT(rec[1].fPos >= 0 && rec[1].fPos <= 1); |
+ SkASSERT(rec[0].fPos <= rec[1].fPos); |
+ rec += 1; |
+ while (rec->fPos < tiledX) { |
+ SkASSERT(rec[0].fPos >= 0 && rec[0].fPos <= 1); |
+ SkASSERT(rec[1].fPos >= 0 && rec[1].fPos <= 1); |
+ SkASSERT(rec[0].fPos <= rec[1].fPos); |
+ rec += 1; |
+ } |
+ return rec - 1; |
+} |
+ |
+static const SkLinearGradient::LinearGradientContext::Rec* |
+find_backward(const SkLinearGradient::LinearGradientContext::Rec rec[], float tiledX) { |
+ SkASSERT(tiledX >= 0 && tiledX <= 1); |
+ |
+ SkASSERT(rec[0].fPos >= 0 && rec[0].fPos <= 1); |
+ SkASSERT(rec[1].fPos >= 0 && rec[1].fPos <= 1); |
+ SkASSERT(rec[0].fPos <= rec[1].fPos); |
+ while (tiledX < rec->fPos) { |
+ rec -= 1; |
+ SkASSERT(rec[0].fPos >= 0 && rec[0].fPos <= 1); |
+ SkASSERT(rec[1].fPos >= 0 && rec[1].fPos <= 1); |
+ SkASSERT(rec[0].fPos <= rec[1].fPos); |
+ } |
+ return rec; |
+} |
+ |
+template <bool apply_alpha> SkPMColor trunc_from_255(const Sk4f& x) { |
+ SkPMColor c; |
+ x.toBytes((uint8_t*)&c); |
+ if (apply_alpha) { |
+ c = SkPreMultiplyARGB(SkGetPackedA32(c), SkGetPackedR32(c), |
+ SkGetPackedG32(c), SkGetPackedB32(c)); |
+ } |
+ return c; |
+} |
+ |
+template <bool apply_alpha> void fill(SkPMColor dst[], int count, |
+ const Sk4f& c4, const Sk4f& c4other) { |
+ sk_memset32_dither(dst, trunc_from_255<apply_alpha>(c4), |
+ trunc_from_255<apply_alpha>(c4other), count); |
+} |
+ |
+template <bool apply_alpha> void fill(SkPMColor dst[], int count, const Sk4f& c4) { |
+ // Assumes that c4 does not need to be dithered. |
+ sk_memset32(dst, trunc_from_255<apply_alpha>(c4), count); |
+} |
+ |
+/* |
+ * TODOs |
+ * |
+ * - tilemodes |
+ * - interp before or after premul |
+ * - perspective |
+ * - optimizations |
+ * - use fixed (32bit or 16bit) instead of floats? |
+ */ |
+ |
+static Sk4f lerp_color(float fx, const SkLinearGradient::LinearGradientContext::Rec* rec) { |
+ const float p0 = rec[0].fPos; |
+ const Sk4f c0 = rec[0].fColor; |
+ const Sk4f c1 = rec[1].fColor; |
+ const Sk4f diffc = c1 - c0; |
+ const float scale = rec[1].fPosScale; |
+ const float t = (fx - p0) * scale; |
+ return c0 + Sk4f(t) * diffc; |
+} |
+ |
+template <bool apply_alpha> void ramp(SkPMColor dstC[], int n, const Sk4f& c, const Sk4f& dc, |
+ const Sk4f& dither0, const Sk4f& dither1) { |
+ Sk4f dc2 = dc + dc; |
+ Sk4f dc4 = dc2 + dc2; |
+ Sk4f cd0 = c + dither0; |
+ Sk4f cd1 = c + dc + dither1; |
+ Sk4f cd2 = cd0 + dc2; |
+ Sk4f cd3 = cd1 + dc2; |
+ while (n >= 4) { |
+ *dstC++ = trunc_from_255<apply_alpha>(cd0); |
+ *dstC++ = trunc_from_255<apply_alpha>(cd1); |
+ *dstC++ = trunc_from_255<apply_alpha>(cd2); |
+ *dstC++ = trunc_from_255<apply_alpha>(cd3); |
+ cd0 = cd0 + dc4; |
+ cd1 = cd1 + dc4; |
+ cd2 = cd2 + dc4; |
+ cd3 = cd3 + dc4; |
+ n -= 4; |
+ } |
+ if (n & 2) { |
+ *dstC++ = trunc_from_255<apply_alpha>(cd0); |
+ *dstC++ = trunc_from_255<apply_alpha>(cd1); |
+ cd0 = cd0 + dc2; |
+ } |
+ if (n & 1) { |
+ *dstC++ = trunc_from_255<apply_alpha>(cd0); |
+ } |
+} |
+ |
+template <bool apply_alpha, bool dx_is_pos> |
+void SkLinearGradient::LinearGradientContext::shade4_dx_clamp(SkPMColor dstC[], int count, |
+ float fx, float dx, float invDx, |
+ const float dither[2]) { |
+ Sk4f dither0(dither[0]); |
+ Sk4f dither1(dither[1]); |
+ const Rec* rec = fRecs.begin(); |
+ |
+ const Sk4f dx4 = Sk4f(dx); |
+ SkDEBUGCODE(SkPMColor* endDstC = dstC + count;) |
+ |
+ if (dx_is_pos) { |
+ if (fx < 0) { |
+ int n = SkTMin(SkFloatToIntFloor(-fx * invDx) + 1, count); |
+ fill<apply_alpha>(dstC, n, rec[0].fColor); |
+ count -= n; |
+ dstC += n; |
+ fx += n * dx; |
+ SkASSERT(0 == count || fx >= 0); |
+ if (n & 1) { |
+ SkTSwap(dither0, dither1); |
+ } |
+ } |
+ } else { // dx < 0 |
+ if (fx > 1) { |
+ int n = SkTMin(SkFloatToIntFloor((1 - fx) * invDx) + 1, count); |
+ fill<apply_alpha>(dstC, n, rec[fRecs.count() - 1].fColor); |
+ count -= n; |
+ dstC += n; |
+ fx += n * dx; |
+ SkASSERT(0 == count || fx <= 1); |
+ if (n & 1) { |
+ SkTSwap(dither0, dither1); |
+ } |
+ } |
+ } |
+ SkASSERT(count >= 0); |
+ |
+ const Rec* r; |
+ if (dx_is_pos) { |
+ r = fRecs.begin(); // start at the beginning |
+ } else { |
+ r = fRecs.begin() + fRecs.count() - 2; // start at the end |
+ } |
+ |
+ while (count > 0) { |
+ if (dx_is_pos) { |
+ if (fx >= 1) { |
+ fill<apply_alpha>(dstC, count, rec[fRecs.count() - 1].fColor); |
+ return; |
+ } |
+ } else { // dx < 0 |
+ if (fx <= 0) { |
+ fill<apply_alpha>(dstC, count, rec[0].fColor); |
+ return; |
+ } |
+ } |
+ |
+ if (dx_is_pos) { |
+ r = find_forward(r, fx); |
+ } else { |
+ r = find_backward(r, fx); |
+ } |
+ SkASSERT(r >= fRecs.begin() && r < fRecs.begin() + fRecs.count() - 1); |
+ |
+ const float p0 = r[0].fPos; |
+ const Sk4f c0 = r[0].fColor; |
+ const float p1 = r[1].fPos; |
+ const Sk4f diffc = Sk4f(r[1].fColor) - c0; |
+ const float scale = r[1].fPosScale; |
+ const float t = (fx - p0) * scale; |
+ const Sk4f c = c0 + Sk4f(t) * diffc; |
+ const Sk4f dc = diffc * dx4 * Sk4f(scale); |
+ |
+ int n; |
+ if (dx_is_pos) { |
+ n = SkTMin((int)((p1 - fx) * invDx) + 1, count); |
+ } else { |
+ n = SkTMin((int)((p0 - fx) * invDx) + 1, count); |
+ } |
+ |
+ fx += n * dx; |
+ count -= n; |
+ SkASSERT(count >= 0); |
+ if (dx_is_pos) { |
+ SkASSERT(0 == count || fx >= p1); |
+ } else { |
+ SkASSERT(0 == count || fx <= p0); |
+ } |
+ |
+ ramp<apply_alpha>(dstC, n, c, dc, dither0, dither1); |
+ dstC += n; |
+ SkASSERT(dstC <= endDstC); |
+ |
+ if (n & 1) { |
+ SkTSwap(dither0, dither1); |
+ } |
+ } |
+} |
+ |
+void SkLinearGradient::LinearGradientContext::shade4_clamp(int x, int y, SkPMColor dstC[], |
+ int count) { |
+ SkASSERT(count > 0); |
+ SkASSERT(kLinear_MatrixClass == fDstToIndexClass); |
+ |
+ SkPoint srcPt; |
+ fDstToIndexProc(fDstToIndex, x + SK_ScalarHalf, y + SK_ScalarHalf, &srcPt); |
+ float fx = srcPt.x(); |
+ const float dx = fDstToIndex.getScaleX(); |
+ |
+ // Default our dither bias values to 1/2, (rounding), which is no dithering |
+ float dither0 = 0.5f; |
+ float dither1 = 0.5f; |
+ if (fDither) { |
+ const float ditherCell[] = { |
+ 1/8.0f, 5/8.0f, |
+ 7/8.0f, 3/8.0f, |
+ }; |
+ const int rowIndex = (y & 1) << 1; |
+ dither0 = ditherCell[rowIndex]; |
+ dither1 = ditherCell[rowIndex + 1]; |
+ if (x & 1) { |
+ SkTSwap(dither0, dither1); |
+ } |
+ } |
+ const float dither[2] = { dither0, dither1 }; |
+ const float invDx = 1 / dx; |
+ |
+ if (!SkScalarIsFinite(invDx)) { // dx is effectively zero, gradient is vertical |
+ Sk4f c = lerp_color(fx, find_forward(fRecs.begin(), SkTPin(fx, 0.0f, 1.0f))); |
+ if (fApplyAlphaAfterInterp) { |
+ fill<true>(dstC, count, c + dither0, c + dither1); |
+ } else { |
+ fill<false>(dstC, count, c + dither0, c + dither1); |
+ } |
+ return; |
+ } |
+ |
+ if (dx > 0) { |
+ if (fApplyAlphaAfterInterp) { |
+ this->shade4_dx_clamp<true, true>(dstC, count, fx, dx, invDx, dither); |
+ } else { |
+ this->shade4_dx_clamp<false, true>(dstC, count, fx, dx, invDx, dither); |
+ } |
+ } else { |
+ if (fApplyAlphaAfterInterp) { |
+ this->shade4_dx_clamp<true, false>(dstC, count, fx, dx, invDx, dither); |
+ } else { |
+ this->shade4_dx_clamp<false, false>(dstC, count, fx, dx, invDx, dither); |
+ } |
+ } |
+} |
+ |