aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/shaders/gradients/SkLinearGradient.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/shaders/gradients/SkLinearGradient.cpp')
-rw-r--r--src/shaders/gradients/SkLinearGradient.cpp804
1 files changed, 804 insertions, 0 deletions
diff --git a/src/shaders/gradients/SkLinearGradient.cpp b/src/shaders/gradients/SkLinearGradient.cpp
new file mode 100644
index 0000000000..17c4fd36a4
--- /dev/null
+++ b/src/shaders/gradients/SkLinearGradient.cpp
@@ -0,0 +1,804 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "Sk4fLinearGradient.h"
+#include "SkColorSpaceXformer.h"
+#include "SkLinearGradient.h"
+#include "SkRefCnt.h"
+
+// define to test the 4f gradient path
+// #define FORCE_4F_CONTEXT
+
+static const float kInv255Float = 1.0f / 255;
+
+static inline int repeat_8bits(int x) {
+ return x & 0xFF;
+}
+
+static inline int mirror_8bits(int x) {
+ if (x & 256) {
+ x = ~x;
+ }
+ return x & 255;
+}
+
+static SkMatrix pts_to_unit_matrix(const SkPoint pts[2]) {
+ SkVector vec = pts[1] - pts[0];
+ SkScalar mag = vec.length();
+ SkScalar inv = mag ? SkScalarInvert(mag) : 0;
+
+ vec.scale(inv);
+ SkMatrix matrix;
+ matrix.setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
+ matrix.postTranslate(-pts[0].fX, -pts[0].fY);
+ matrix.postScale(inv, inv);
+ return matrix;
+}
+
+static bool use_4f_context(const SkShaderBase::ContextRec& rec, uint32_t flags) {
+#ifdef FORCE_4F_CONTEXT
+ return true;
+#else
+ return rec.fPreferredDstType == SkShaderBase::ContextRec::kPM4f_DstType
+ || SkToBool(flags & SkLinearGradient::kForce4fContext_PrivateFlag);
+#endif
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkLinearGradient::SkLinearGradient(const SkPoint pts[2], const Descriptor& desc)
+ : SkGradientShaderBase(desc, pts_to_unit_matrix(pts))
+ , fStart(pts[0])
+ , fEnd(pts[1]) {
+}
+
+sk_sp<SkFlattenable> SkLinearGradient::CreateProc(SkReadBuffer& buffer) {
+ DescriptorScope desc;
+ if (!desc.unflatten(buffer)) {
+ return nullptr;
+ }
+ SkPoint pts[2];
+ pts[0] = buffer.readPoint();
+ pts[1] = buffer.readPoint();
+ return SkGradientShader::MakeLinear(pts, desc.fColors, std::move(desc.fColorSpace), desc.fPos,
+ desc.fCount, desc.fTileMode, desc.fGradFlags,
+ desc.fLocalMatrix);
+}
+
+void SkLinearGradient::flatten(SkWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writePoint(fStart);
+ buffer.writePoint(fEnd);
+}
+
+SkShaderBase::Context* SkLinearGradient::onMakeContext(
+ const ContextRec& rec, SkArenaAlloc* alloc) const
+{
+ return use_4f_context(rec, fGradFlags)
+ ? CheckedMakeContext<LinearGradient4fContext>(alloc, *this, rec)
+ : CheckedMakeContext< LinearGradientContext>(alloc, *this, rec);
+}
+
+bool SkLinearGradient::adjustMatrixAndAppendStages(SkArenaAlloc* alloc,
+ SkMatrix* matrix,
+ SkRasterPipeline* p) const {
+ *matrix = SkMatrix::Concat(fPtsToUnit, *matrix);
+ // If the gradient is less than a quarter of a pixel, this falls into the
+ // subpixel gradient code handled on a different path.
+ SkVector dx = matrix->mapVector(1, 0);
+ if (dx.fX >= 4) {
+ return false;
+ }
+ return true;
+}
+
+sk_sp<SkShader> SkLinearGradient::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
+ SkPoint pts[2] = { fStart, fEnd };
+ SkSTArray<8, SkColor> xformedColors(fColorCount);
+ xformer->apply(xformedColors.begin(), fOrigColors, fColorCount);
+ return SkGradientShader::MakeLinear(pts, xformedColors.begin(), fOrigPos, fColorCount,
+ fTileMode, fGradFlags, &this->getLocalMatrix());
+}
+
+// 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& ctx)
+ : INHERITED(shader, ctx)
+{
+ // setup for Sk4f
+ const int count = shader.fColorCount;
+ SkASSERT(count > 1);
+
+ 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);
+ float diff = rec[i].fPos - rec[i - 1].fPos;
+ if (diff > 0) {
+ rec[i].fPosScale = 1.0f / diff;
+ } else {
+ rec[i].fPosScale = 0;
+ }
+ }
+ } else {
+ // no pos specified, so we compute evenly spaced values
+ const float scale = float(count - 1);
+ const float invScale = 1.0f / scale;
+ for (int i = 0; i < count; ++i) {
+ rec[i].fPos = i * invScale;
+ rec[i].fPosScale = scale;
+ }
+ }
+ rec[count - 1].fPos = 1; // overwrite the last value just to be sure we end at 1.0
+
+ 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 = SkNx_cast<float>(Sk4b::Load(&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 = SkNx_cast<float>(Sk4b::Load(&pmc));
+ if (i > 0) {
+ SkASSERT(rec[i - 1].fPos <= rec[i].fPos);
+ }
+ }
+ }
+}
+
+#define NO_CHECK_ITER \
+ do { \
+ unsigned fi = SkGradFixedToFixed(fx) >> SkGradientShaderBase::kCache32Shift; \
+ SkASSERT(fi <= 0xFF); \
+ fx += dx; \
+ *dstC++ = cache[toggle + fi]; \
+ toggle = next_dither_toggle(toggle); \
+ } while (0)
+
+namespace {
+
+typedef void (*LinearShadeProc)(TileProc proc, SkGradFixed dx, SkGradFixed fx,
+ SkPMColor* dstC, const SkPMColor* cache,
+ int toggle, int count);
+
+// Linear interpolation (lerp) is unnecessary if there are no sharp
+// discontinuities in the gradient - which must be true if there are
+// only 2 colors - but it's cheap.
+void shadeSpan_linear_vertical_lerp(TileProc proc, SkGradFixed dx, SkGradFixed fx,
+ SkPMColor* SK_RESTRICT dstC,
+ const SkPMColor* SK_RESTRICT cache,
+ int toggle, int count) {
+ // We're a vertical gradient, so no change in a span.
+ // If colors change sharply across the gradient, dithering is
+ // insufficient (it subsamples the color space) and we need to lerp.
+ unsigned fullIndex = proc(SkGradFixedToFixed(fx));
+ unsigned fi = fullIndex >> SkGradientShaderBase::kCache32Shift;
+ unsigned remainder = fullIndex & ((1 << SkGradientShaderBase::kCache32Shift) - 1);
+
+ int index0 = fi + toggle;
+ int index1 = index0;
+ if (fi < SkGradientShaderBase::kCache32Count - 1) {
+ index1 += 1;
+ }
+ SkPMColor lerp = SkFastFourByteInterp(cache[index1], cache[index0], remainder);
+ index0 ^= SkGradientShaderBase::kDitherStride32;
+ index1 ^= SkGradientShaderBase::kDitherStride32;
+ SkPMColor dlerp = SkFastFourByteInterp(cache[index1], cache[index0], remainder);
+ sk_memset32_dither(dstC, lerp, dlerp, count);
+}
+
+void shadeSpan_linear_clamp(TileProc proc, SkGradFixed dx, SkGradFixed fx,
+ SkPMColor* SK_RESTRICT dstC,
+ const SkPMColor* SK_RESTRICT cache,
+ int toggle, int count) {
+ SkClampRange range;
+ range.init(fx, dx, count, 0, SkGradientShaderBase::kCache32Count - 1);
+ range.validate(count);
+
+ if ((count = range.fCount0) > 0) {
+ sk_memset32_dither(dstC,
+ cache[toggle + range.fV0],
+ cache[next_dither_toggle(toggle) + range.fV0],
+ count);
+ dstC += count;
+ }
+ if ((count = range.fCount1) > 0) {
+ int unroll = count >> 3;
+ fx = range.fFx1;
+ for (int i = 0; i < unroll; i++) {
+ NO_CHECK_ITER; NO_CHECK_ITER;
+ NO_CHECK_ITER; NO_CHECK_ITER;
+ NO_CHECK_ITER; NO_CHECK_ITER;
+ NO_CHECK_ITER; NO_CHECK_ITER;
+ }
+ if ((count &= 7) > 0) {
+ do {
+ NO_CHECK_ITER;
+ } while (--count != 0);
+ }
+ }
+ if ((count = range.fCount2) > 0) {
+ sk_memset32_dither(dstC,
+ cache[toggle + range.fV1],
+ cache[next_dither_toggle(toggle) + range.fV1],
+ count);
+ }
+}
+
+void shadeSpan_linear_mirror(TileProc proc, SkGradFixed dx, SkGradFixed fx,
+ SkPMColor* SK_RESTRICT dstC,
+ const SkPMColor* SK_RESTRICT cache,
+ int toggle, int count) {
+ do {
+ unsigned fi = mirror_8bits(SkGradFixedToFixed(fx) >> 8);
+ SkASSERT(fi <= 0xFF);
+ fx += dx;
+ *dstC++ = cache[toggle + fi];
+ toggle = next_dither_toggle(toggle);
+ } while (--count != 0);
+}
+
+void shadeSpan_linear_repeat(TileProc proc, SkGradFixed dx, SkGradFixed fx,
+ SkPMColor* SK_RESTRICT dstC,
+ const SkPMColor* SK_RESTRICT cache,
+ int toggle, int count) {
+ do {
+ unsigned fi = repeat_8bits(SkGradFixedToFixed(fx) >> 8);
+ SkASSERT(fi <= 0xFF);
+ fx += dx;
+ *dstC++ = cache[toggle + fi];
+ toggle = next_dither_toggle(toggle);
+ } while (--count != 0);
+}
+
+}
+
+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);
+
+ if (SkShader::kClamp_TileMode == linearGradient.fTileMode &&
+ kLinear_MatrixClass == fDstToIndexClass)
+ {
+ this->shade4_clamp(x, y, dstC, count);
+ return;
+ }
+
+ SkPoint srcPt;
+ SkMatrix::MapXYProc dstProc = fDstToIndexProc;
+ TileProc proc = linearGradient.fTileProc;
+ const SkPMColor* SK_RESTRICT cache = fCache->getCache32();
+ int toggle = init_dither_toggle(x, y);
+
+ if (fDstToIndexClass != kPerspective_MatrixClass) {
+ dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+ SkGradFixed dx, fx = SkScalarPinToGradFixed(srcPt.fX);
+
+ if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
+ const auto step = fDstToIndex.fixedStepInX(SkIntToScalar(y));
+ // todo: do we need a real/high-precision value for dx here?
+ dx = SkScalarPinToGradFixed(step.fX);
+ } else {
+ SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
+ dx = SkScalarPinToGradFixed(fDstToIndex.getScaleX());
+ }
+
+ LinearShadeProc shadeProc = shadeSpan_linear_repeat;
+ if (0 == dx) {
+ shadeProc = shadeSpan_linear_vertical_lerp;
+ } else if (SkShader::kClamp_TileMode == linearGradient.fTileMode) {
+ shadeProc = shadeSpan_linear_clamp;
+ } else if (SkShader::kMirror_TileMode == linearGradient.fTileMode) {
+ shadeProc = shadeSpan_linear_mirror;
+ } else {
+ SkASSERT(SkShader::kRepeat_TileMode == linearGradient.fTileMode);
+ }
+ (*shadeProc)(proc, dx, fx, dstC, cache, toggle, count);
+ } else {
+ SkScalar dstX = SkIntToScalar(x);
+ SkScalar dstY = SkIntToScalar(y);
+ do {
+ dstProc(fDstToIndex, dstX, dstY, &srcPt);
+ unsigned fi = proc(SkScalarToFixed(srcPt.fX));
+ SkASSERT(fi <= 0xFFFF);
+ *dstC++ = cache[toggle + (fi >> kCache32Shift)];
+ toggle = next_dither_toggle(toggle);
+ dstX += SK_Scalar1;
+ } while (--count != 0);
+ }
+}
+
+SkShader::GradientType SkLinearGradient::asAGradient(GradientInfo* info) const {
+ if (info) {
+ commonAsAGradient(info);
+ info->fPoint[0] = fStart;
+ info->fPoint[1] = fEnd;
+ }
+ return kLinear_GradientType;
+}
+
+#if SK_SUPPORT_GPU
+
+#include "GrColorSpaceXform.h"
+#include "GrShaderCaps.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "SkGr.h"
+
+/////////////////////////////////////////////////////////////////////
+
+class GrLinearGradient : public GrGradientEffect {
+public:
+ class GLSLLinearProcessor;
+
+ static sk_sp<GrFragmentProcessor> Make(const CreateArgs& args) {
+ return sk_sp<GrFragmentProcessor>(new GrLinearGradient(args));
+ }
+
+ ~GrLinearGradient() override {}
+
+ const char* name() const override { return "Linear Gradient"; }
+
+private:
+ GrLinearGradient(const CreateArgs& args) : INHERITED(args, args.fShader->colorsAreOpaque()) {
+ this->initClassID<GrLinearGradient>();
+ }
+
+ GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+
+ virtual void onGetGLSLProcessorKey(const GrShaderCaps& caps,
+ GrProcessorKeyBuilder* b) const override;
+
+ GR_DECLARE_FRAGMENT_PROCESSOR_TEST;
+
+ typedef GrGradientEffect INHERITED;
+};
+
+/////////////////////////////////////////////////////////////////////
+
+class GrLinearGradient::GLSLLinearProcessor : public GrGradientEffect::GLSLProcessor {
+public:
+ GLSLLinearProcessor(const GrProcessor&) {}
+
+ ~GLSLLinearProcessor() override {}
+
+ virtual void emitCode(EmitArgs&) override;
+
+ static void GenKey(const GrProcessor& processor, const GrShaderCaps&, GrProcessorKeyBuilder* b) {
+ b->add32(GenBaseGradientKey(processor));
+ }
+
+private:
+ typedef GrGradientEffect::GLSLProcessor INHERITED;
+};
+
+/////////////////////////////////////////////////////////////////////
+
+GrGLSLFragmentProcessor* GrLinearGradient::onCreateGLSLInstance() const {
+ return new GrLinearGradient::GLSLLinearProcessor(*this);
+}
+
+void GrLinearGradient::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+ GrProcessorKeyBuilder* b) const {
+ GrLinearGradient::GLSLLinearProcessor::GenKey(*this, caps, b);
+}
+
+/////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrLinearGradient);
+
+#if GR_TEST_UTILS
+sk_sp<GrFragmentProcessor> GrLinearGradient::TestCreate(GrProcessorTestData* d) {
+ SkPoint points[] = {{d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()},
+ {d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()}};
+
+ RandomGradientParams params(d->fRandom);
+ auto shader = params.fUseColors4f ?
+ SkGradientShader::MakeLinear(points, params.fColors4f, params.fColorSpace, params.fStops,
+ params.fColorCount, params.fTileMode) :
+ SkGradientShader::MakeLinear(points, params.fColors, params.fStops,
+ params.fColorCount, params.fTileMode);
+ GrTest::TestAsFPArgs asFPArgs(d);
+ sk_sp<GrFragmentProcessor> fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args());
+ GrAlwaysAssert(fp);
+ return fp;
+}
+#endif
+
+/////////////////////////////////////////////////////////////////////
+
+void GrLinearGradient::GLSLLinearProcessor::emitCode(EmitArgs& args) {
+ const GrLinearGradient& ge = args.fFp.cast<GrLinearGradient>();
+ this->emitUniforms(args.fUniformHandler, ge);
+ SkString t = args.fFragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
+ t.append(".x");
+ this->emitColor(args.fFragBuilder,
+ args.fUniformHandler,
+ args.fShaderCaps,
+ ge,
+ t.c_str(),
+ args.fOutputColor,
+ args.fInputColor,
+ args.fTexSamplers);
+}
+
+/////////////////////////////////////////////////////////////////////
+
+sk_sp<GrFragmentProcessor> SkLinearGradient::asFragmentProcessor(const AsFPArgs& args) const {
+ SkASSERT(args.fContext);
+
+ SkMatrix matrix;
+ if (!this->getLocalMatrix().invert(&matrix)) {
+ return nullptr;
+ }
+ if (args.fLocalMatrix) {
+ SkMatrix inv;
+ if (!args.fLocalMatrix->invert(&inv)) {
+ return nullptr;
+ }
+ matrix.postConcat(inv);
+ }
+ matrix.postConcat(fPtsToUnit);
+
+ sk_sp<GrColorSpaceXform> colorSpaceXform = GrColorSpaceXform::Make(fColorSpace.get(),
+ args.fDstColorSpace);
+ sk_sp<GrFragmentProcessor> inner(GrLinearGradient::Make(
+ GrGradientEffect::CreateArgs(args.fContext, this, &matrix, fTileMode,
+ std::move(colorSpaceXform), SkToBool(args.fDstColorSpace))));
+ return GrFragmentProcessor::MulOutputByInputAlpha(std::move(inner));
+}
+
+
+#endif
+
+#ifndef SK_IGNORE_TO_STRING
+void SkLinearGradient::toString(SkString* str) const {
+ str->append("SkLinearGradient (");
+
+ str->appendf("start: (%f, %f)", fStart.fX, fStart.fY);
+ str->appendf(" end: (%f, %f) ", fEnd.fX, fEnd.fY);
+
+ this->INHERITED::toString(str);
+
+ 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 || rec->fPosScale == 0) {
+ 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].fPosScale == 0) {
+ 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;
+}
+
+// As an optimization, we can apply the dither bias before interpolation -- but only when
+// operating in premul space (apply_alpha == false). When apply_alpha == true, we must
+// defer the bias application until after premul.
+//
+// The following two helpers encapsulate this logic: pre_bias is called before interpolation,
+// and effects the bias when apply_alpha == false, while post_bias is called after premul and
+// effects the bias for the apply_alpha == true case.
+
+template <bool apply_alpha>
+Sk4f pre_bias(const Sk4f& x, const Sk4f& bias) {
+ return apply_alpha ? x : x + bias;
+}
+
+template <bool apply_alpha>
+Sk4f post_bias(const Sk4f& x, const Sk4f& bias) {
+ return apply_alpha ? x + bias : x;
+}
+
+template <bool apply_alpha> SkPMColor trunc_from_255(const Sk4f& x, const Sk4f& bias) {
+ SkPMColor c;
+ Sk4f c4f255 = x;
+ if (apply_alpha) {
+ const float scale = x[SkPM4f::A] * (1 / 255.f);
+ c4f255 *= Sk4f(scale, scale, scale, 1);
+ }
+ SkNx_cast<uint8_t>(post_bias<apply_alpha>(c4f255, bias)).store(&c);
+
+ return c;
+}
+
+template <bool apply_alpha> void fill(SkPMColor dst[], int count,
+ const Sk4f& c4, const Sk4f& bias0, const Sk4f& bias1) {
+ const SkPMColor c0 = trunc_from_255<apply_alpha>(pre_bias<apply_alpha>(c4, bias0), bias0);
+ const SkPMColor c1 = trunc_from_255<apply_alpha>(pre_bias<apply_alpha>(c4, bias1), bias1);
+ sk_memset32_dither(dst, c0, c1, 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, 0), 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) {
+ SkASSERT(fx >= rec[0].fPos);
+ SkASSERT(fx <= rec[1].fPos);
+
+ 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 = pre_bias<apply_alpha>(c , dither0);
+ Sk4f cd1 = pre_bias<apply_alpha>(c + dc, dither1);
+ Sk4f cd2 = cd0 + dc2;
+ Sk4f cd3 = cd1 + dc2;
+ while (n >= 4) {
+ if (!apply_alpha) {
+ Sk4f_ToBytes((uint8_t*)dstC, cd0, cd1, cd2, cd3);
+ dstC += 4;
+ } else {
+ *dstC++ = trunc_from_255<apply_alpha>(cd0, dither0);
+ *dstC++ = trunc_from_255<apply_alpha>(cd1, dither1);
+ *dstC++ = trunc_from_255<apply_alpha>(cd2, dither0);
+ *dstC++ = trunc_from_255<apply_alpha>(cd3, dither1);
+ }
+ cd0 = cd0 + dc4;
+ cd1 = cd1 + dc4;
+ cd2 = cd2 + dc4;
+ cd3 = cd3 + dc4;
+ n -= 4;
+ }
+ if (n & 2) {
+ *dstC++ = trunc_from_255<apply_alpha>(cd0, dither0);
+ *dstC++ = trunc_from_255<apply_alpha>(cd1, dither1);
+ cd0 = cd0 + dc2;
+ }
+ if (n & 1) {
+ *dstC++ = trunc_from_255<apply_alpha>(cd0, dither0);
+ }
+}
+
+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) {
+ // count is guaranteed to be positive, but the first arg may overflow int32 after
+ // increment => casting to uint32 ensures correct clamping.
+ int n = SkTMin<uint32_t>(static_cast<uint32_t>(SkFloatToIntFloor(-fx * invDx)) + 1,
+ count);
+ SkASSERT(n > 0);
+ 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) {
+ // count is guaranteed to be positive, but the first arg may overflow int32 after
+ // increment => casting to uint32 ensures correct clamping.
+ int n = SkTMin<uint32_t>(static_cast<uint32_t>(SkFloatToIntFloor((1 - fx) * invDx)) + 1,
+ count);
+ SkASSERT(n > 0);
+ 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;
+ // fx should now outside of the p0..p1 interval. However, due to float precision loss,
+ // its possible that fx is slightly too small/large, so we clamp it.
+ if (dx_is_pos) {
+ fx = SkTMax(fx, p1);
+ } else {
+ fx = SkTMin(fx, p0);
+ }
+
+ ramp<apply_alpha>(dstC, n, c, dc, dither0, dither1);
+ dstC += n;
+ SkASSERT(dstC <= endDstC);
+
+ if (n & 1) {
+ SkTSwap(dither0, dither1);
+ }
+
+ count -= n;
+ SkASSERT(count >= 0);
+ }
+}
+
+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 };
+
+ if (SkScalarNearlyZero(dx * count)) { // gradient is vertical
+ const float pinFx = SkTPin(fx, 0.0f, 1.0f);
+ Sk4f c = lerp_color(pinFx, find_forward(fRecs.begin(), pinFx));
+ if (fApplyAlphaAfterInterp) {
+ fill<true>(dstC, count, c, dither0, dither1);
+ } else {
+ fill<false>(dstC, count, c, dither0, dither1);
+ }
+ return;
+ }
+
+ SkASSERT(0.f != dx);
+ const float invDx = 1 / dx;
+ 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);
+ }
+ }
+}