summaryrefslogtreecommitdiff
path: root/effects
diff options
context:
space:
mode:
Diffstat (limited to 'effects')
-rw-r--r--effects/Sk1DPathEffect.cpp200
-rw-r--r--effects/Sk2DPathEffect.cpp132
-rw-r--r--effects/SkArithmeticMode.cpp455
-rw-r--r--effects/SkAvoidXfermode.cpp179
-rw-r--r--effects/SkBicubicImageFilter.cpp377
-rw-r--r--effects/SkBitmapSource.cpp29
-rw-r--r--effects/SkBlurDrawLooper.cpp151
-rw-r--r--effects/SkBlurImageFilter.cpp236
-rw-r--r--effects/SkBlurMask.cpp985
-rw-r--r--effects/SkBlurMask.h55
-rw-r--r--effects/SkBlurMaskFilter.cpp499
-rwxr-xr-xeffects/SkColorFilterImageFilter.cpp137
-rw-r--r--effects/SkColorFilters.cpp547
-rw-r--r--effects/SkColorMatrix.cpp161
-rw-r--r--effects/SkColorMatrixFilter.cpp495
-rw-r--r--effects/SkComposeImageFilter.cpp56
-rw-r--r--effects/SkCornerPathEffect.cpp137
-rw-r--r--effects/SkDashPathEffect.cpp559
-rw-r--r--effects/SkDiscretePathEffect.cpp82
-rw-r--r--effects/SkDisplacementMapEffect.cpp541
-rw-r--r--effects/SkDropShadowImageFilter.cpp62
-rw-r--r--effects/SkEmbossMask.cpp163
-rw-r--r--effects/SkEmbossMask.h20
-rw-r--r--effects/SkEmbossMaskFilter.cpp155
-rw-r--r--effects/SkEmbossMask_Table.h1038
-rw-r--r--effects/SkGpuBlurUtils.cpp265
-rw-r--r--effects/SkGpuBlurUtils.h46
-rw-r--r--effects/SkKernel33MaskFilter.cpp142
-rw-r--r--effects/SkLayerDrawLooper.cpp350
-rw-r--r--effects/SkLayerRasterizer.cpp171
-rw-r--r--effects/SkLerpXfermode.cpp110
-rw-r--r--effects/SkLightingImageFilter.cpp1553
-rw-r--r--effects/SkMagnifierImageFilter.cpp354
-rw-r--r--effects/SkMatrixConvolutionImageFilter.cpp587
-rwxr-xr-xeffects/SkMergeImageFilter.cpp165
-rw-r--r--effects/SkMorphologyImageFilter.cpp533
-rw-r--r--effects/SkOffsetImageFilter.cpp53
-rw-r--r--effects/SkPaintFlagsDrawFilter.cpp20
-rw-r--r--effects/SkPerlinNoiseShader.cpp1378
-rw-r--r--effects/SkPixelXorXfermode.cpp38
-rw-r--r--effects/SkPorterDuff.cpp87
-rw-r--r--effects/SkRectShaderImageFilter.cpp67
-rw-r--r--effects/SkStippleMaskFilter.cpp52
-rw-r--r--effects/SkTableColorFilter.cpp431
-rw-r--r--effects/SkTableMaskFilter.cpp143
-rwxr-xr-xeffects/SkTestImageFilters.cpp81
-rw-r--r--effects/SkTransparentShader.cpp122
-rw-r--r--effects/SkXfermodeImageFilter.cpp146
-rw-r--r--effects/gradients/SkBitmapCache.cpp153
-rw-r--r--effects/gradients/SkBitmapCache.h49
-rw-r--r--effects/gradients/SkClampRange.cpp165
-rw-r--r--effects/gradients/SkClampRange.h38
-rw-r--r--effects/gradients/SkGradientShader.cpp986
-rw-r--r--effects/gradients/SkGradientShaderPriv.h349
-rw-r--r--effects/gradients/SkLinearGradient.cpp568
-rw-r--r--effects/gradients/SkLinearGradient.h38
-rw-r--r--effects/gradients/SkRadialGradient.cpp608
-rw-r--r--effects/gradients/SkRadialGradient.h40
-rw-r--r--effects/gradients/SkRadialGradient_Table.h139
-rw-r--r--effects/gradients/SkSweepGradient.cpp517
-rw-r--r--effects/gradients/SkSweepGradient.h40
-rw-r--r--effects/gradients/SkTwoPointConicalGradient.cpp765
-rw-r--r--effects/gradients/SkTwoPointConicalGradient.h84
-rw-r--r--effects/gradients/SkTwoPointRadialGradient.cpp717
-rw-r--r--effects/gradients/SkTwoPointRadialGradient.h55
65 files changed, 19656 insertions, 0 deletions
diff --git a/effects/Sk1DPathEffect.cpp b/effects/Sk1DPathEffect.cpp
new file mode 100644
index 00000000..10a68b9f
--- /dev/null
+++ b/effects/Sk1DPathEffect.cpp
@@ -0,0 +1,200 @@
+
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#include "Sk1DPathEffect.h"
+#include "SkFlattenableBuffers.h"
+#include "SkPathMeasure.h"
+
+bool Sk1DPathEffect::filterPath(SkPath* dst, const SkPath& src,
+ SkStrokeRec*, const SkRect*) const {
+ SkPathMeasure meas(src, false);
+ do {
+ SkScalar length = meas.getLength();
+ SkScalar distance = this->begin(length);
+ while (distance < length) {
+ SkScalar delta = this->next(dst, distance, meas);
+ if (delta <= 0) {
+ break;
+ }
+ distance += delta;
+ }
+ } while (meas.nextContour());
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkPath1DPathEffect::SkPath1DPathEffect(const SkPath& path, SkScalar advance,
+ SkScalar phase, Style style) : fPath(path)
+{
+ if (advance <= 0 || path.isEmpty()) {
+ SkDEBUGF(("SkPath1DPathEffect can't use advance <= 0\n"));
+ fAdvance = 0; // signals we can't draw anything
+ fInitialOffset = 0;
+ fStyle = kStyleCount;
+ } else {
+ // cleanup their phase parameter, inverting it so that it becomes an
+ // offset along the path (to match the interpretation in PostScript)
+ if (phase < 0) {
+ phase = -phase;
+ if (phase > advance) {
+ phase = SkScalarMod(phase, advance);
+ }
+ } else {
+ if (phase > advance) {
+ phase = SkScalarMod(phase, advance);
+ }
+ phase = advance - phase;
+ }
+ // now catch the edge case where phase == advance (within epsilon)
+ if (phase >= advance) {
+ phase = 0;
+ }
+ SkASSERT(phase >= 0);
+
+ fAdvance = advance;
+ fInitialOffset = phase;
+
+ if ((unsigned)style >= kStyleCount) {
+ SkDEBUGF(("SkPath1DPathEffect style enum out of range %d\n", style));
+ }
+ fStyle = style;
+ }
+}
+
+bool SkPath1DPathEffect::filterPath(SkPath* dst, const SkPath& src,
+ SkStrokeRec* rec, const SkRect* cullRect) const {
+ if (fAdvance > 0) {
+ rec->setFillStyle();
+ return this->INHERITED::filterPath(dst, src, rec, cullRect);
+ }
+ return false;
+}
+
+static bool morphpoints(SkPoint dst[], const SkPoint src[], int count,
+ SkPathMeasure& meas, SkScalar dist) {
+ for (int i = 0; i < count; i++) {
+ SkPoint pos;
+ SkVector tangent;
+
+ SkScalar sx = src[i].fX;
+ SkScalar sy = src[i].fY;
+
+ if (!meas.getPosTan(dist + sx, &pos, &tangent)) {
+ return false;
+ }
+
+ SkMatrix matrix;
+ SkPoint pt;
+
+ pt.set(sx, sy);
+ matrix.setSinCos(tangent.fY, tangent.fX, 0, 0);
+ matrix.preTranslate(-sx, 0);
+ matrix.postTranslate(pos.fX, pos.fY);
+ matrix.mapPoints(&dst[i], &pt, 1);
+ }
+ return true;
+}
+
+/* TODO
+
+Need differentially more subdivisions when the follow-path is curvy. Not sure how to
+determine that, but we need it. I guess a cheap answer is let the caller tell us,
+but that seems like a cop-out. Another answer is to get Rob Johnson to figure it out.
+*/
+static void morphpath(SkPath* dst, const SkPath& src, SkPathMeasure& meas,
+ SkScalar dist) {
+ SkPath::Iter iter(src, false);
+ SkPoint srcP[4], dstP[3];
+ SkPath::Verb verb;
+
+ while ((verb = iter.next(srcP)) != SkPath::kDone_Verb) {
+ switch (verb) {
+ case SkPath::kMove_Verb:
+ if (morphpoints(dstP, srcP, 1, meas, dist)) {
+ dst->moveTo(dstP[0]);
+ }
+ break;
+ case SkPath::kLine_Verb:
+ srcP[2] = srcP[1];
+ srcP[1].set(SkScalarAve(srcP[0].fX, srcP[2].fX),
+ SkScalarAve(srcP[0].fY, srcP[2].fY));
+ // fall through to quad
+ case SkPath::kQuad_Verb:
+ if (morphpoints(dstP, &srcP[1], 2, meas, dist)) {
+ dst->quadTo(dstP[0], dstP[1]);
+ }
+ break;
+ case SkPath::kCubic_Verb:
+ if (morphpoints(dstP, &srcP[1], 3, meas, dist)) {
+ dst->cubicTo(dstP[0], dstP[1], dstP[2]);
+ }
+ break;
+ case SkPath::kClose_Verb:
+ dst->close();
+ break;
+ default:
+ SkDEBUGFAIL("unknown verb");
+ break;
+ }
+ }
+}
+
+SkPath1DPathEffect::SkPath1DPathEffect(SkFlattenableReadBuffer& buffer) {
+ fAdvance = buffer.readScalar();
+ if (fAdvance > 0) {
+ buffer.readPath(&fPath);
+ fInitialOffset = buffer.readScalar();
+ fStyle = (Style) buffer.readUInt();
+ } else {
+ SkDEBUGF(("SkPath1DPathEffect can't use advance <= 0\n"));
+ // Make Coverity happy.
+ fInitialOffset = 0;
+ fStyle = kStyleCount;
+ }
+}
+
+SkScalar SkPath1DPathEffect::begin(SkScalar contourLength) const {
+ return fInitialOffset;
+}
+
+void SkPath1DPathEffect::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeScalar(fAdvance);
+ if (fAdvance > 0) {
+ buffer.writePath(fPath);
+ buffer.writeScalar(fInitialOffset);
+ buffer.writeUInt(fStyle);
+ }
+}
+
+SkScalar SkPath1DPathEffect::next(SkPath* dst, SkScalar distance,
+ SkPathMeasure& meas) const {
+ switch (fStyle) {
+ case kTranslate_Style: {
+ SkPoint pos;
+ if (meas.getPosTan(distance, &pos, NULL)) {
+ dst->addPath(fPath, pos.fX, pos.fY);
+ }
+ } break;
+ case kRotate_Style: {
+ SkMatrix matrix;
+ if (meas.getMatrix(distance, &matrix)) {
+ dst->addPath(fPath, matrix);
+ }
+ } break;
+ case kMorph_Style:
+ morphpath(dst, fPath, meas, distance);
+ break;
+ default:
+ SkDEBUGFAIL("unknown Style enum");
+ break;
+ }
+ return fAdvance;
+}
diff --git a/effects/Sk2DPathEffect.cpp b/effects/Sk2DPathEffect.cpp
new file mode 100644
index 00000000..dc15f076
--- /dev/null
+++ b/effects/Sk2DPathEffect.cpp
@@ -0,0 +1,132 @@
+
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#include "Sk2DPathEffect.h"
+#include "SkFlattenableBuffers.h"
+#include "SkPath.h"
+#include "SkRegion.h"
+
+Sk2DPathEffect::Sk2DPathEffect(const SkMatrix& mat) : fMatrix(mat) {
+ fMatrixIsInvertible = mat.invert(&fInverse);
+}
+
+bool Sk2DPathEffect::filterPath(SkPath* dst, const SkPath& src,
+ SkStrokeRec*, const SkRect*) const {
+ if (!fMatrixIsInvertible) {
+ return false;
+ }
+
+ SkPath tmp;
+ SkIRect ir;
+
+ src.transform(fInverse, &tmp);
+ tmp.getBounds().round(&ir);
+ if (!ir.isEmpty()) {
+ this->begin(ir, dst);
+
+ SkRegion rgn;
+ rgn.setPath(tmp, SkRegion(ir));
+ SkRegion::Iterator iter(rgn);
+ for (; !iter.done(); iter.next()) {
+ const SkIRect& rect = iter.rect();
+ for (int y = rect.fTop; y < rect.fBottom; ++y) {
+ this->nextSpan(rect.fLeft, y, rect.width(), dst);
+ }
+ }
+
+ this->end(dst);
+ }
+ return true;
+}
+
+void Sk2DPathEffect::nextSpan(int x, int y, int count, SkPath* path) const {
+ if (!fMatrixIsInvertible) {
+ return;
+ }
+
+ const SkMatrix& mat = this->getMatrix();
+ SkPoint src, dst;
+
+ src.set(SkIntToScalar(x) + SK_ScalarHalf, SkIntToScalar(y) + SK_ScalarHalf);
+ do {
+ mat.mapPoints(&dst, &src, 1);
+ this->next(dst, x++, y, path);
+ src.fX += SK_Scalar1;
+ } while (--count > 0);
+}
+
+void Sk2DPathEffect::begin(const SkIRect& uvBounds, SkPath* dst) const {}
+void Sk2DPathEffect::next(const SkPoint& loc, int u, int v, SkPath* dst) const {}
+void Sk2DPathEffect::end(SkPath* dst) const {}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void Sk2DPathEffect::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeMatrix(fMatrix);
+}
+
+Sk2DPathEffect::Sk2DPathEffect(SkFlattenableReadBuffer& buffer) {
+ buffer.readMatrix(&fMatrix);
+ fMatrixIsInvertible = fMatrix.invert(&fInverse);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkLine2DPathEffect::filterPath(SkPath* dst, const SkPath& src,
+ SkStrokeRec* rec, const SkRect* cullRect) const {
+ if (this->INHERITED::filterPath(dst, src, rec, cullRect)) {
+ rec->setStrokeStyle(fWidth);
+ return true;
+ }
+ return false;
+}
+
+void SkLine2DPathEffect::nextSpan(int u, int v, int ucount, SkPath* dst) const {
+ if (ucount > 1) {
+ SkPoint src[2], dstP[2];
+
+ src[0].set(SkIntToScalar(u) + SK_ScalarHalf, SkIntToScalar(v) + SK_ScalarHalf);
+ src[1].set(SkIntToScalar(u+ucount) + SK_ScalarHalf, SkIntToScalar(v) + SK_ScalarHalf);
+ this->getMatrix().mapPoints(dstP, src, 2);
+
+ dst->moveTo(dstP[0]);
+ dst->lineTo(dstP[1]);
+ }
+}
+
+SkLine2DPathEffect::SkLine2DPathEffect(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ fWidth = buffer.readScalar();
+}
+
+void SkLine2DPathEffect::flatten(SkFlattenableWriteBuffer &buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeScalar(fWidth);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkPath2DPathEffect::SkPath2DPathEffect(const SkMatrix& m, const SkPath& p)
+ : INHERITED(m), fPath(p) {
+}
+
+SkPath2DPathEffect::SkPath2DPathEffect(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {
+ buffer.readPath(&fPath);
+}
+
+void SkPath2DPathEffect::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writePath(fPath);
+}
+
+void SkPath2DPathEffect::next(const SkPoint& loc, int u, int v,
+ SkPath* dst) const {
+ dst->addPath(fPath, loc.fX, loc.fY);
+}
diff --git a/effects/SkArithmeticMode.cpp b/effects/SkArithmeticMode.cpp
new file mode 100644
index 00000000..43c62ecd
--- /dev/null
+++ b/effects/SkArithmeticMode.cpp
@@ -0,0 +1,455 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkArithmeticMode.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkString.h"
+#include "SkUnPreMultiply.h"
+#if SK_SUPPORT_GPU
+#include "GrContext.h"
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
+#include "GrTBackendEffectFactory.h"
+#include "SkImageFilterUtils.h"
+#endif
+
+static const bool gUseUnpremul = false;
+
+class SkArithmeticMode_scalar : public SkXfermode {
+public:
+ SkArithmeticMode_scalar(SkScalar k1, SkScalar k2, SkScalar k3, SkScalar k4) {
+ fK[0] = k1;
+ fK[1] = k2;
+ fK[2] = k3;
+ fK[3] = k4;
+ }
+
+ virtual void xfer32(SkPMColor dst[], const SkPMColor src[], int count,
+ const SkAlpha aa[]) const SK_OVERRIDE;
+
+ SK_DEVELOPER_TO_STRING()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkArithmeticMode_scalar)
+
+#if SK_SUPPORT_GPU
+ virtual bool asNewEffectOrCoeff(GrContext*, GrEffectRef** effect, Coeff*, Coeff*, GrTexture* background) const SK_OVERRIDE;
+#endif
+
+private:
+ SkArithmeticMode_scalar(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ fK[0] = buffer.readScalar();
+ fK[1] = buffer.readScalar();
+ fK[2] = buffer.readScalar();
+ fK[3] = buffer.readScalar();
+ }
+
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
+ INHERITED::flatten(buffer);
+ buffer.writeScalar(fK[0]);
+ buffer.writeScalar(fK[1]);
+ buffer.writeScalar(fK[2]);
+ buffer.writeScalar(fK[3]);
+ }
+ SkScalar fK[4];
+
+ typedef SkXfermode INHERITED;
+};
+
+static int pinToByte(int value) {
+ if (value < 0) {
+ value = 0;
+ } else if (value > 255) {
+ value = 255;
+ }
+ return value;
+}
+
+static int arith(SkScalar k1, SkScalar k2, SkScalar k3, SkScalar k4,
+ int src, int dst) {
+ SkScalar result = SkScalarMul(k1, src * dst) +
+ SkScalarMul(k2, src) +
+ SkScalarMul(k3, dst) +
+ k4;
+ int res = SkScalarRoundToInt(result);
+ return pinToByte(res);
+}
+
+static int blend(int src, int dst, int scale) {
+ return dst + ((src - dst) * scale >> 8);
+}
+
+static bool needsUnpremul(int alpha) {
+ return 0 != alpha && 0xFF != alpha;
+}
+
+void SkArithmeticMode_scalar::xfer32(SkPMColor dst[], const SkPMColor src[],
+ int count, const SkAlpha aaCoverage[]) const {
+ SkScalar k1 = fK[0] / 255;
+ SkScalar k2 = fK[1];
+ SkScalar k3 = fK[2];
+ SkScalar k4 = fK[3] * 255;
+
+ for (int i = 0; i < count; ++i) {
+ if ((NULL == aaCoverage) || aaCoverage[i]) {
+ SkPMColor sc = src[i];
+ SkPMColor dc = dst[i];
+
+ int a, r, g, b;
+
+ if (gUseUnpremul) {
+ int sa = SkGetPackedA32(sc);
+ int da = SkGetPackedA32(dc);
+
+ int srcNeedsUnpremul = needsUnpremul(sa);
+ int dstNeedsUnpremul = needsUnpremul(da);
+
+ if (!srcNeedsUnpremul && !dstNeedsUnpremul) {
+ a = arith(k1, k2, k3, k4, sa, da);
+ r = arith(k1, k2, k3, k4, SkGetPackedR32(sc), SkGetPackedR32(dc));
+ g = arith(k1, k2, k3, k4, SkGetPackedG32(sc), SkGetPackedG32(dc));
+ b = arith(k1, k2, k3, k4, SkGetPackedB32(sc), SkGetPackedB32(dc));
+ } else {
+ int sr = SkGetPackedR32(sc);
+ int sg = SkGetPackedG32(sc);
+ int sb = SkGetPackedB32(sc);
+ if (srcNeedsUnpremul) {
+ SkUnPreMultiply::Scale scale = SkUnPreMultiply::GetScale(sa);
+ sr = SkUnPreMultiply::ApplyScale(scale, sr);
+ sg = SkUnPreMultiply::ApplyScale(scale, sg);
+ sb = SkUnPreMultiply::ApplyScale(scale, sb);
+ }
+
+ int dr = SkGetPackedR32(dc);
+ int dg = SkGetPackedG32(dc);
+ int db = SkGetPackedB32(dc);
+ if (dstNeedsUnpremul) {
+ SkUnPreMultiply::Scale scale = SkUnPreMultiply::GetScale(da);
+ dr = SkUnPreMultiply::ApplyScale(scale, dr);
+ dg = SkUnPreMultiply::ApplyScale(scale, dg);
+ db = SkUnPreMultiply::ApplyScale(scale, db);
+ }
+
+ a = arith(k1, k2, k3, k4, sa, da);
+ r = arith(k1, k2, k3, k4, sr, dr);
+ g = arith(k1, k2, k3, k4, sg, dg);
+ b = arith(k1, k2, k3, k4, sb, db);
+ }
+ } else {
+ a = arith(k1, k2, k3, k4, SkGetPackedA32(sc), SkGetPackedA32(dc));
+ r = arith(k1, k2, k3, k4, SkGetPackedR32(sc), SkGetPackedR32(dc));
+ r = SkMin32(r, a);
+ g = arith(k1, k2, k3, k4, SkGetPackedG32(sc), SkGetPackedG32(dc));
+ g = SkMin32(g, a);
+ b = arith(k1, k2, k3, k4, SkGetPackedB32(sc), SkGetPackedB32(dc));
+ b = SkMin32(b, a);
+ }
+
+ // apply antialias coverage if necessary
+ if (aaCoverage && 0xFF != aaCoverage[i]) {
+ int scale = aaCoverage[i] + (aaCoverage[i] >> 7);
+ a = blend(a, SkGetPackedA32(sc), scale);
+ r = blend(r, SkGetPackedR32(sc), scale);
+ g = blend(g, SkGetPackedG32(sc), scale);
+ b = blend(b, SkGetPackedB32(sc), scale);
+ }
+
+ // turn the result back into premul
+ if (gUseUnpremul && (0xFF != a)) {
+ int scale = a + (a >> 7);
+ r = SkAlphaMul(r, scale);
+ g = SkAlphaMul(g, scale);
+ b = SkAlphaMul(b, scale);
+ }
+ dst[i] = SkPackARGB32(a, r, g, b);
+ }
+ }
+}
+
+#ifdef SK_DEVELOPER
+void SkArithmeticMode_scalar::toString(SkString* str) const {
+ str->append("SkArithmeticMode_scalar: ");
+ for (int i = 0; i < 4; ++i) {
+ str->appendScalar(fK[i]);
+ if (i < 3) {
+ str->append(" ");
+ }
+ }
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+static bool fitsInBits(SkScalar x, int bits) {
+#ifdef SK_SCALAR_IS_FIXED
+ x = SkAbs32(x);
+ x += 1 << 7;
+ x >>= 8;
+ return x < (1 << (bits - 1));
+#else
+ return SkScalarAbs(x) < (1 << (bits - 1));
+#endif
+}
+
+#if 0 // UNUSED
+static int32_t toDot8(SkScalar x) {
+#ifdef SK_SCALAR_IS_FIXED
+ x += 1 << 7;
+ x >>= 8;
+ return x;
+#else
+ return (int32_t)(x * 256);
+#endif
+}
+#endif
+
+SkXfermode* SkArithmeticMode::Create(SkScalar k1, SkScalar k2,
+ SkScalar k3, SkScalar k4) {
+ if (fitsInBits(k1, 8) && fitsInBits(k2, 16) &&
+ fitsInBits(k2, 16) && fitsInBits(k2, 24)) {
+
+#if 0 // UNUSED
+ int32_t i1 = toDot8(k1);
+ int32_t i2 = toDot8(k2);
+ int32_t i3 = toDot8(k3);
+ int32_t i4 = toDot8(k4);
+ if (i1) {
+ return SkNEW_ARGS(SkArithmeticMode_quad, (i1, i2, i3, i4));
+ }
+ if (0 == i2) {
+ return SkNEW_ARGS(SkArithmeticMode_dst, (i3, i4));
+ }
+ if (0 == i3) {
+ return SkNEW_ARGS(SkArithmeticMode_src, (i2, i4));
+ }
+ return SkNEW_ARGS(SkArithmeticMode_linear, (i2, i3, i4));
+#endif
+ }
+ return SkNEW_ARGS(SkArithmeticMode_scalar, (k1, k2, k3, k4));
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+
+class GrGLArithmeticEffect : public GrGLEffect {
+public:
+ GrGLArithmeticEffect(const GrBackendEffectFactory&, const GrDrawEffect&);
+ virtual ~GrGLArithmeticEffect();
+
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ static inline EffectKey GenKey(const GrDrawEffect&, const GrGLCaps&);
+
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+private:
+ static const GrEffect::CoordsType kCoordsType = GrEffect::kLocal_CoordsType;
+ GrGLEffectMatrix fBackgroundEffectMatrix;
+ GrGLUniformManager::UniformHandle fKUni;
+
+ typedef GrGLEffect INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class GrArithmeticEffect : public GrEffect {
+public:
+ static GrEffectRef* Create(float k1, float k2, float k3, float k4, GrTexture* background) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrArithmeticEffect, (k1, k2, k3, k4, background)));
+ return CreateEffectRef(effect);
+ }
+
+ virtual ~GrArithmeticEffect();
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+
+ typedef GrGLArithmeticEffect GLEffect;
+ static const char* Name() { return "Arithmetic"; }
+ GrTexture* backgroundTexture() const { return fBackgroundAccess.getTexture(); }
+
+ virtual void getConstantColorComponents(GrColor* color, uint32_t* validFlags) const SK_OVERRIDE;
+
+ float k1() const { return fK1; }
+ float k2() const { return fK2; }
+ float k3() const { return fK3; }
+ float k4() const { return fK4; }
+
+private:
+ virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE;
+
+ GrArithmeticEffect(float k1, float k2, float k3, float k4, GrTexture* background);
+ float fK1, fK2, fK3, fK4;
+ GrTextureAccess fBackgroundAccess;
+
+ GR_DECLARE_EFFECT_TEST;
+ typedef GrEffect INHERITED;
+
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrArithmeticEffect::GrArithmeticEffect(float k1, float k2, float k3, float k4,
+ GrTexture* background)
+ : fK1(k1), fK2(k2), fK3(k3), fK4(k4) {
+ if (background) {
+ fBackgroundAccess.reset(background);
+ this->addTextureAccess(&fBackgroundAccess);
+ } else {
+ this->setWillReadDstColor();
+ }
+}
+
+GrArithmeticEffect::~GrArithmeticEffect() {
+}
+
+bool GrArithmeticEffect::onIsEqual(const GrEffect& sBase) const {
+ const GrArithmeticEffect& s = CastEffect<GrArithmeticEffect>(sBase);
+ return fK1 == s.fK1 &&
+ fK2 == s.fK2 &&
+ fK3 == s.fK3 &&
+ fK4 == s.fK4 &&
+ backgroundTexture() == s.backgroundTexture();
+}
+
+const GrBackendEffectFactory& GrArithmeticEffect::getFactory() const {
+ return GrTBackendEffectFactory<GrArithmeticEffect>::getInstance();
+}
+
+void GrArithmeticEffect::getConstantColorComponents(GrColor* color, uint32_t* validFlags) const {
+ // TODO: optimize this
+ *validFlags = 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrGLArithmeticEffect::GrGLArithmeticEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect)
+ : INHERITED(factory)
+ , fBackgroundEffectMatrix(kCoordsType) {
+}
+
+GrGLArithmeticEffect::~GrGLArithmeticEffect() {
+}
+
+void GrGLArithmeticEffect::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect& drawEffect,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+
+ GrTexture* backgroundTex = drawEffect.castEffect<GrArithmeticEffect>().backgroundTexture();
+ const char* dstColor;
+ if (backgroundTex) {
+ const char* bgCoords;
+ GrSLType bgCoordsType = fBackgroundEffectMatrix.emitCode(builder, key, &bgCoords, NULL, "BG");
+ builder->fsCodeAppend("\t\tvec4 bgColor = ");
+ builder->appendTextureLookup(GrGLShaderBuilder::kFragment_ShaderType,
+ samplers[0],
+ bgCoords,
+ bgCoordsType);
+ builder->fsCodeAppendf(";\n");
+ dstColor = "bgColor";
+ } else {
+ dstColor = builder->dstColor();
+ }
+
+ GrAssert(NULL != dstColor);
+ fKUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec4f_GrSLType, "k");
+ const char* kUni = builder->getUniformCStr(fKUni);
+
+ // We don't try to optimize for this case at all
+ if (NULL == inputColor) {
+ builder->fsCodeAppendf("\t\tconst vec4 src = %s;\n", GrGLSLOnesVecf(4));
+ } else {
+ builder->fsCodeAppendf("\t\tvec4 src = %s;\n", inputColor);
+ if (gUseUnpremul) {
+ builder->fsCodeAppendf("\t\tsrc.rgb = clamp(src.rgb / src.a, 0.0, 1.0);\n");
+ }
+ }
+
+ builder->fsCodeAppendf("\t\tvec4 dst = %s;\n", dstColor);
+ if (gUseUnpremul) {
+ builder->fsCodeAppendf("\t\tdst.rgb = clamp(dst.rgb / dst.a, 0.0, 1.0);\n");
+ }
+
+ builder->fsCodeAppendf("\t\t%s = %s.x * src * dst + %s.y * src + %s.z * dst + %s.w;\n", outputColor, kUni, kUni, kUni, kUni);
+ builder->fsCodeAppendf("\t\t%s = clamp(%s, 0.0, 1.0);\n", outputColor, outputColor);
+ if (gUseUnpremul) {
+ builder->fsCodeAppendf("\t\t%s.rgb *= %s.a;\n", outputColor, outputColor);
+ } else {
+ builder->fsCodeAppendf("\t\t%s.rgb = min(%s.rgb, %s.a);\n", outputColor, outputColor, outputColor);
+ }
+}
+
+void GrGLArithmeticEffect::setData(const GrGLUniformManager& uman, const GrDrawEffect& drawEffect) {
+ const GrArithmeticEffect& arith = drawEffect.castEffect<GrArithmeticEffect>();
+ uman.set4f(fKUni, arith.k1(), arith.k2(), arith.k3(), arith.k4());
+ GrTexture* bgTex = arith.backgroundTexture();
+ if (bgTex) {
+ fBackgroundEffectMatrix.setData(uman,
+ GrEffect::MakeDivByTextureWHMatrix(bgTex),
+ drawEffect,
+ bgTex);
+ }
+}
+
+GrGLEffect::EffectKey GrGLArithmeticEffect::GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
+ const GrArithmeticEffect& effect = drawEffect.castEffect<GrArithmeticEffect>();
+ GrTexture* bgTex = effect.backgroundTexture();
+ EffectKey bgKey = 0;
+ if (bgTex) {
+ bgKey = GrGLEffectMatrix::GenKey(GrEffect::MakeDivByTextureWHMatrix(bgTex),
+ drawEffect,
+ GrGLArithmeticEffect::kCoordsType,
+ bgTex);
+ }
+ return bgKey;
+}
+
+GrEffectRef* GrArithmeticEffect::TestCreate(SkMWCRandom* rand,
+ GrContext*,
+ const GrDrawTargetCaps&,
+ GrTexture*[]) {
+ float k1 = rand->nextF();
+ float k2 = rand->nextF();
+ float k3 = rand->nextF();
+ float k4 = rand->nextF();
+
+ static AutoEffectUnref gEffect(SkNEW_ARGS(GrArithmeticEffect, (k1, k2, k3, k4, NULL)));
+ return CreateEffectRef(gEffect);
+}
+
+GR_DEFINE_EFFECT_TEST(GrArithmeticEffect);
+
+bool SkArithmeticMode_scalar::asNewEffectOrCoeff(GrContext*,
+ GrEffectRef** effect,
+ Coeff*,
+ Coeff*,
+ GrTexture* background) const {
+ if (effect) {
+ *effect = GrArithmeticEffect::Create(SkScalarToFloat(fK[0]),
+ SkScalarToFloat(fK[1]),
+ SkScalarToFloat(fK[2]),
+ SkScalarToFloat(fK[3]),
+ background);
+ }
+ return true;
+}
+
+#endif
+
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkArithmeticMode)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkArithmeticMode_scalar)
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
diff --git a/effects/SkAvoidXfermode.cpp b/effects/SkAvoidXfermode.cpp
new file mode 100644
index 00000000..d76efb83
--- /dev/null
+++ b/effects/SkAvoidXfermode.cpp
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkAvoidXfermode.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkString.h"
+
+SkAvoidXfermode::SkAvoidXfermode(SkColor opColor, U8CPU tolerance, Mode mode) {
+ if (tolerance > 255) {
+ tolerance = 255;
+ }
+
+ fOpColor = opColor;
+ fDistMul = (256 << 14) / (tolerance + 1);
+ fMode = mode;
+}
+
+SkAvoidXfermode::SkAvoidXfermode(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {
+ fOpColor = buffer.readColor();
+ fDistMul = buffer.readUInt();
+ fMode = (Mode)buffer.readUInt();
+}
+
+void SkAvoidXfermode::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+
+ buffer.writeColor(fOpColor);
+ buffer.writeUInt(fDistMul);
+ buffer.writeUInt(fMode);
+}
+
+// returns 0..31
+static unsigned color_dist16(uint16_t c, unsigned r, unsigned g, unsigned b) {
+ SkASSERT(r <= SK_R16_MASK);
+ SkASSERT(g <= SK_G16_MASK);
+ SkASSERT(b <= SK_B16_MASK);
+
+ unsigned dr = SkAbs32(SkGetPackedR16(c) - r);
+ unsigned dg = SkAbs32(SkGetPackedG16(c) - g) >> (SK_G16_BITS - SK_R16_BITS);
+ unsigned db = SkAbs32(SkGetPackedB16(c) - b);
+
+ return SkMax32(dr, SkMax32(dg, db));
+}
+
+// returns 0..255
+static unsigned color_dist32(SkPMColor c, U8CPU r, U8CPU g, U8CPU b) {
+ SkASSERT(r <= 0xFF);
+ SkASSERT(g <= 0xFF);
+ SkASSERT(b <= 0xFF);
+
+ unsigned dr = SkAbs32(SkGetPackedR32(c) - r);
+ unsigned dg = SkAbs32(SkGetPackedG32(c) - g);
+ unsigned db = SkAbs32(SkGetPackedB32(c) - b);
+
+ return SkMax32(dr, SkMax32(dg, db));
+}
+
+static int scale_dist_14(int dist, uint32_t mul, uint32_t sub) {
+ int tmp = dist * mul - sub;
+ int result = (tmp + (1 << 13)) >> 14;
+
+ return result;
+}
+
+static inline unsigned Accurate255To256(unsigned x) {
+ return x + (x >> 7);
+}
+
+void SkAvoidXfermode::xfer32(SkPMColor dst[], const SkPMColor src[], int count,
+ const SkAlpha aa[]) const {
+ unsigned opR = SkColorGetR(fOpColor);
+ unsigned opG = SkColorGetG(fOpColor);
+ unsigned opB = SkColorGetB(fOpColor);
+ uint32_t mul = fDistMul;
+ uint32_t sub = (fDistMul - (1 << 14)) << 8;
+
+ int MAX, mask;
+
+ if (kTargetColor_Mode == fMode) {
+ mask = -1;
+ MAX = 255;
+ } else {
+ mask = 0;
+ MAX = 0;
+ }
+
+ for (int i = 0; i < count; i++) {
+ int d = color_dist32(dst[i], opR, opG, opB);
+ // now reverse d if we need to
+ d = MAX + (d ^ mask) - mask;
+ SkASSERT((unsigned)d <= 255);
+ d = Accurate255To256(d);
+
+ d = scale_dist_14(d, mul, sub);
+ SkASSERT(d <= 256);
+
+ if (d > 0) {
+ if (NULL != aa) {
+ d = SkAlphaMul(d, Accurate255To256(*aa++));
+ if (0 == d) {
+ continue;
+ }
+ }
+ dst[i] = SkFourByteInterp256(src[i], dst[i], d);
+ }
+ }
+}
+
+static inline U16CPU SkBlend3216(SkPMColor src, U16CPU dst, unsigned scale) {
+ SkASSERT(scale <= 32);
+ scale <<= 3;
+
+ return SkPackRGB16( SkAlphaBlend(SkPacked32ToR16(src), SkGetPackedR16(dst), scale),
+ SkAlphaBlend(SkPacked32ToG16(src), SkGetPackedG16(dst), scale),
+ SkAlphaBlend(SkPacked32ToB16(src), SkGetPackedB16(dst), scale));
+}
+
+void SkAvoidXfermode::xfer16(uint16_t dst[], const SkPMColor src[], int count,
+ const SkAlpha aa[]) const {
+ unsigned opR = SkColorGetR(fOpColor) >> (8 - SK_R16_BITS);
+ unsigned opG = SkColorGetG(fOpColor) >> (8 - SK_G16_BITS);
+ unsigned opB = SkColorGetB(fOpColor) >> (8 - SK_R16_BITS);
+ uint32_t mul = fDistMul;
+ uint32_t sub = (fDistMul - (1 << 14)) << SK_R16_BITS;
+
+ int MAX, mask;
+
+ if (kTargetColor_Mode == fMode) {
+ mask = -1;
+ MAX = 31;
+ } else {
+ mask = 0;
+ MAX = 0;
+ }
+
+ for (int i = 0; i < count; i++) {
+ int d = color_dist16(dst[i], opR, opG, opB);
+ // now reverse d if we need to
+ d = MAX + (d ^ mask) - mask;
+ SkASSERT((unsigned)d <= 31);
+ // convert from 0..31 to 0..32
+ d += d >> 4;
+ d = scale_dist_14(d, mul, sub);
+ SkASSERT(d <= 32);
+
+ if (d > 0) {
+ if (NULL != aa) {
+ d = SkAlphaMul(d, Accurate255To256(*aa++));
+ if (0 == d) {
+ continue;
+ }
+ }
+ dst[i] = SkBlend3216(src[i], dst[i], d);
+ }
+ }
+}
+
+void SkAvoidXfermode::xferA8(SkAlpha dst[], const SkPMColor src[], int count,
+ const SkAlpha aa[]) const {
+ // override in subclass
+}
+
+#ifdef SK_DEVELOPER
+void SkAvoidXfermode::toString(SkString* str) const {
+ str->append("SkAvoidXfermode: opColor: ");
+ str->appendHex(fOpColor);
+ str->appendf("distMul: %d ", fDistMul);
+
+ static const char* gModeStrings[] = { "Avoid", "Target" };
+
+ str->appendf("mode: %s", gModeStrings[fMode]);
+}
+#endif
diff --git a/effects/SkBicubicImageFilter.cpp b/effects/SkBicubicImageFilter.cpp
new file mode 100644
index 00000000..db8dbfd0
--- /dev/null
+++ b/effects/SkBicubicImageFilter.cpp
@@ -0,0 +1,377 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBicubicImageFilter.h"
+#include "SkBitmap.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkMatrix.h"
+#include "SkRect.h"
+#include "SkUnPreMultiply.h"
+
+#if SK_SUPPORT_GPU
+#include "gl/GrGLEffectMatrix.h"
+#include "effects/GrSingleTextureEffect.h"
+#include "GrTBackendEffectFactory.h"
+#include "GrContext.h"
+#include "GrTexture.h"
+#include "SkImageFilterUtils.h"
+#endif
+
+SkBicubicImageFilter::SkBicubicImageFilter(const SkSize& scale, const SkScalar coefficients[16], SkImageFilter* input)
+ : INHERITED(input),
+ fScale(scale) {
+ memcpy(fCoefficients, coefficients, sizeof(fCoefficients));
+}
+
+#define DS(x) SkDoubleToScalar(x)
+
+SkBicubicImageFilter* SkBicubicImageFilter::CreateMitchell(const SkSize& scale,
+ SkImageFilter* input) {
+ static const SkScalar coefficients[16] = {
+ DS( 1.0 / 18.0), DS(-9.0 / 18.0), DS( 15.0 / 18.0), DS( -7.0 / 18.0),
+ DS(16.0 / 18.0), DS( 0.0 / 18.0), DS(-36.0 / 18.0), DS( 21.0 / 18.0),
+ DS( 1.0 / 18.0), DS( 9.0 / 18.0), DS( 27.0 / 18.0), DS(-21.0 / 18.0),
+ DS( 0.0 / 18.0), DS( 0.0 / 18.0), DS( -6.0 / 18.0), DS( 7.0 / 18.0),
+ };
+ return SkNEW_ARGS(SkBicubicImageFilter, (scale, coefficients, input));
+}
+
+SkBicubicImageFilter::SkBicubicImageFilter(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ SkDEBUGCODE(uint32_t readSize =) buffer.readScalarArray(fCoefficients);
+ SkASSERT(readSize == 16);
+ fScale.fWidth = buffer.readScalar();
+ fScale.fHeight = buffer.readScalar();
+}
+
+void SkBicubicImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeScalarArray(fCoefficients, 16);
+ buffer.writeScalar(fScale.fWidth);
+ buffer.writeScalar(fScale.fHeight);
+}
+
+SkBicubicImageFilter::~SkBicubicImageFilter() {
+}
+
+inline SkPMColor cubicBlend(const SkScalar c[16], SkScalar t, SkPMColor c0, SkPMColor c1, SkPMColor c2, SkPMColor c3) {
+ SkScalar t2 = t * t, t3 = t2 * t;
+ SkScalar cc[4];
+ // FIXME: For the fractx case, this should be refactored out of this function.
+ cc[0] = c[0] + SkScalarMul(c[1], t) + SkScalarMul(c[2], t2) + SkScalarMul(c[3], t3);
+ cc[1] = c[4] + SkScalarMul(c[5], t) + SkScalarMul(c[6], t2) + SkScalarMul(c[7], t3);
+ cc[2] = c[8] + SkScalarMul(c[9], t) + SkScalarMul(c[10], t2) + SkScalarMul(c[11], t3);
+ cc[3] = c[12] + SkScalarMul(c[13], t) + SkScalarMul(c[14], t2) + SkScalarMul(c[15], t3);
+ SkScalar a = SkScalarClampMax(SkScalarMul(cc[0], SkGetPackedA32(c0)) + SkScalarMul(cc[1], SkGetPackedA32(c1)) + SkScalarMul(cc[2], SkGetPackedA32(c2)) + SkScalarMul(cc[3], SkGetPackedA32(c3)), 255);
+ SkScalar r = SkScalarMul(cc[0], SkGetPackedR32(c0)) + SkScalarMul(cc[1], SkGetPackedR32(c1)) + SkScalarMul(cc[2], SkGetPackedR32(c2)) + SkScalarMul(cc[3], SkGetPackedR32(c3));
+ SkScalar g = SkScalarMul(cc[0], SkGetPackedG32(c0)) + SkScalarMul(cc[1], SkGetPackedG32(c1)) + SkScalarMul(cc[2], SkGetPackedG32(c2)) + SkScalarMul(cc[3], SkGetPackedG32(c3));
+ SkScalar b = SkScalarMul(cc[0], SkGetPackedB32(c0)) + SkScalarMul(cc[1], SkGetPackedB32(c1)) + SkScalarMul(cc[2], SkGetPackedB32(c2)) + SkScalarMul(cc[3], SkGetPackedB32(c3));
+ return SkPackARGB32(SkScalarRoundToInt(a),
+ SkScalarRoundToInt(SkScalarClampMax(r, a)),
+ SkScalarRoundToInt(SkScalarClampMax(g, a)),
+ SkScalarRoundToInt(SkScalarClampMax(b, a)));
+}
+
+bool SkBicubicImageFilter::onFilterImage(Proxy* proxy,
+ const SkBitmap& source,
+ const SkMatrix& matrix,
+ SkBitmap* result,
+ SkIPoint* loc) {
+ SkBitmap src = source;
+ if (getInput(0) && !getInput(0)->filterImage(proxy, source, matrix, &src, loc)) {
+ return false;
+ }
+
+ if (src.config() != SkBitmap::kARGB_8888_Config) {
+ return false;
+ }
+
+ SkAutoLockPixels alp(src);
+ if (!src.getPixels()) {
+ return false;
+ }
+
+ SkRect dstRect = SkRect::MakeWH(SkScalarMul(SkIntToScalar(src.width()), fScale.fWidth),
+ SkScalarMul(SkIntToScalar(src.height()), fScale.fHeight));
+ SkIRect dstIRect;
+ dstRect.roundOut(&dstIRect);
+ result->setConfig(src.config(), dstIRect.width(), dstIRect.height());
+ result->allocPixels();
+ if (!result->getPixels()) {
+ return false;
+ }
+
+ SkRect srcRect;
+ src.getBounds(&srcRect);
+ SkMatrix inverse;
+ inverse.setRectToRect(dstRect, srcRect, SkMatrix::kFill_ScaleToFit);
+ inverse.postTranslate(SkFloatToScalar(-0.5f), SkFloatToScalar(-0.5f));
+
+ for (int y = dstIRect.fTop; y < dstIRect.fBottom; ++y) {
+ SkPMColor* dptr = result->getAddr32(dstIRect.fLeft, y);
+ for (int x = dstIRect.fLeft; x < dstIRect.fRight; ++x) {
+ SkPoint srcPt, dstPt = SkPoint::Make(SkIntToScalar(x), SkIntToScalar(y));
+ inverse.mapPoints(&srcPt, &dstPt, 1);
+ SkScalar fractx = srcPt.fX - SkScalarFloorToScalar(srcPt.fX);
+ SkScalar fracty = srcPt.fY - SkScalarFloorToScalar(srcPt.fY);
+ int sx = SkScalarFloorToInt(srcPt.fX);
+ int sy = SkScalarFloorToInt(srcPt.fY);
+ int x0 = SkClampMax(sx - 1, src.width() - 1);
+ int x1 = SkClampMax(sx , src.width() - 1);
+ int x2 = SkClampMax(sx + 1, src.width() - 1);
+ int x3 = SkClampMax(sx + 2, src.width() - 1);
+ int y0 = SkClampMax(sy - 1, src.height() - 1);
+ int y1 = SkClampMax(sy , src.height() - 1);
+ int y2 = SkClampMax(sy + 1, src.height() - 1);
+ int y3 = SkClampMax(sy + 2, src.height() - 1);
+ SkPMColor s00 = *src.getAddr32(x0, y0);
+ SkPMColor s10 = *src.getAddr32(x1, y0);
+ SkPMColor s20 = *src.getAddr32(x2, y0);
+ SkPMColor s30 = *src.getAddr32(x3, y0);
+ SkPMColor s0 = cubicBlend(fCoefficients, fractx, s00, s10, s20, s30);
+ SkPMColor s01 = *src.getAddr32(x0, y1);
+ SkPMColor s11 = *src.getAddr32(x1, y1);
+ SkPMColor s21 = *src.getAddr32(x2, y1);
+ SkPMColor s31 = *src.getAddr32(x3, y1);
+ SkPMColor s1 = cubicBlend(fCoefficients, fractx, s01, s11, s21, s31);
+ SkPMColor s02 = *src.getAddr32(x0, y2);
+ SkPMColor s12 = *src.getAddr32(x1, y2);
+ SkPMColor s22 = *src.getAddr32(x2, y2);
+ SkPMColor s32 = *src.getAddr32(x3, y2);
+ SkPMColor s2 = cubicBlend(fCoefficients, fractx, s02, s12, s22, s32);
+ SkPMColor s03 = *src.getAddr32(x0, y3);
+ SkPMColor s13 = *src.getAddr32(x1, y3);
+ SkPMColor s23 = *src.getAddr32(x2, y3);
+ SkPMColor s33 = *src.getAddr32(x3, y3);
+ SkPMColor s3 = cubicBlend(fCoefficients, fractx, s03, s13, s23, s33);
+ *dptr++ = cubicBlend(fCoefficients, fracty, s0, s1, s2, s3);
+ }
+ }
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+class GrGLBicubicEffect;
+
+class GrBicubicEffect : public GrSingleTextureEffect {
+public:
+ virtual ~GrBicubicEffect();
+
+ static const char* Name() { return "Bicubic"; }
+ const float* coefficients() const { return fCoefficients; }
+
+ typedef GrGLBicubicEffect GLEffect;
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+ virtual void getConstantColorComponents(GrColor* color, uint32_t* validFlags) const SK_OVERRIDE;
+
+ static GrEffectRef* Create(GrTexture* tex, const SkScalar coefficients[16]) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrBicubicEffect, (tex, coefficients)));
+ return CreateEffectRef(effect);
+ }
+
+private:
+ GrBicubicEffect(GrTexture*, const SkScalar coefficients[16]);
+ virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE;
+ float fCoefficients[16];
+
+ GR_DECLARE_EFFECT_TEST;
+
+ typedef GrSingleTextureEffect INHERITED;
+};
+
+class GrGLBicubicEffect : public GrGLEffect {
+public:
+ GrGLBicubicEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect&);
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ static inline EffectKey GenKey(const GrDrawEffect&, const GrGLCaps&);
+
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+private:
+ typedef GrGLUniformManager::UniformHandle UniformHandle;
+
+ UniformHandle fCoefficientsUni;
+ UniformHandle fImageIncrementUni;
+
+ GrGLEffectMatrix fEffectMatrix;
+
+ typedef GrGLEffect INHERITED;
+};
+
+GrGLBicubicEffect::GrGLBicubicEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect)
+ : INHERITED(factory)
+ , fCoefficientsUni(GrGLUniformManager::kInvalidUniformHandle)
+ , fImageIncrementUni(GrGLUniformManager::kInvalidUniformHandle)
+ , fEffectMatrix(drawEffect.castEffect<GrBicubicEffect>().coordsType()) {
+}
+
+void GrGLBicubicEffect::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect&,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ const char* coords;
+ fEffectMatrix.emitCodeMakeFSCoords2D(builder, key, &coords);
+ fCoefficientsUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kMat44f_GrSLType, "Coefficients");
+ fImageIncrementUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec2f_GrSLType, "ImageIncrement");
+
+ const char* imgInc = builder->getUniformCStr(fImageIncrementUni);
+ const char* coeff = builder->getUniformCStr(fCoefficientsUni);
+
+ SkString cubicBlendName;
+
+ static const GrGLShaderVar gCubicBlendArgs[] = {
+ GrGLShaderVar("coefficients", kMat44f_GrSLType),
+ GrGLShaderVar("t", kFloat_GrSLType),
+ GrGLShaderVar("c0", kVec4f_GrSLType),
+ GrGLShaderVar("c1", kVec4f_GrSLType),
+ GrGLShaderVar("c2", kVec4f_GrSLType),
+ GrGLShaderVar("c3", kVec4f_GrSLType),
+ };
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec4f_GrSLType,
+ "cubicBlend",
+ SK_ARRAY_COUNT(gCubicBlendArgs),
+ gCubicBlendArgs,
+ "\tvec4 ts = vec4(1.0, t, t * t, t * t * t);\n"
+ "\tvec4 c = coefficients * ts;\n"
+ "\treturn c.x * c0 + c.y * c1 + c.z * c2 + c.w * c3;\n",
+ &cubicBlendName);
+ builder->fsCodeAppendf("\tvec2 coord = %s - %s * vec2(0.5, 0.5);\n", coords, imgInc);
+ builder->fsCodeAppendf("\tvec2 f = fract(coord / %s);\n", imgInc);
+ for (int y = 0; y < 4; ++y) {
+ for (int x = 0; x < 4; ++x) {
+ SkString coord;
+ coord.printf("coord + %s * vec2(%d, %d)", imgInc, x - 1, y - 1);
+ builder->fsCodeAppendf("\tvec4 s%d%d = ", x, y);
+ builder->appendTextureLookup(GrGLShaderBuilder::kFragment_ShaderType,
+ samplers[0],
+ coord.c_str());
+ builder->fsCodeAppend(";\n");
+ }
+ builder->fsCodeAppendf("\tvec4 s%d = %s(%s, f.x, s0%d, s1%d, s2%d, s3%d);\n", y, cubicBlendName.c_str(), coeff, y, y, y, y);
+ }
+ builder->fsCodeAppendf("\t%s = %s(%s, f.y, s0, s1, s2, s3);\n", outputColor, cubicBlendName.c_str(), coeff);
+}
+
+GrGLEffect::EffectKey GrGLBicubicEffect::GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
+ const GrBicubicEffect& bicubic = drawEffect.castEffect<GrBicubicEffect>();
+ EffectKey matrixKey = GrGLEffectMatrix::GenKey(bicubic.getMatrix(),
+ drawEffect,
+ bicubic.coordsType(),
+ bicubic.texture(0));
+ return matrixKey;
+}
+
+void GrGLBicubicEffect::setData(const GrGLUniformManager& uman,
+ const GrDrawEffect& drawEffect) {
+ const GrBicubicEffect& effect = drawEffect.castEffect<GrBicubicEffect>();
+ GrTexture& texture = *effect.texture(0);
+ float imageIncrement[2];
+ imageIncrement[0] = 1.0f / texture.width();
+ imageIncrement[1] = 1.0f / texture.height();
+ uman.set2fv(fImageIncrementUni, 0, 1, imageIncrement);
+ uman.setMatrix4f(fCoefficientsUni, effect.coefficients());
+ fEffectMatrix.setData(uman,
+ effect.getMatrix(),
+ drawEffect,
+ effect.texture(0));
+}
+
+GrBicubicEffect::GrBicubicEffect(GrTexture* texture,
+ const SkScalar coefficients[16])
+ : INHERITED(texture, MakeDivByTextureWHMatrix(texture)) {
+ for (int y = 0; y < 4; y++) {
+ for (int x = 0; x < 4; x++) {
+ // Convert from row-major scalars to column-major floats.
+ fCoefficients[x * 4 + y] = SkScalarToFloat(coefficients[y * 4 + x]);
+ }
+ }
+}
+
+GrBicubicEffect::~GrBicubicEffect() {
+}
+
+const GrBackendEffectFactory& GrBicubicEffect::getFactory() const {
+ return GrTBackendEffectFactory<GrBicubicEffect>::getInstance();
+}
+
+bool GrBicubicEffect::onIsEqual(const GrEffect& sBase) const {
+ const GrBicubicEffect& s = CastEffect<GrBicubicEffect>(sBase);
+ return this->texture(0) == s.texture(0) &&
+ !memcmp(fCoefficients, s.coefficients(), 16);
+}
+
+void GrBicubicEffect::getConstantColorComponents(GrColor* color, uint32_t* validFlags) const {
+ // FIXME: Perhaps we can do better.
+ *validFlags = 0;
+ return;
+}
+
+GR_DEFINE_EFFECT_TEST(GrBicubicEffect);
+
+GrEffectRef* GrBicubicEffect::TestCreate(SkMWCRandom* random,
+ GrContext* context,
+ const GrDrawTargetCaps&,
+ GrTexture* textures[]) {
+ int texIdx = random->nextBool() ? GrEffectUnitTest::kSkiaPMTextureIdx :
+ GrEffectUnitTest::kAlphaTextureIdx;
+ SkScalar coefficients[16];
+ for (int i = 0; i < 16; i++) {
+ coefficients[i] = random->nextSScalar1();
+ }
+ return GrBicubicEffect::Create(textures[texIdx], coefficients);
+}
+
+bool SkBicubicImageFilter::filterImageGPU(Proxy* proxy, const SkBitmap& src, const SkMatrix& ctm,
+ SkBitmap* result, SkIPoint* offset) {
+ SkBitmap srcBM;
+ if (!SkImageFilterUtils::GetInputResultGPU(getInput(0), proxy, src, ctm, &srcBM, offset)) {
+ return false;
+ }
+ GrTexture* srcTexture = srcBM.getTexture();
+ GrContext* context = srcTexture->getContext();
+
+ SkRect dstRect = SkRect::MakeWH(srcBM.width() * fScale.fWidth,
+ srcBM.height() * fScale.fHeight);
+
+ GrTextureDesc desc;
+ desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
+ desc.fWidth = SkScalarCeilToInt(dstRect.width());
+ desc.fHeight = SkScalarCeilToInt(dstRect.height());
+ desc.fConfig = kSkia8888_GrPixelConfig;
+
+ GrAutoScratchTexture ast(context, desc);
+ SkAutoTUnref<GrTexture> dst(ast.detach());
+ if (!dst) {
+ return false;
+ }
+ GrContext::AutoRenderTarget art(context, dst->asRenderTarget());
+ GrPaint paint;
+ paint.addColorEffect(GrBicubicEffect::Create(srcTexture, fCoefficients))->unref();
+ SkRect srcRect;
+ srcBM.getBounds(&srcRect);
+ context->drawRectToRect(paint, dstRect, srcRect);
+ return SkImageFilterUtils::WrapTexture(dst, desc.fWidth, desc.fHeight, result);
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
diff --git a/effects/SkBitmapSource.cpp b/effects/SkBitmapSource.cpp
new file mode 100644
index 00000000..854df9df
--- /dev/null
+++ b/effects/SkBitmapSource.cpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBitmapSource.h"
+
+SkBitmapSource::SkBitmapSource(const SkBitmap& bitmap)
+ : INHERITED(0),
+ fBitmap(bitmap) {
+}
+
+SkBitmapSource::SkBitmapSource(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {
+ fBitmap.unflatten(buffer);
+}
+
+void SkBitmapSource::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ fBitmap.flatten(buffer);
+}
+
+bool SkBitmapSource::onFilterImage(Proxy*, const SkBitmap&, const SkMatrix&,
+ SkBitmap* result, SkIPoint* offset) {
+ *result = fBitmap;
+ return true;
+}
diff --git a/effects/SkBlurDrawLooper.cpp b/effects/SkBlurDrawLooper.cpp
new file mode 100644
index 00000000..9585214b
--- /dev/null
+++ b/effects/SkBlurDrawLooper.cpp
@@ -0,0 +1,151 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "SkBlurDrawLooper.h"
+#include "SkBlurMaskFilter.h"
+#include "SkCanvas.h"
+#include "SkColorFilter.h"
+#include "SkFlattenableBuffers.h"
+#include "SkMaskFilter.h"
+#include "SkPaint.h"
+#include "SkString.h"
+#include "SkStringUtils.h"
+
+SkBlurDrawLooper::SkBlurDrawLooper(SkScalar radius, SkScalar dx, SkScalar dy,
+ SkColor color, uint32_t flags)
+ : fDx(dx), fDy(dy), fBlurColor(color), fBlurFlags(flags), fState(kDone) {
+
+ SkASSERT(flags <= kAll_BlurFlag);
+ if (radius > 0) {
+ uint32_t blurFlags = flags & kIgnoreTransform_BlurFlag ?
+ SkBlurMaskFilter::kIgnoreTransform_BlurFlag :
+ SkBlurMaskFilter::kNone_BlurFlag;
+
+ blurFlags |= flags & kHighQuality_BlurFlag ?
+ SkBlurMaskFilter::kHighQuality_BlurFlag :
+ SkBlurMaskFilter::kNone_BlurFlag;
+
+ fBlur = SkBlurMaskFilter::Create(radius,
+ SkBlurMaskFilter::kNormal_BlurStyle,
+ blurFlags);
+ } else {
+ fBlur = NULL;
+ }
+
+ if (flags & kOverrideColor_BlurFlag) {
+ // Set alpha to 1 for the override since transparency will already
+ // be baked into the blurred mask.
+ SkColor opaqueColor = SkColorSetA(color, 255);
+ //The SrcIn xfer mode will multiply 'color' by the incoming alpha
+ fColorFilter = SkColorFilter::CreateModeFilter(opaqueColor,
+ SkXfermode::kSrcIn_Mode);
+ } else {
+ fColorFilter = NULL;
+ }
+}
+
+SkBlurDrawLooper::SkBlurDrawLooper(SkFlattenableReadBuffer& buffer)
+: INHERITED(buffer) {
+
+ fDx = buffer.readScalar();
+ fDy = buffer.readScalar();
+ fBlurColor = buffer.readColor();
+ fBlur = buffer.readFlattenableT<SkMaskFilter>();
+ fColorFilter = buffer.readFlattenableT<SkColorFilter>();
+ fBlurFlags = buffer.readUInt() & kAll_BlurFlag;
+}
+
+SkBlurDrawLooper::~SkBlurDrawLooper() {
+ SkSafeUnref(fBlur);
+ SkSafeUnref(fColorFilter);
+}
+
+void SkBlurDrawLooper::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeScalar(fDx);
+ buffer.writeScalar(fDy);
+ buffer.writeColor(fBlurColor);
+ buffer.writeFlattenable(fBlur);
+ buffer.writeFlattenable(fColorFilter);
+ buffer.writeUInt(fBlurFlags);
+}
+
+void SkBlurDrawLooper::init(SkCanvas*) {
+ fState = kBeforeEdge;
+}
+
+bool SkBlurDrawLooper::next(SkCanvas* canvas, SkPaint* paint) {
+ switch (fState) {
+ case kBeforeEdge:
+ // we do nothing if a maskfilter is already installed
+ if (paint->getMaskFilter()) {
+ fState = kDone;
+ return false;
+ }
+#ifdef SK_BUILD_FOR_ANDROID
+ SkColor blurColor;
+ blurColor = fBlurColor;
+ if (SkColorGetA(blurColor) == 255) {
+ blurColor = SkColorSetA(blurColor, paint->getAlpha());
+ }
+ paint->setColor(blurColor);
+#else
+ paint->setColor(fBlurColor);
+#endif
+ paint->setMaskFilter(fBlur);
+ paint->setColorFilter(fColorFilter);
+ canvas->save(SkCanvas::kMatrix_SaveFlag);
+ if (fBlurFlags & kIgnoreTransform_BlurFlag) {
+ SkMatrix transform(canvas->getTotalMatrix());
+ transform.postTranslate(fDx, fDy);
+ canvas->setMatrix(transform);
+ } else {
+ canvas->translate(fDx, fDy);
+ }
+ fState = kAfterEdge;
+ return true;
+ case kAfterEdge:
+ canvas->restore();
+ fState = kDone;
+ return true;
+ default:
+ SkASSERT(kDone == fState);
+ return false;
+ }
+}
+
+#ifdef SK_DEVELOPER
+void SkBlurDrawLooper::toString(SkString* str) const {
+ str->append("SkBlurDrawLooper: ");
+
+ str->append("dx: ");
+ str->appendScalar(fDx);
+
+ str->append(" dy: ");
+ str->appendScalar(fDy);
+
+ str->append(" color: ");
+ str->appendHex(fBlurColor);
+
+ str->append(" flags: (");
+ if (kNone_BlurFlag == fBlurFlags) {
+ str->append("None");
+ } else {
+ bool needsSeparator = false;
+ SkAddFlagToString(str, SkToBool(kIgnoreTransform_BlurFlag & fBlurFlags), "IgnoreTransform",
+ &needsSeparator);
+ SkAddFlagToString(str, SkToBool(kOverrideColor_BlurFlag & fBlurFlags), "OverrideColor",
+ &needsSeparator);
+ SkAddFlagToString(str, SkToBool(kHighQuality_BlurFlag & fBlurFlags), "HighQuality",
+ &needsSeparator);
+ }
+ str->append(")");
+
+ // TODO: add optional "fBlurFilter->toString(str);" when SkMaskFilter::toString is added
+ // alternatively we could cache the radius in SkBlurDrawLooper and just add it here
+}
+#endif
diff --git a/effects/SkBlurImageFilter.cpp b/effects/SkBlurImageFilter.cpp
new file mode 100644
index 00000000..4b2d3b88
--- /dev/null
+++ b/effects/SkBlurImageFilter.cpp
@@ -0,0 +1,236 @@
+/*
+ * Copyright 2011 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBitmap.h"
+#include "SkBlurImageFilter.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkGpuBlurUtils.h"
+#if SK_SUPPORT_GPU
+#include "GrContext.h"
+#include "SkImageFilterUtils.h"
+#endif
+
+SkBlurImageFilter::SkBlurImageFilter(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {
+ fSigma.fWidth = buffer.readScalar();
+ fSigma.fHeight = buffer.readScalar();
+}
+
+SkBlurImageFilter::SkBlurImageFilter(SkScalar sigmaX,
+ SkScalar sigmaY,
+ SkImageFilter* input,
+ const SkIRect* cropRect)
+ : INHERITED(input, cropRect), fSigma(SkSize::Make(sigmaX, sigmaY)) {
+ SkASSERT(sigmaX >= 0 && sigmaY >= 0);
+}
+
+void SkBlurImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeScalar(fSigma.fWidth);
+ buffer.writeScalar(fSigma.fHeight);
+}
+
+static void boxBlurX(const SkBitmap& src, SkBitmap* dst, int kernelSize,
+ int leftOffset, int rightOffset, const SkIRect& bounds)
+{
+ int width = bounds.width(), height = bounds.height();
+ int rightBorder = SkMin32(rightOffset + 1, width);
+ for (int y = 0; y < height; ++y) {
+ int sumA = 0, sumR = 0, sumG = 0, sumB = 0;
+ SkPMColor* p = src.getAddr32(bounds.fLeft, y + bounds.fTop);
+ for (int i = 0; i < rightBorder; ++i) {
+ sumA += SkGetPackedA32(*p);
+ sumR += SkGetPackedR32(*p);
+ sumG += SkGetPackedG32(*p);
+ sumB += SkGetPackedB32(*p);
+ p++;
+ }
+
+ const SkColor* sptr = src.getAddr32(bounds.fLeft, bounds.fTop + y);
+ SkColor* dptr = dst->getAddr32(0, y);
+ for (int x = 0; x < width; ++x) {
+ *dptr = SkPackARGB32(sumA / kernelSize,
+ sumR / kernelSize,
+ sumG / kernelSize,
+ sumB / kernelSize);
+ if (x >= leftOffset) {
+ SkColor l = *(sptr - leftOffset);
+ sumA -= SkGetPackedA32(l);
+ sumR -= SkGetPackedR32(l);
+ sumG -= SkGetPackedG32(l);
+ sumB -= SkGetPackedB32(l);
+ }
+ if (x + rightOffset + 1 < width) {
+ SkColor r = *(sptr + rightOffset + 1);
+ sumA += SkGetPackedA32(r);
+ sumR += SkGetPackedR32(r);
+ sumG += SkGetPackedG32(r);
+ sumB += SkGetPackedB32(r);
+ }
+ sptr++;
+ dptr++;
+ }
+ }
+}
+
+static void boxBlurY(const SkBitmap& src, SkBitmap* dst, int kernelSize,
+ int topOffset, int bottomOffset, const SkIRect& bounds)
+{
+ int width = bounds.width(), height = bounds.height();
+ int bottomBorder = SkMin32(bottomOffset + 1, height);
+ int srcStride = src.rowBytesAsPixels();
+ int dstStride = dst->rowBytesAsPixels();
+ for (int x = 0; x < width; ++x) {
+ int sumA = 0, sumR = 0, sumG = 0, sumB = 0;
+ SkColor* p = src.getAddr32(bounds.fLeft + x, bounds.fTop);
+ for (int i = 0; i < bottomBorder; ++i) {
+ sumA += SkGetPackedA32(*p);
+ sumR += SkGetPackedR32(*p);
+ sumG += SkGetPackedG32(*p);
+ sumB += SkGetPackedB32(*p);
+ p += srcStride;
+ }
+
+ const SkColor* sptr = src.getAddr32(bounds.fLeft + x, bounds.fTop);
+ SkColor* dptr = dst->getAddr32(x, 0);
+ for (int y = 0; y < height; ++y) {
+ *dptr = SkPackARGB32(sumA / kernelSize,
+ sumR / kernelSize,
+ sumG / kernelSize,
+ sumB / kernelSize);
+ if (y >= topOffset) {
+ SkColor l = *(sptr - topOffset * srcStride);
+ sumA -= SkGetPackedA32(l);
+ sumR -= SkGetPackedR32(l);
+ sumG -= SkGetPackedG32(l);
+ sumB -= SkGetPackedB32(l);
+ }
+ if (y + bottomOffset + 1 < height) {
+ SkColor r = *(sptr + (bottomOffset + 1) * srcStride);
+ sumA += SkGetPackedA32(r);
+ sumR += SkGetPackedR32(r);
+ sumG += SkGetPackedG32(r);
+ sumB += SkGetPackedB32(r);
+ }
+ sptr += srcStride;
+ dptr += dstStride;
+ }
+ }
+}
+
+static void getBox3Params(SkScalar s, int *kernelSize, int* kernelSize3, int *lowOffset,
+ int *highOffset)
+{
+ float pi = SkScalarToFloat(SK_ScalarPI);
+ int d = static_cast<int>(floorf(SkScalarToFloat(s) * 3.0f * sqrtf(2.0f * pi) / 4.0f + 0.5f));
+ *kernelSize = d;
+ if (d % 2 == 1) {
+ *lowOffset = *highOffset = (d - 1) / 2;
+ *kernelSize3 = d;
+ } else {
+ *highOffset = d / 2;
+ *lowOffset = *highOffset - 1;
+ *kernelSize3 = d + 1;
+ }
+}
+
+bool SkBlurImageFilter::onFilterImage(Proxy* proxy,
+ const SkBitmap& source, const SkMatrix& ctm,
+ SkBitmap* dst, SkIPoint* offset) {
+ SkBitmap src = source;
+ if (getInput(0) && !getInput(0)->filterImage(proxy, source, ctm, &src, offset)) {
+ return false;
+ }
+
+ if (src.config() != SkBitmap::kARGB_8888_Config) {
+ return false;
+ }
+
+ SkAutoLockPixels alp(src);
+ if (!src.getPixels()) {
+ return false;
+ }
+
+ SkIRect srcBounds, dstBounds;
+ src.getBounds(&srcBounds);
+ if (!this->applyCropRect(&srcBounds)) {
+ return false;
+ }
+
+ dst->setConfig(src.config(), srcBounds.width(), srcBounds.height());
+ dst->getBounds(&dstBounds);
+ dst->allocPixels();
+ int kernelSizeX, kernelSizeX3, lowOffsetX, highOffsetX;
+ int kernelSizeY, kernelSizeY3, lowOffsetY, highOffsetY;
+ getBox3Params(fSigma.width(), &kernelSizeX, &kernelSizeX3, &lowOffsetX, &highOffsetX);
+ getBox3Params(fSigma.height(), &kernelSizeY, &kernelSizeY3, &lowOffsetY, &highOffsetY);
+
+ if (kernelSizeX < 0 || kernelSizeY < 0) {
+ return false;
+ }
+
+ if (kernelSizeX == 0 && kernelSizeY == 0) {
+ src.copyTo(dst, dst->config());
+ return true;
+ }
+
+ SkBitmap temp;
+ temp.setConfig(dst->config(), dst->width(), dst->height());
+ if (!temp.allocPixels()) {
+ return false;
+ }
+
+ if (kernelSizeX > 0 && kernelSizeY > 0) {
+ boxBlurX(src, &temp, kernelSizeX, lowOffsetX, highOffsetX, srcBounds);
+ boxBlurY(temp, dst, kernelSizeY, lowOffsetY, highOffsetY, dstBounds);
+ boxBlurX(*dst, &temp, kernelSizeX, highOffsetX, lowOffsetX, dstBounds);
+ boxBlurY(temp, dst, kernelSizeY, highOffsetY, lowOffsetY, dstBounds);
+ boxBlurX(*dst, &temp, kernelSizeX3, highOffsetX, highOffsetX, dstBounds);
+ boxBlurY(temp, dst, kernelSizeY3, highOffsetY, highOffsetY, dstBounds);
+ } else if (kernelSizeX > 0) {
+ boxBlurX(src, dst, kernelSizeX, lowOffsetX, highOffsetX, srcBounds);
+ boxBlurX(*dst, &temp, kernelSizeX, highOffsetX, lowOffsetX, dstBounds);
+ boxBlurX(temp, dst, kernelSizeX3, highOffsetX, highOffsetX, dstBounds);
+ } else if (kernelSizeY > 0) {
+ boxBlurY(src, dst, kernelSizeY, lowOffsetY, highOffsetY, srcBounds);
+ boxBlurY(*dst, &temp, kernelSizeY, highOffsetY, lowOffsetY, dstBounds);
+ boxBlurY(temp, dst, kernelSizeY3, highOffsetY, highOffsetY, dstBounds);
+ }
+ offset->fX += srcBounds.fLeft;
+ offset->fY += srcBounds.fTop;
+ return true;
+}
+
+bool SkBlurImageFilter::filterImageGPU(Proxy* proxy, const SkBitmap& src, const SkMatrix& ctm,
+ SkBitmap* result, SkIPoint* offset) {
+#if SK_SUPPORT_GPU
+ SkBitmap input;
+ if (!SkImageFilterUtils::GetInputResultGPU(getInput(0), proxy, src, ctm, &input, offset)) {
+ return false;
+ }
+ GrTexture* source = input.getTexture();
+ SkIRect rect;
+ src.getBounds(&rect);
+ if (!this->applyCropRect(&rect)) {
+ return false;
+ }
+ SkAutoTUnref<GrTexture> tex(SkGpuBlurUtils::GaussianBlur(source->getContext(),
+ source,
+ false,
+ SkRect::Make(rect),
+ true,
+ fSigma.width(),
+ fSigma.height()));
+ offset->fX += rect.fLeft;
+ offset->fY += rect.fTop;
+ return SkImageFilterUtils::WrapTexture(tex, rect.width(), rect.height(), result);
+#else
+ SkDEBUGFAIL("Should not call in GPU-less build");
+ return false;
+#endif
+}
diff --git a/effects/SkBlurMask.cpp b/effects/SkBlurMask.cpp
new file mode 100644
index 00000000..c946c5eb
--- /dev/null
+++ b/effects/SkBlurMask.cpp
@@ -0,0 +1,985 @@
+
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#include "SkBlurMask.h"
+#include "SkMath.h"
+#include "SkTemplates.h"
+#include "SkEndian.h"
+
+const SkScalar SkBlurMask::kBlurRadiusFudgeFactor = SkFloatToScalar(.57735f);
+
+#define UNROLL_SEPARABLE_LOOPS
+
+/**
+ * This function performs a box blur in X, of the given radius. If the
+ * "transpose" parameter is true, it will transpose the pixels on write,
+ * such that X and Y are swapped. Reads are always performed from contiguous
+ * memory in X, for speed. The destination buffer (dst) must be at least
+ * (width + leftRadius + rightRadius) * height bytes in size.
+ *
+ * This is what the inner loop looks like before unrolling, and with the two
+ * cases broken out separately (width < diameter, width >= diameter):
+ *
+ * if (width < diameter) {
+ * for (int x = 0; x < width; ++x) {
+ * sum += *right++;
+ * *dptr = (sum * scale + half) >> 24;
+ * dptr += dst_x_stride;
+ * }
+ * for (int x = width; x < diameter; ++x) {
+ * *dptr = (sum * scale + half) >> 24;
+ * dptr += dst_x_stride;
+ * }
+ * for (int x = 0; x < width; ++x) {
+ * *dptr = (sum * scale + half) >> 24;
+ * sum -= *left++;
+ * dptr += dst_x_stride;
+ * }
+ * } else {
+ * for (int x = 0; x < diameter; ++x) {
+ * sum += *right++;
+ * *dptr = (sum * scale + half) >> 24;
+ * dptr += dst_x_stride;
+ * }
+ * for (int x = diameter; x < width; ++x) {
+ * sum += *right++;
+ * *dptr = (sum * scale + half) >> 24;
+ * sum -= *left++;
+ * dptr += dst_x_stride;
+ * }
+ * for (int x = 0; x < diameter; ++x) {
+ * *dptr = (sum * scale + half) >> 24;
+ * sum -= *left++;
+ * dptr += dst_x_stride;
+ * }
+ * }
+ */
+static int boxBlur(const uint8_t* src, int src_y_stride, uint8_t* dst,
+ int leftRadius, int rightRadius, int width, int height,
+ bool transpose)
+{
+ int diameter = leftRadius + rightRadius;
+ int kernelSize = diameter + 1;
+ int border = SkMin32(width, diameter);
+ uint32_t scale = (1 << 24) / kernelSize;
+ int new_width = width + SkMax32(leftRadius, rightRadius) * 2;
+ int dst_x_stride = transpose ? height : 1;
+ int dst_y_stride = transpose ? 1 : new_width;
+#ifndef SK_DISABLE_BLUR_ROUNDING
+ uint32_t half = 1 << 23;
+#else
+ uint32_t half = 0;
+#endif
+ for (int y = 0; y < height; ++y) {
+ uint32_t sum = 0;
+ uint8_t* dptr = dst + y * dst_y_stride;
+ const uint8_t* right = src + y * src_y_stride;
+ const uint8_t* left = right;
+ for (int x = 0; x < rightRadius - leftRadius; x++) {
+ *dptr = 0;
+ dptr += dst_x_stride;
+ }
+#define LEFT_BORDER_ITER \
+ sum += *right++; \
+ *dptr = (sum * scale + half) >> 24; \
+ dptr += dst_x_stride;
+
+ int x = 0;
+#ifdef UNROLL_SEPARABLE_LOOPS
+ for (; x < border - 16; x += 16) {
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ }
+#endif
+ for (; x < border; ++x) {
+ LEFT_BORDER_ITER
+ }
+#undef LEFT_BORDER_ITER
+#define TRIVIAL_ITER \
+ *dptr = (sum * scale + half) >> 24; \
+ dptr += dst_x_stride;
+ x = width;
+#ifdef UNROLL_SEPARABLE_LOOPS
+ for (; x < diameter - 16; x += 16) {
+ TRIVIAL_ITER
+ TRIVIAL_ITER
+ TRIVIAL_ITER
+ TRIVIAL_ITER
+ TRIVIAL_ITER
+ TRIVIAL_ITER
+ TRIVIAL_ITER
+ TRIVIAL_ITER
+ TRIVIAL_ITER
+ TRIVIAL_ITER
+ TRIVIAL_ITER
+ TRIVIAL_ITER
+ TRIVIAL_ITER
+ TRIVIAL_ITER
+ TRIVIAL_ITER
+ TRIVIAL_ITER
+ }
+#endif
+ for (; x < diameter; ++x) {
+ TRIVIAL_ITER
+ }
+#undef TRIVIAL_ITER
+#define CENTER_ITER \
+ sum += *right++; \
+ *dptr = (sum * scale + half) >> 24; \
+ sum -= *left++; \
+ dptr += dst_x_stride;
+
+ x = diameter;
+#ifdef UNROLL_SEPARABLE_LOOPS
+ for (; x < width - 16; x += 16) {
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ }
+#endif
+ for (; x < width; ++x) {
+ CENTER_ITER
+ }
+#undef CENTER_ITER
+#define RIGHT_BORDER_ITER \
+ *dptr = (sum * scale + half) >> 24; \
+ sum -= *left++; \
+ dptr += dst_x_stride;
+
+ x = 0;
+#ifdef UNROLL_SEPARABLE_LOOPS
+ for (; x < border - 16; x += 16) {
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ }
+#endif
+ for (; x < border; ++x) {
+ RIGHT_BORDER_ITER
+ }
+#undef RIGHT_BORDER_ITER
+ for (int x = 0; x < leftRadius - rightRadius; ++x) {
+ *dptr = 0;
+ dptr += dst_x_stride;
+ }
+ SkASSERT(sum == 0);
+ }
+ return new_width;
+}
+
+/**
+ * This variant of the box blur handles blurring of non-integer radii. It
+ * keeps two running sums: an outer sum for the rounded-up kernel radius, and
+ * an inner sum for the rounded-down kernel radius. For each pixel, it linearly
+ * interpolates between them. In float this would be:
+ * outer_weight * outer_sum / kernelSize +
+ * (1.0 - outer_weight) * innerSum / (kernelSize - 2)
+ *
+ * This is what the inner loop looks like before unrolling, and with the two
+ * cases broken out separately (width < diameter, width >= diameter):
+ *
+ * if (width < diameter) {
+ * for (int x = 0; x < width; x++) {
+ * inner_sum = outer_sum;
+ * outer_sum += *right++;
+ * *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
+ * dptr += dst_x_stride;
+ * }
+ * for (int x = width; x < diameter; ++x) {
+ * *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
+ * dptr += dst_x_stride;
+ * }
+ * for (int x = 0; x < width; x++) {
+ * inner_sum = outer_sum - *left++;
+ * *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
+ * dptr += dst_x_stride;
+ * outer_sum = inner_sum;
+ * }
+ * } else {
+ * for (int x = 0; x < diameter; x++) {
+ * inner_sum = outer_sum;
+ * outer_sum += *right++;
+ * *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
+ * dptr += dst_x_stride;
+ * }
+ * for (int x = diameter; x < width; ++x) {
+ * inner_sum = outer_sum - *left;
+ * outer_sum += *right++;
+ * *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
+ * dptr += dst_x_stride;
+ * outer_sum -= *left++;
+ * }
+ * for (int x = 0; x < diameter; x++) {
+ * inner_sum = outer_sum - *left++;
+ * *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
+ * dptr += dst_x_stride;
+ * outer_sum = inner_sum;
+ * }
+ * }
+ * }
+ * return new_width;
+ */
+
+static int boxBlurInterp(const uint8_t* src, int src_y_stride, uint8_t* dst,
+ int radius, int width, int height,
+ bool transpose, uint8_t outer_weight)
+{
+ int diameter = radius * 2;
+ int kernelSize = diameter + 1;
+ int border = SkMin32(width, diameter);
+ int inner_weight = 255 - outer_weight;
+ outer_weight += outer_weight >> 7;
+ inner_weight += inner_weight >> 7;
+ uint32_t outer_scale = (outer_weight << 16) / kernelSize;
+ uint32_t inner_scale = (inner_weight << 16) / (kernelSize - 2);
+#ifndef SK_DISABLE_BLUR_ROUNDING
+ uint32_t half = 1 << 23;
+#else
+ uint32_t half = 0;
+#endif
+ int new_width = width + diameter;
+ int dst_x_stride = transpose ? height : 1;
+ int dst_y_stride = transpose ? 1 : new_width;
+ for (int y = 0; y < height; ++y) {
+ uint32_t outer_sum = 0, inner_sum = 0;
+ uint8_t* dptr = dst + y * dst_y_stride;
+ const uint8_t* right = src + y * src_y_stride;
+ const uint8_t* left = right;
+ int x = 0;
+
+#define LEFT_BORDER_ITER \
+ inner_sum = outer_sum; \
+ outer_sum += *right++; \
+ *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24; \
+ dptr += dst_x_stride;
+
+#ifdef UNROLL_SEPARABLE_LOOPS
+ for (;x < border - 16; x += 16) {
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ LEFT_BORDER_ITER
+ }
+#endif
+
+ for (;x < border; ++x) {
+ LEFT_BORDER_ITER
+ }
+#undef LEFT_BORDER_ITER
+ for (int x = width; x < diameter; ++x) {
+ *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
+ dptr += dst_x_stride;
+ }
+ x = diameter;
+
+#define CENTER_ITER \
+ inner_sum = outer_sum - *left; \
+ outer_sum += *right++; \
+ *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24; \
+ dptr += dst_x_stride; \
+ outer_sum -= *left++;
+
+#ifdef UNROLL_SEPARABLE_LOOPS
+ for (; x < width - 16; x += 16) {
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ CENTER_ITER
+ }
+#endif
+ for (; x < width; ++x) {
+ CENTER_ITER
+ }
+#undef CENTER_ITER
+
+ #define RIGHT_BORDER_ITER \
+ inner_sum = outer_sum - *left++; \
+ *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24; \
+ dptr += dst_x_stride; \
+ outer_sum = inner_sum;
+
+ x = 0;
+#ifdef UNROLL_SEPARABLE_LOOPS
+ for (; x < border - 16; x += 16) {
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ RIGHT_BORDER_ITER
+ }
+#endif
+ for (; x < border; ++x) {
+ RIGHT_BORDER_ITER
+ }
+#undef RIGHT_BORDER_ITER
+ SkASSERT(outer_sum == 0 && inner_sum == 0);
+ }
+ return new_width;
+}
+
+static void get_adjusted_radii(SkScalar passRadius, int *loRadius, int *hiRadius)
+{
+ *loRadius = *hiRadius = SkScalarCeil(passRadius);
+ if (SkIntToScalar(*hiRadius) - passRadius > SkFloatToScalar(0.5f)) {
+ *loRadius = *hiRadius - 1;
+ }
+}
+
+#include "SkColorPriv.h"
+
+static void merge_src_with_blur(uint8_t dst[], int dstRB,
+ const uint8_t src[], int srcRB,
+ const uint8_t blur[], int blurRB,
+ int sw, int sh) {
+ dstRB -= sw;
+ srcRB -= sw;
+ blurRB -= sw;
+ while (--sh >= 0) {
+ for (int x = sw - 1; x >= 0; --x) {
+ *dst = SkToU8(SkAlphaMul(*blur, SkAlpha255To256(*src)));
+ dst += 1;
+ src += 1;
+ blur += 1;
+ }
+ dst += dstRB;
+ src += srcRB;
+ blur += blurRB;
+ }
+}
+
+static void clamp_with_orig(uint8_t dst[], int dstRowBytes,
+ const uint8_t src[], int srcRowBytes,
+ int sw, int sh,
+ SkBlurMask::Style style) {
+ int x;
+ while (--sh >= 0) {
+ switch (style) {
+ case SkBlurMask::kSolid_Style:
+ for (x = sw - 1; x >= 0; --x) {
+ int s = *src;
+ int d = *dst;
+ *dst = SkToU8(s + d - SkMulDiv255Round(s, d));
+ dst += 1;
+ src += 1;
+ }
+ break;
+ case SkBlurMask::kOuter_Style:
+ for (x = sw - 1; x >= 0; --x) {
+ if (*src) {
+ *dst = SkToU8(SkAlphaMul(*dst, SkAlpha255To256(255 - *src)));
+ }
+ dst += 1;
+ src += 1;
+ }
+ break;
+ default:
+ SkDEBUGFAIL("Unexpected blur style here");
+ break;
+ }
+ dst += dstRowBytes - sw;
+ src += srcRowBytes - sw;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+// we use a local function to wrap the class static method to work around
+// a bug in gcc98
+void SkMask_FreeImage(uint8_t* image);
+void SkMask_FreeImage(uint8_t* image) {
+ SkMask::FreeImage(image);
+}
+
+bool SkBlurMask::Blur(SkMask* dst, const SkMask& src,
+ SkScalar radius, Style style, Quality quality,
+ SkIPoint* margin)
+{
+
+ if (src.fFormat != SkMask::kA8_Format) {
+ return false;
+ }
+
+ // Force high quality off for small radii (performance)
+ if (radius < SkIntToScalar(3)) {
+ quality = kLow_Quality;
+ }
+
+ // highQuality: use three box blur passes as a cheap way
+ // to approximate a Gaussian blur
+ int passCount = (kHigh_Quality == quality) ? 3 : 1;
+ SkScalar passRadius = (kHigh_Quality == quality) ?
+ SkScalarMul( radius, kBlurRadiusFudgeFactor):
+ radius;
+
+ int rx = SkScalarCeil(passRadius);
+ int outerWeight = 255 - SkScalarRound((SkIntToScalar(rx) - passRadius) * 255);
+
+ SkASSERT(rx >= 0);
+ SkASSERT((unsigned)outerWeight <= 255);
+ if (rx <= 0) {
+ return false;
+ }
+
+ int ry = rx; // only do square blur for now
+
+ int padx = passCount * rx;
+ int pady = passCount * ry;
+
+ if (margin) {
+ margin->set(padx, pady);
+ }
+ dst->fBounds.set(src.fBounds.fLeft - padx, src.fBounds.fTop - pady,
+ src.fBounds.fRight + padx, src.fBounds.fBottom + pady);
+
+ dst->fRowBytes = dst->fBounds.width();
+ dst->fFormat = SkMask::kA8_Format;
+ dst->fImage = NULL;
+
+ if (src.fImage) {
+ size_t dstSize = dst->computeImageSize();
+ if (0 == dstSize) {
+ return false; // too big to allocate, abort
+ }
+
+ int sw = src.fBounds.width();
+ int sh = src.fBounds.height();
+ const uint8_t* sp = src.fImage;
+ uint8_t* dp = SkMask::AllocImage(dstSize);
+ SkAutoTCallVProc<uint8_t, SkMask_FreeImage> autoCall(dp);
+
+ // build the blurry destination
+ SkAutoTMalloc<uint8_t> tmpBuffer(dstSize);
+ uint8_t* tp = tmpBuffer.get();
+ int w = sw, h = sh;
+
+ if (outerWeight == 255) {
+ int loRadius, hiRadius;
+ get_adjusted_radii(passRadius, &loRadius, &hiRadius);
+ if (kHigh_Quality == quality) {
+ // Do three X blurs, with a transpose on the final one.
+ w = boxBlur(sp, src.fRowBytes, tp, loRadius, hiRadius, w, h, false);
+ w = boxBlur(tp, w, dp, hiRadius, loRadius, w, h, false);
+ w = boxBlur(dp, w, tp, hiRadius, hiRadius, w, h, true);
+ // Do three Y blurs, with a transpose on the final one.
+ h = boxBlur(tp, h, dp, loRadius, hiRadius, h, w, false);
+ h = boxBlur(dp, h, tp, hiRadius, loRadius, h, w, false);
+ h = boxBlur(tp, h, dp, hiRadius, hiRadius, h, w, true);
+ } else {
+ w = boxBlur(sp, src.fRowBytes, tp, rx, rx, w, h, true);
+ h = boxBlur(tp, h, dp, ry, ry, h, w, true);
+ }
+ } else {
+ if (kHigh_Quality == quality) {
+ // Do three X blurs, with a transpose on the final one.
+ w = boxBlurInterp(sp, src.fRowBytes, tp, rx, w, h, false, outerWeight);
+ w = boxBlurInterp(tp, w, dp, rx, w, h, false, outerWeight);
+ w = boxBlurInterp(dp, w, tp, rx, w, h, true, outerWeight);
+ // Do three Y blurs, with a transpose on the final one.
+ h = boxBlurInterp(tp, h, dp, ry, h, w, false, outerWeight);
+ h = boxBlurInterp(dp, h, tp, ry, h, w, false, outerWeight);
+ h = boxBlurInterp(tp, h, dp, ry, h, w, true, outerWeight);
+ } else {
+ w = boxBlurInterp(sp, src.fRowBytes, tp, rx, w, h, true, outerWeight);
+ h = boxBlurInterp(tp, h, dp, ry, h, w, true, outerWeight);
+ }
+ }
+
+ dst->fImage = dp;
+ // if need be, alloc the "real" dst (same size as src) and copy/merge
+ // the blur into it (applying the src)
+ if (style == kInner_Style) {
+ // now we allocate the "real" dst, mirror the size of src
+ size_t srcSize = src.computeImageSize();
+ if (0 == srcSize) {
+ return false; // too big to allocate, abort
+ }
+ dst->fImage = SkMask::AllocImage(srcSize);
+ merge_src_with_blur(dst->fImage, src.fRowBytes,
+ sp, src.fRowBytes,
+ dp + passCount * (rx + ry * dst->fRowBytes),
+ dst->fRowBytes, sw, sh);
+ SkMask::FreeImage(dp);
+ } else if (style != kNormal_Style) {
+ clamp_with_orig(dp + passCount * (rx + ry * dst->fRowBytes),
+ dst->fRowBytes, sp, src.fRowBytes, sw, sh, style);
+ }
+ (void)autoCall.detach();
+ }
+
+ if (style == kInner_Style) {
+ dst->fBounds = src.fBounds; // restore trimmed bounds
+ dst->fRowBytes = src.fRowBytes;
+ }
+
+ return true;
+}
+
+/* Convolving a box with itself three times results in a piecewise
+ quadratic function:
+
+ 0 x <= -1.5
+ 9/8 + 3/2 x + 1/2 x^2 -1.5 < x <= -.5
+ 3/4 - x^2 -.5 < x <= .5
+ 9/8 - 3/2 x + 1/2 x^2 0.5 < x <= 1.5
+ 0 1.5 < x
+
+ Mathematica:
+
+ g[x_] := Piecewise [ {
+ {9/8 + 3/2 x + 1/2 x^2 , -1.5 < x <= -.5},
+ {3/4 - x^2 , -.5 < x <= .5},
+ {9/8 - 3/2 x + 1/2 x^2 , 0.5 < x <= 1.5}
+ }, 0]
+
+ To get the profile curve of the blurred step function at the rectangle
+ edge, we evaluate the indefinite integral, which is piecewise cubic:
+
+ 0 x <= -1.5
+ 9/16 + 9/8 x + 3/4 x^2 + 1/6 x^3 -1.5 < x <= -0.5
+ 1/2 + 3/4 x - 1/3 x^3 -.5 < x <= .5
+ 7/16 + 9/8 x - 3/4 x^2 + 1/6 x^3 .5 < x <= 1.5
+ 1 1.5 < x
+
+ in Mathematica code:
+
+ gi[x_] := Piecewise[ {
+ { 0 , x <= -1.5 },
+ { 9/16 + 9/8 x + 3/4 x^2 + 1/6 x^3, -1.5 < x <= -0.5 },
+ { 1/2 + 3/4 x - 1/3 x^3 , -.5 < x <= .5},
+ { 7/16 + 9/8 x - 3/4 x^2 + 1/6 x^3, .5 < x <= 1.5}
+ },1]
+*/
+
+static float gaussianIntegral(float x) {
+ if (x > 1.5f) {
+ return 0.0f;
+ }
+ if (x < -1.5f) {
+ return 1.0f;
+ }
+
+ float x2 = x*x;
+ float x3 = x2*x;
+
+ if ( x > 0.5f ) {
+ return 0.5625f - (x3 / 6.0f - 3.0f * x2 * 0.25f + 1.125f * x);
+ }
+ if ( x > -0.5f ) {
+ return 0.5f - (0.75f * x - x3 / 3.0f);
+ }
+ return 0.4375f + (-x3 / 6.0f - 3.0f * x2 * 0.25f - 1.125f * x);
+}
+
+// Compute the size of the array allocated for the profile.
+
+static int compute_profile_size(SkScalar radius) {
+ return SkScalarRoundToInt(radius * 3);
+
+}
+
+/* compute_profile allocates and fills in an array of floating
+ point values between 0 and 255 for the profile signature of
+ a blurred half-plane with the given blur radius. Since we're
+ going to be doing screened multiplications (i.e., 1 - (1-x)(1-y))
+ all the time, we actually fill in the profile pre-inverted
+ (already done 255-x).
+
+ It's the responsibility of the caller to delete the
+ memory returned in profile_out.
+*/
+
+static void compute_profile(SkScalar radius, unsigned int **profile_out) {
+ int size = compute_profile_size(radius);
+
+ int center = size >> 1;
+ unsigned int *profile = SkNEW_ARRAY(unsigned int, size);
+
+ float invr = 1.f/radius;
+
+ profile[0] = 255;
+ for (int x = 1 ; x < size ; ++x) {
+ float scaled_x = (center - x - .5f) * invr;
+ float gi = gaussianIntegral(scaled_x);
+ profile[x] = 255 - (uint8_t) (255.f * gi);
+ }
+
+ *profile_out = profile;
+}
+
+// TODO MAYBE: Maintain a profile cache to avoid recomputing this for
+// commonly used radii. Consider baking some of the most common blur radii
+// directly in as static data?
+
+// Implementation adapted from Michael Herf's approach:
+// http://stereopsis.com/shadowrect/
+
+static inline unsigned int profile_lookup( unsigned int *profile, int loc, int blurred_width, int sharp_width ) {
+ int dx = SkAbs32(((loc << 1) + 1) - blurred_width) - sharp_width; // how far are we from the original edge?
+ int ox = dx >> 1;
+ if (ox < 0) {
+ ox = 0;
+ }
+
+ return profile[ox];
+}
+
+bool SkBlurMask::BlurRect(SkMask *dst, const SkRect &src,
+ SkScalar provided_radius, Style style,
+ SkIPoint *margin, SkMask::CreateMode createMode) {
+ int profile_size;
+
+ float radius = SkScalarToFloat(SkScalarMul(provided_radius, kBlurRadiusFudgeFactor));
+
+ // adjust blur radius to match interpretation from boxfilter code
+ radius = (radius + .5f) * 2.f;
+
+ profile_size = compute_profile_size(radius);
+
+ int pad = profile_size/2;
+ if (margin) {
+ margin->set( pad, pad );
+ }
+
+ dst->fBounds.set(SkScalarRoundToInt(src.fLeft - pad),
+ SkScalarRoundToInt(src.fTop - pad),
+ SkScalarRoundToInt(src.fRight + pad),
+ SkScalarRoundToInt(src.fBottom + pad));
+
+ dst->fRowBytes = dst->fBounds.width();
+ dst->fFormat = SkMask::kA8_Format;
+ dst->fImage = NULL;
+
+ int sw = SkScalarFloorToInt(src.width());
+ int sh = SkScalarFloorToInt(src.height());
+
+ if (createMode == SkMask::kJustComputeBounds_CreateMode) {
+ if (style == kInner_Style) {
+ dst->fBounds.set(SkScalarRoundToInt(src.fLeft),
+ SkScalarRoundToInt(src.fTop),
+ SkScalarRoundToInt(src.fRight),
+ SkScalarRoundToInt(src.fBottom)); // restore trimmed bounds
+ dst->fRowBytes = sw;
+ }
+ return true;
+ }
+ unsigned int *profile = NULL;
+
+ compute_profile(radius, &profile);
+ SkAutoTDeleteArray<unsigned int> ada(profile);
+
+ size_t dstSize = dst->computeImageSize();
+ if (0 == dstSize) {
+ return false; // too big to allocate, abort
+ }
+
+ uint8_t* dp = SkMask::AllocImage(dstSize);
+
+ dst->fImage = dp;
+
+ int dstHeight = dst->fBounds.height();
+ int dstWidth = dst->fBounds.width();
+
+ // nearest odd number less than the profile size represents the center
+ // of the (2x scaled) profile
+ int center = ( profile_size & ~1 ) - 1;
+
+ int w = sw - center;
+ int h = sh - center;
+
+ uint8_t *outptr = dp;
+
+ SkAutoTMalloc<uint8_t> horizontalScanline(dstWidth);
+
+ for (int x = 0 ; x < dstWidth ; ++x) {
+ if (profile_size <= sw) {
+ horizontalScanline[x] = profile_lookup(profile, x, dstWidth, w);
+ } else {
+ float span = float(sw)/radius;
+ float giX = 1.5f - (x+.5f)/radius;
+ horizontalScanline[x] = (uint8_t) (255 * (gaussianIntegral(giX) - gaussianIntegral(giX + span)));
+ }
+ }
+
+ for (int y = 0 ; y < dstHeight ; ++y) {
+ unsigned int profile_y;
+ if (profile_size <= sh) {
+ profile_y = profile_lookup(profile, y, dstHeight, h);
+ } else {
+ float span = float(sh)/radius;
+ float giY = 1.5f - (y+.5f)/radius;
+ profile_y = (uint8_t) (255 * (gaussianIntegral(giY) - gaussianIntegral(giY + span)));
+ }
+
+ for (int x = 0 ; x < dstWidth ; x++) {
+ unsigned int maskval = SkMulDiv255Round(horizontalScanline[x], profile_y);
+ *(outptr++) = maskval;
+ }
+ }
+
+ if (style == kInner_Style) {
+ // now we allocate the "real" dst, mirror the size of src
+ size_t srcSize = (size_t)(src.width() * src.height());
+ if (0 == srcSize) {
+ return false; // too big to allocate, abort
+ }
+ dst->fImage = SkMask::AllocImage(srcSize);
+ for (int y = 0 ; y < sh ; y++) {
+ uint8_t *blur_scanline = dp + (y+pad)*dstWidth + pad;
+ uint8_t *inner_scanline = dst->fImage + y*sw;
+ memcpy(inner_scanline, blur_scanline, sw);
+ }
+ SkMask::FreeImage(dp);
+
+ dst->fBounds.set(SkScalarRoundToInt(src.fLeft),
+ SkScalarRoundToInt(src.fTop),
+ SkScalarRoundToInt(src.fRight),
+ SkScalarRoundToInt(src.fBottom)); // restore trimmed bounds
+ dst->fRowBytes = sw;
+
+ } else if (style == kOuter_Style) {
+ for (int y = pad ; y < dstHeight-pad ; y++) {
+ uint8_t *dst_scanline = dp + y*dstWidth + pad;
+ memset(dst_scanline, 0, sw);
+ }
+ } else if (style == kSolid_Style) {
+ for (int y = pad ; y < dstHeight-pad ; y++) {
+ uint8_t *dst_scanline = dp + y*dstWidth + pad;
+ memset(dst_scanline, 0xff, sw);
+ }
+ }
+ // normal and solid styles are the same for analytic rect blurs, so don't
+ // need to handle solid specially.
+
+ return true;
+}
+
+// The "simple" blur is a direct implementation of separable convolution with a discrete
+// gaussian kernel. It's "ground truth" in a sense; too slow to be used, but very
+// useful for correctness comparisons.
+
+bool SkBlurMask::BlurGroundTruth(SkMask* dst, const SkMask& src, SkScalar provided_radius,
+ Style style, SkIPoint* margin) {
+
+ if (src.fFormat != SkMask::kA8_Format) {
+ return false;
+ }
+
+ float radius = SkScalarToFloat(SkScalarMul(provided_radius, kBlurRadiusFudgeFactor));
+ float stddev = SkScalarToFloat(radius) /2.0f;
+ float variance = stddev * stddev;
+
+ int windowSize = SkScalarCeil(stddev*4);
+ // round window size up to nearest odd number
+ windowSize |= 1;
+
+ SkAutoTMalloc<float> gaussWindow(windowSize);
+
+ int halfWindow = windowSize >> 1;
+
+ gaussWindow[halfWindow] = 1;
+
+ float windowSum = 1;
+ for (int x = 1 ; x <= halfWindow ; ++x) {
+ float gaussian = expf(-x*x / variance);
+ gaussWindow[halfWindow + x] = gaussWindow[halfWindow-x] = gaussian;
+ windowSum += 2*gaussian;
+ }
+
+ // leave the filter un-normalized for now; we will divide by the normalization
+ // sum later;
+
+ int pad = halfWindow;
+ if (margin) {
+ margin->set( pad, pad );
+ }
+
+ dst->fBounds = src.fBounds;
+ dst->fBounds.outset(pad, pad);
+
+ dst->fRowBytes = dst->fBounds.width();
+ dst->fFormat = SkMask::kA8_Format;
+ dst->fImage = NULL;
+
+ if (src.fImage) {
+
+ size_t dstSize = dst->computeImageSize();
+ if (0 == dstSize) {
+ return false; // too big to allocate, abort
+ }
+
+ int srcWidth = src.fBounds.width();
+ int srcHeight = src.fBounds.height();
+ int dstWidth = dst->fBounds.width();
+
+ const uint8_t* srcPixels = src.fImage;
+ uint8_t* dstPixels = SkMask::AllocImage(dstSize);
+ SkAutoTCallVProc<uint8_t, SkMask_FreeImage> autoCall(dstPixels);
+
+ // do the actual blur. First, make a padded copy of the source.
+ // use double pad so we never have to check if we're outside anything
+
+ int padWidth = srcWidth + 4*pad;
+ int padHeight = srcHeight;
+ int padSize = padWidth * padHeight;
+
+ SkAutoTMalloc<uint8_t> padPixels(padSize);
+ memset(padPixels, 0, padSize);
+
+ for (int y = 0 ; y < srcHeight; ++y) {
+ uint8_t* padptr = padPixels + y * padWidth + 2*pad;
+ const uint8_t* srcptr = srcPixels + y * srcWidth;
+ memcpy(padptr, srcptr, srcWidth);
+ }
+
+ // blur in X, transposing the result into a temporary floating point buffer.
+ // also double-pad the intermediate result so that the second blur doesn't
+ // have to do extra conditionals.
+
+ int tmpWidth = padHeight + 4*pad;
+ int tmpHeight = padWidth - 2*pad;
+ int tmpSize = tmpWidth * tmpHeight;
+
+ SkAutoTMalloc<float> tmpImage(tmpSize);
+ memset(tmpImage, 0, tmpSize*sizeof(tmpImage[0]));
+
+ for (int y = 0 ; y < padHeight ; ++y) {
+ uint8_t *srcScanline = padPixels + y*padWidth;
+ for (int x = pad ; x < padWidth - pad ; ++x) {
+ float *outPixel = tmpImage + (x-pad)*tmpWidth + y + 2*pad; // transposed output
+ uint8_t *windowCenter = srcScanline + x;
+ for (int i = -pad ; i <= pad ; ++i) {
+ *outPixel += gaussWindow[pad+i]*windowCenter[i];
+ }
+ *outPixel /= windowSum;
+ }
+ }
+
+ // blur in Y; now filling in the actual desired destination. We have to do
+ // the transpose again; these transposes guarantee that we read memory in
+ // linear order.
+
+ for (int y = 0 ; y < tmpHeight ; ++y) {
+ float *srcScanline = tmpImage + y*tmpWidth;
+ for (int x = pad ; x < tmpWidth - pad ; ++x) {
+ float *windowCenter = srcScanline + x;
+ float finalValue = 0;
+ for (int i = -pad ; i <= pad ; ++i) {
+ finalValue += gaussWindow[pad+i]*windowCenter[i];
+ }
+ finalValue /= windowSum;
+ uint8_t *outPixel = dstPixels + (x-pad)*dstWidth + y; // transposed output
+ int integerPixel = int(finalValue + 0.5f);
+ *outPixel = SkClampMax( SkClampPos(integerPixel), 255 );
+ }
+ }
+
+ dst->fImage = dstPixels;
+ // if need be, alloc the "real" dst (same size as src) and copy/merge
+ // the blur into it (applying the src)
+ if (style == kInner_Style) {
+ // now we allocate the "real" dst, mirror the size of src
+ size_t srcSize = src.computeImageSize();
+ if (0 == srcSize) {
+ return false; // too big to allocate, abort
+ }
+ dst->fImage = SkMask::AllocImage(srcSize);
+ merge_src_with_blur(dst->fImage, src.fRowBytes,
+ srcPixels, src.fRowBytes,
+ dstPixels + pad*dst->fRowBytes + pad,
+ dst->fRowBytes, srcWidth, srcHeight);
+ SkMask::FreeImage(dstPixels);
+ } else if (style != kNormal_Style) {
+ clamp_with_orig(dstPixels + pad*dst->fRowBytes + pad,
+ dst->fRowBytes, srcPixels, src.fRowBytes, srcWidth, srcHeight, style);
+ }
+ (void)autoCall.detach();
+ }
+
+ if (style == kInner_Style) {
+ dst->fBounds = src.fBounds; // restore trimmed bounds
+ dst->fRowBytes = src.fRowBytes;
+ }
+
+ return true;
+}
diff --git a/effects/SkBlurMask.h b/effects/SkBlurMask.h
new file mode 100644
index 00000000..36d78000
--- /dev/null
+++ b/effects/SkBlurMask.h
@@ -0,0 +1,55 @@
+
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#ifndef SkBlurMask_DEFINED
+#define SkBlurMask_DEFINED
+
+#include "SkShader.h"
+#include "SkMask.h"
+
+class SkBlurMask {
+public:
+ enum Style {
+ kNormal_Style, //!< fuzzy inside and outside
+ kSolid_Style, //!< solid inside, fuzzy outside
+ kOuter_Style, //!< nothing inside, fuzzy outside
+ kInner_Style, //!< fuzzy inside, nothing outside
+
+ kStyleCount
+ };
+
+ enum Quality {
+ kLow_Quality, //!< box blur
+ kHigh_Quality //!< three pass box blur (similar to gaussian)
+ };
+
+ static bool BlurRect(SkMask *dst, const SkRect &src,
+ SkScalar radius, Style style,
+ SkIPoint *margin = NULL,
+ SkMask::CreateMode createMode=SkMask::kComputeBoundsAndRenderImage_CreateMode);
+ static bool Blur(SkMask* dst, const SkMask& src,
+ SkScalar radius, Style style, Quality quality,
+ SkIPoint* margin = NULL);
+
+ // the "ground truth" blur does a gaussian convolution; it's slow
+ // but useful for comparison purposes.
+
+ static bool BlurGroundTruth(SkMask* dst, const SkMask& src,
+ SkScalar provided_radius, Style style,
+ SkIPoint* margin = NULL);
+
+ // scale factor for the blur radius to match the behavior of the all existing blur
+ // code (both on the CPU and the GPU). This magic constant is 1/sqrt(3).
+ // TODO: get rid of this fudge factor and move any required fudging up into
+ // the calling library
+ static const SkScalar kBlurRadiusFudgeFactor;
+
+};
+
+#endif
diff --git a/effects/SkBlurMaskFilter.cpp b/effects/SkBlurMaskFilter.cpp
new file mode 100644
index 00000000..b54c3300
--- /dev/null
+++ b/effects/SkBlurMaskFilter.cpp
@@ -0,0 +1,499 @@
+
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBlurMaskFilter.h"
+#include "SkBlurMask.h"
+#include "SkGpuBlurUtils.h"
+#include "SkFlattenableBuffers.h"
+#include "SkMaskFilter.h"
+#include "SkRTConf.h"
+#include "SkStringUtils.h"
+#include "SkStrokeRec.h"
+
+#if SK_SUPPORT_GPU
+#include "GrContext.h"
+#include "GrTexture.h"
+#include "effects/GrSimpleTextureEffect.h"
+#include "SkGrPixelRef.h"
+#endif
+
+class SkBlurMaskFilterImpl : public SkMaskFilter {
+public:
+ SkBlurMaskFilterImpl(SkScalar radius, SkBlurMaskFilter::BlurStyle,
+ uint32_t flags);
+
+ // overrides from SkMaskFilter
+ virtual SkMask::Format getFormat() const SK_OVERRIDE;
+ virtual bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix&,
+ SkIPoint* margin) const SK_OVERRIDE;
+
+#if SK_SUPPORT_GPU
+ virtual bool canFilterMaskGPU(const SkRect& devBounds,
+ const SkIRect& clipBounds,
+ const SkMatrix& ctm,
+ SkRect* maskRect) const SK_OVERRIDE;
+ virtual bool filterMaskGPU(GrTexture* src,
+ const SkRect& maskRect,
+ GrTexture** result,
+ bool canOverwriteSrc) const;
+#endif
+
+ virtual void computeFastBounds(const SkRect&, SkRect*) const SK_OVERRIDE;
+
+ SkDEVCODE(virtual void toString(SkString* str) const SK_OVERRIDE;)
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkBlurMaskFilterImpl)
+
+protected:
+ virtual FilterReturn filterRectsToNine(const SkRect[], int count, const SkMatrix&,
+ const SkIRect& clipBounds,
+ NinePatch*) const SK_OVERRIDE;
+
+ bool filterRectMask(SkMask* dstM, const SkRect& r, const SkMatrix& matrix,
+ SkIPoint* margin, SkMask::CreateMode createMode) const;
+
+private:
+ // To avoid unseemly allocation requests (esp. for finite platforms like
+ // handset) we limit the radius so something manageable. (as opposed to
+ // a request like 10,000)
+ static const SkScalar kMAX_BLUR_RADIUS;
+ // This constant approximates the scaling done in the software path's
+ // "high quality" mode, in SkBlurMask::Blur() (1 / sqrt(3)).
+ // IMHO, it actually should be 1: we blur "less" than we should do
+ // according to the CSS and canvas specs, simply because Safari does the same.
+ // Firefox used to do the same too, until 4.0 where they fixed it. So at some
+ // point we should probably get rid of these scaling constants and rebaseline
+ // all the blur tests.
+ static const SkScalar kBLUR_SIGMA_SCALE;
+
+ SkScalar fRadius;
+ SkBlurMaskFilter::BlurStyle fBlurStyle;
+ uint32_t fBlurFlags;
+
+ SkBlurMaskFilterImpl(SkFlattenableReadBuffer&);
+ virtual void flatten(SkFlattenableWriteBuffer&) const SK_OVERRIDE;
+#if SK_SUPPORT_GPU
+ SkScalar computeXformedRadius(const SkMatrix& ctm) const {
+ bool ignoreTransform = SkToBool(fBlurFlags & SkBlurMaskFilter::kIgnoreTransform_BlurFlag);
+
+ SkScalar xformedRadius = ignoreTransform ? fRadius
+ : ctm.mapRadius(fRadius);
+ return SkMinScalar(xformedRadius, kMAX_BLUR_RADIUS);
+ }
+#endif
+
+ typedef SkMaskFilter INHERITED;
+};
+
+const SkScalar SkBlurMaskFilterImpl::kMAX_BLUR_RADIUS = SkIntToScalar(128);
+const SkScalar SkBlurMaskFilterImpl::kBLUR_SIGMA_SCALE = SkFloatToScalar(0.6f);
+
+SkMaskFilter* SkBlurMaskFilter::Create(SkScalar radius,
+ SkBlurMaskFilter::BlurStyle style,
+ uint32_t flags) {
+ // use !(radius > 0) instead of radius <= 0 to reject NaN values
+ if (!(radius > 0) || (unsigned)style >= SkBlurMaskFilter::kBlurStyleCount
+ || flags > SkBlurMaskFilter::kAll_BlurFlag) {
+ return NULL;
+ }
+
+ return SkNEW_ARGS(SkBlurMaskFilterImpl, (radius, style, flags));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkBlurMaskFilterImpl::SkBlurMaskFilterImpl(SkScalar radius,
+ SkBlurMaskFilter::BlurStyle style,
+ uint32_t flags)
+ : fRadius(radius), fBlurStyle(style), fBlurFlags(flags) {
+#if 0
+ fGamma = NULL;
+ if (gammaScale) {
+ fGamma = new U8[256];
+ if (gammaScale > 0)
+ SkBlurMask::BuildSqrGamma(fGamma, gammaScale);
+ else
+ SkBlurMask::BuildSqrtGamma(fGamma, -gammaScale);
+ }
+#endif
+ SkASSERT(radius >= 0);
+ SkASSERT((unsigned)style < SkBlurMaskFilter::kBlurStyleCount);
+ SkASSERT(flags <= SkBlurMaskFilter::kAll_BlurFlag);
+}
+
+SkMask::Format SkBlurMaskFilterImpl::getFormat() const {
+ return SkMask::kA8_Format;
+}
+
+bool SkBlurMaskFilterImpl::filterMask(SkMask* dst, const SkMask& src,
+ const SkMatrix& matrix,
+ SkIPoint* margin) const{
+ SkScalar radius;
+ if (fBlurFlags & SkBlurMaskFilter::kIgnoreTransform_BlurFlag) {
+ radius = fRadius;
+ } else {
+ radius = matrix.mapRadius(fRadius);
+ }
+
+ radius = SkMinScalar(radius, kMAX_BLUR_RADIUS);
+ SkBlurMask::Quality blurQuality =
+ (fBlurFlags & SkBlurMaskFilter::kHighQuality_BlurFlag) ?
+ SkBlurMask::kHigh_Quality : SkBlurMask::kLow_Quality;
+
+ return SkBlurMask::Blur(dst, src, radius, (SkBlurMask::Style)fBlurStyle,
+ blurQuality, margin);
+}
+
+bool SkBlurMaskFilterImpl::filterRectMask(SkMask* dst, const SkRect& r,
+ const SkMatrix& matrix,
+ SkIPoint* margin, SkMask::CreateMode createMode) const{
+ SkScalar radius;
+ if (fBlurFlags & SkBlurMaskFilter::kIgnoreTransform_BlurFlag) {
+ radius = fRadius;
+ } else {
+ radius = matrix.mapRadius(fRadius);
+ }
+
+ radius = SkMinScalar(radius, kMAX_BLUR_RADIUS);
+
+ return SkBlurMask::BlurRect(dst, r, radius, (SkBlurMask::Style)fBlurStyle,
+ margin, createMode);
+}
+
+#include "SkCanvas.h"
+
+static bool drawRectsIntoMask(const SkRect rects[], int count, SkMask* mask) {
+ rects[0].roundOut(&mask->fBounds);
+ mask->fRowBytes = SkAlign4(mask->fBounds.width());
+ mask->fFormat = SkMask::kA8_Format;
+ size_t size = mask->computeImageSize();
+ mask->fImage = SkMask::AllocImage(size);
+ if (NULL == mask->fImage) {
+ return false;
+ }
+ sk_bzero(mask->fImage, size);
+
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kA8_Config,
+ mask->fBounds.width(), mask->fBounds.height(),
+ mask->fRowBytes);
+ bitmap.setPixels(mask->fImage);
+
+ SkCanvas canvas(bitmap);
+ canvas.translate(-SkIntToScalar(mask->fBounds.left()),
+ -SkIntToScalar(mask->fBounds.top()));
+
+ SkPaint paint;
+ paint.setAntiAlias(true);
+
+ if (1 == count) {
+ canvas.drawRect(rects[0], paint);
+ } else {
+ // todo: do I need a fast way to do this?
+ SkPath path;
+ path.addRect(rects[0]);
+ path.addRect(rects[1]);
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ canvas.drawPath(path, paint);
+ }
+ return true;
+}
+
+static bool rect_exceeds(const SkRect& r, SkScalar v) {
+ return r.fLeft < -v || r.fTop < -v || r.fRight > v || r.fBottom > v ||
+ r.width() > v || r.height() > v;
+}
+
+#ifdef SK_IGNORE_FAST_RECT_BLUR
+SK_CONF_DECLARE( bool, c_analyticBlurNinepatch, "mask.filter.analyticNinePatch", false, "Use the faster analytic blur approach for ninepatch rects" );
+#else
+SK_CONF_DECLARE( bool, c_analyticBlurNinepatch, "mask.filter.analyticNinePatch", true, "Use the faster analytic blur approach for ninepatch rects" );
+#endif
+
+SkMaskFilter::FilterReturn
+SkBlurMaskFilterImpl::filterRectsToNine(const SkRect rects[], int count,
+ const SkMatrix& matrix,
+ const SkIRect& clipBounds,
+ NinePatch* patch) const {
+ if (count < 1 || count > 2) {
+ return kUnimplemented_FilterReturn;
+ }
+
+ // TODO: report correct metrics for innerstyle, where we do not grow the
+ // total bounds, but we do need an inset the size of our blur-radius
+ if (SkBlurMaskFilter::kInner_BlurStyle == fBlurStyle) {
+ return kUnimplemented_FilterReturn;
+ }
+
+ // TODO: take clipBounds into account to limit our coordinates up front
+ // for now, just skip too-large src rects (to take the old code path).
+ if (rect_exceeds(rects[0], SkIntToScalar(32767))) {
+ return kUnimplemented_FilterReturn;
+ }
+
+ SkIPoint margin;
+ SkMask srcM, dstM;
+ rects[0].roundOut(&srcM.fBounds);
+ srcM.fImage = NULL;
+ srcM.fFormat = SkMask::kA8_Format;
+ srcM.fRowBytes = 0;
+
+ bool filterResult = false;
+ if (count == 1 && c_analyticBlurNinepatch) {
+ // special case for fast rect blur
+ // don't actually do the blur the first time, just compute the correct size
+ filterResult = this->filterRectMask(&dstM, rects[0], matrix, &margin,
+ SkMask::kJustComputeBounds_CreateMode);
+ } else {
+ filterResult = this->filterMask(&dstM, srcM, matrix, &margin);
+ }
+
+ if (!filterResult) {
+ return kFalse_FilterReturn;
+ }
+
+ /*
+ * smallR is the smallest version of 'rect' that will still guarantee that
+ * we get the same blur results on all edges, plus 1 center row/col that is
+ * representative of the extendible/stretchable edges of the ninepatch.
+ * Since our actual edge may be fractional we inset 1 more to be sure we
+ * don't miss any interior blur.
+ * x is an added pixel of blur, and { and } are the (fractional) edge
+ * pixels from the original rect.
+ *
+ * x x { x x .... x x } x x
+ *
+ * Thus, in this case, we inset by a total of 5 (on each side) beginning
+ * with our outer-rect (dstM.fBounds)
+ */
+ SkRect smallR[2];
+ SkIPoint center;
+
+ // +2 is from +1 for each edge (to account for possible fractional edges
+ int smallW = dstM.fBounds.width() - srcM.fBounds.width() + 2;
+ int smallH = dstM.fBounds.height() - srcM.fBounds.height() + 2;
+ SkIRect innerIR;
+
+ if (1 == count) {
+ innerIR = srcM.fBounds;
+ center.set(smallW, smallH);
+ } else {
+ SkASSERT(2 == count);
+ rects[1].roundIn(&innerIR);
+ center.set(smallW + (innerIR.left() - srcM.fBounds.left()),
+ smallH + (innerIR.top() - srcM.fBounds.top()));
+ }
+
+ // +1 so we get a clean, stretchable, center row/col
+ smallW += 1;
+ smallH += 1;
+
+ // we want the inset amounts to be integral, so we don't change any
+ // fractional phase on the fRight or fBottom of our smallR.
+ const SkScalar dx = SkIntToScalar(innerIR.width() - smallW);
+ const SkScalar dy = SkIntToScalar(innerIR.height() - smallH);
+ if (dx < 0 || dy < 0) {
+ // we're too small, relative to our blur, to break into nine-patch,
+ // so we ask to have our normal filterMask() be called.
+ return kUnimplemented_FilterReturn;
+ }
+
+ smallR[0].set(rects[0].left(), rects[0].top(), rects[0].right() - dx, rects[0].bottom() - dy);
+ SkASSERT(!smallR[0].isEmpty());
+ if (2 == count) {
+ smallR[1].set(rects[1].left(), rects[1].top(),
+ rects[1].right() - dx, rects[1].bottom() - dy);
+ SkASSERT(!smallR[1].isEmpty());
+ }
+
+ if (count > 1 || !c_analyticBlurNinepatch) {
+ if (!drawRectsIntoMask(smallR, count, &srcM)) {
+ return kFalse_FilterReturn;
+ }
+
+ SkAutoMaskFreeImage amf(srcM.fImage);
+
+ if (!this->filterMask(&patch->fMask, srcM, matrix, &margin)) {
+ return kFalse_FilterReturn;
+ }
+ } else {
+ if (!this->filterRectMask(&patch->fMask, smallR[0], matrix, &margin,
+ SkMask::kComputeBoundsAndRenderImage_CreateMode)) {
+ return kFalse_FilterReturn;
+ }
+ }
+ patch->fMask.fBounds.offsetTo(0, 0);
+ patch->fOuterRect = dstM.fBounds;
+ patch->fCenter = center;
+ return kTrue_FilterReturn;
+}
+
+void SkBlurMaskFilterImpl::computeFastBounds(const SkRect& src,
+ SkRect* dst) const {
+ SkScalar gpuPad, rasterPad;
+
+ {
+ // GPU path
+ SkScalar sigma = SkScalarMul(fRadius, kBLUR_SIGMA_SCALE);
+ gpuPad = sigma * 3.0f;
+ }
+
+ {
+ // raster path
+ SkScalar radius = SkScalarMul(fRadius, SkBlurMask::kBlurRadiusFudgeFactor);
+
+ radius = (radius + .5f) * 2.f;
+
+ rasterPad = SkIntToScalar(SkScalarRoundToInt(radius * 3)/2);
+ }
+
+ SkScalar pad = SkMaxScalar(gpuPad, rasterPad);
+
+ dst->set(src.fLeft - pad, src.fTop - pad,
+ src.fRight + pad, src.fBottom + pad);
+}
+
+SkBlurMaskFilterImpl::SkBlurMaskFilterImpl(SkFlattenableReadBuffer& buffer)
+ : SkMaskFilter(buffer) {
+ fRadius = buffer.readScalar();
+ fBlurStyle = (SkBlurMaskFilter::BlurStyle)buffer.readInt();
+ fBlurFlags = buffer.readUInt() & SkBlurMaskFilter::kAll_BlurFlag;
+ SkASSERT(fRadius >= 0);
+ SkASSERT((unsigned)fBlurStyle < SkBlurMaskFilter::kBlurStyleCount);
+}
+
+void SkBlurMaskFilterImpl::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeScalar(fRadius);
+ buffer.writeInt(fBlurStyle);
+ buffer.writeUInt(fBlurFlags);
+}
+
+#if SK_SUPPORT_GPU
+
+bool SkBlurMaskFilterImpl::canFilterMaskGPU(const SkRect& srcBounds,
+ const SkIRect& clipBounds,
+ const SkMatrix& ctm,
+ SkRect* maskRect) const {
+ SkScalar xformedRadius = this->computeXformedRadius(ctm);
+ if (xformedRadius <= 0) {
+ return false;
+ }
+
+ static const SkScalar kMIN_GPU_BLUR_SIZE = SkIntToScalar(64);
+ static const SkScalar kMIN_GPU_BLUR_RADIUS = SkIntToScalar(32);
+
+ if (srcBounds.width() <= kMIN_GPU_BLUR_SIZE &&
+ srcBounds.height() <= kMIN_GPU_BLUR_SIZE &&
+ xformedRadius <= kMIN_GPU_BLUR_RADIUS) {
+ // We prefer to blur small rect with small radius via CPU.
+ return false;
+ }
+
+ if (NULL == maskRect) {
+ // don't need to compute maskRect
+ return true;
+ }
+
+ float sigma3 = 3 * SkScalarToFloat(xformedRadius) * kBLUR_SIGMA_SCALE;
+
+ SkRect clipRect = SkRect::MakeFromIRect(clipBounds);
+ SkRect srcRect(srcBounds);
+
+ // Outset srcRect and clipRect by 3 * sigma, to compute affected blur area.
+ srcRect.outset(SkFloatToScalar(sigma3), SkFloatToScalar(sigma3));
+ clipRect.outset(SkFloatToScalar(sigma3), SkFloatToScalar(sigma3));
+ srcRect.intersect(clipRect);
+ *maskRect = srcRect;
+ return true;
+}
+
+bool SkBlurMaskFilterImpl::filterMaskGPU(GrTexture* src,
+ const SkRect& maskRect,
+ GrTexture** result,
+ bool canOverwriteSrc) const {
+ SkRect clipRect = SkRect::MakeWH(maskRect.width(), maskRect.height());
+
+ GrContext* context = src->getContext();
+
+ GrContext::AutoWideOpenIdentityDraw awo(context, NULL);
+
+ SkScalar xformedRadius = this->computeXformedRadius(context->getMatrix());
+ SkASSERT(xformedRadius > 0);
+
+ float sigma = SkScalarToFloat(xformedRadius) * kBLUR_SIGMA_SCALE;
+
+ // If we're doing a normal blur, we can clobber the pathTexture in the
+ // gaussianBlur. Otherwise, we need to save it for later compositing.
+ bool isNormalBlur = (SkBlurMaskFilter::kNormal_BlurStyle == fBlurStyle);
+ *result = SkGpuBlurUtils::GaussianBlur(context, src, isNormalBlur && canOverwriteSrc,
+ clipRect, false, sigma, sigma);
+ if (NULL == *result) {
+ return false;
+ }
+
+ if (!isNormalBlur) {
+ context->setIdentityMatrix();
+ GrPaint paint;
+ SkMatrix matrix;
+ matrix.setIDiv(src->width(), src->height());
+ // Blend pathTexture over blurTexture.
+ GrContext::AutoRenderTarget art(context, (*result)->asRenderTarget());
+ paint.addColorEffect(GrSimpleTextureEffect::Create(src, matrix))->unref();
+ if (SkBlurMaskFilter::kInner_BlurStyle == fBlurStyle) {
+ // inner: dst = dst * src
+ paint.setBlendFunc(kDC_GrBlendCoeff, kZero_GrBlendCoeff);
+ } else if (SkBlurMaskFilter::kSolid_BlurStyle == fBlurStyle) {
+ // solid: dst = src + dst - src * dst
+ // = (1 - dst) * src + 1 * dst
+ paint.setBlendFunc(kIDC_GrBlendCoeff, kOne_GrBlendCoeff);
+ } else if (SkBlurMaskFilter::kOuter_BlurStyle == fBlurStyle) {
+ // outer: dst = dst * (1 - src)
+ // = 0 * src + (1 - src) * dst
+ paint.setBlendFunc(kZero_GrBlendCoeff, kISC_GrBlendCoeff);
+ }
+ context->drawRect(paint, clipRect);
+ }
+
+ return true;
+}
+
+#endif // SK_SUPPORT_GPU
+
+
+#ifdef SK_DEVELOPER
+void SkBlurMaskFilterImpl::toString(SkString* str) const {
+ str->append("SkBlurMaskFilterImpl: (");
+
+ str->append("radius: ");
+ str->appendScalar(fRadius);
+ str->append(" ");
+
+ static const char* gStyleName[SkBlurMaskFilter::kBlurStyleCount] = {
+ "normal", "solid", "outer", "inner"
+ };
+
+ str->appendf("style: %s ", gStyleName[fBlurStyle]);
+ str->append("flags: (");
+ if (fBlurFlags) {
+ bool needSeparator = false;
+ SkAddFlagToString(str,
+ SkToBool(fBlurFlags & SkBlurMaskFilter::kIgnoreTransform_BlurFlag),
+ "IgnoreXform", &needSeparator);
+ SkAddFlagToString(str,
+ SkToBool(fBlurFlags & SkBlurMaskFilter::kHighQuality_BlurFlag),
+ "HighQuality", &needSeparator);
+ } else {
+ str->append("None");
+ }
+ str->append("))");
+}
+#endif
+
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkBlurMaskFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBlurMaskFilterImpl)
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
diff --git a/effects/SkColorFilterImageFilter.cpp b/effects/SkColorFilterImageFilter.cpp
new file mode 100755
index 00000000..9c2c54eb
--- /dev/null
+++ b/effects/SkColorFilterImageFilter.cpp
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkColorFilterImageFilter.h"
+#include "SkBitmap.h"
+#include "SkCanvas.h"
+#include "SkColorMatrixFilter.h"
+#include "SkDevice.h"
+#include "SkColorFilter.h"
+#include "SkFlattenableBuffers.h"
+
+namespace {
+
+void mult_color_matrix(SkScalar a[20], SkScalar b[20], SkScalar out[20]) {
+ for (int j = 0; j < 4; ++j) {
+ for (int i = 0; i < 5; ++i) {
+ out[i+j*5] = 4 == i ? a[4+j*5] : 0;
+ for (int k = 0; k < 4; ++k)
+ out[i+j*5] += SkScalarMul(a[k+j*5], b[i+k*5]);
+ }
+ }
+}
+
+// To detect if we need to apply clamping after applying a matrix, we check if
+// any output component might go outside of [0, 255] for any combination of
+// input components in [0..255].
+// Each output component is an affine transformation of the input component, so
+// the minimum and maximum values are for any combination of minimum or maximum
+// values of input components (i.e. 0 or 255).
+// E.g. if R' = x*R + y*G + z*B + w*A + t
+// Then the maximum value will be for R=255 if x>0 or R=0 if x<0, and the
+// minimum value will be for R=0 if x>0 or R=255 if x<0.
+// Same goes for all components.
+bool component_needs_clamping(SkScalar row[5]) {
+ SkScalar maxValue = row[4] / 255;
+ SkScalar minValue = row[4] / 255;
+ for (int i = 0; i < 4; ++i) {
+ if (row[i] > 0)
+ maxValue += row[i];
+ else
+ minValue += row[i];
+ }
+ return (maxValue > 1) || (minValue < 0);
+}
+
+bool matrix_needs_clamping(SkScalar matrix[20]) {
+ return component_needs_clamping(matrix)
+ || component_needs_clamping(matrix+5)
+ || component_needs_clamping(matrix+10)
+ || component_needs_clamping(matrix+15);
+}
+
+};
+
+SkColorFilterImageFilter* SkColorFilterImageFilter::Create(SkColorFilter* cf,
+ SkImageFilter* input, const SkIRect* cropRect) {
+ SkASSERT(cf);
+ SkScalar colorMatrix[20], inputMatrix[20];
+ SkColorFilter* inputColorFilter;
+ if (input && cf->asColorMatrix(colorMatrix)
+ && input->asColorFilter(&inputColorFilter)
+ && (NULL != inputColorFilter)) {
+ SkAutoUnref autoUnref(inputColorFilter);
+ if (inputColorFilter->asColorMatrix(inputMatrix) && !matrix_needs_clamping(inputMatrix)) {
+ SkScalar combinedMatrix[20];
+ mult_color_matrix(inputMatrix, colorMatrix, combinedMatrix);
+ SkAutoTUnref<SkColorFilter> newCF(SkNEW_ARGS(SkColorMatrixFilter, (combinedMatrix)));
+ return SkNEW_ARGS(SkColorFilterImageFilter, (newCF, input->getInput(0), cropRect));
+ }
+ }
+ return SkNEW_ARGS(SkColorFilterImageFilter, (cf, input, cropRect));
+}
+
+SkColorFilterImageFilter::SkColorFilterImageFilter(SkColorFilter* cf,
+ SkImageFilter* input, const SkIRect* cropRect)
+ : INHERITED(input, cropRect), fColorFilter(cf) {
+ SkASSERT(cf);
+ SkSafeRef(cf);
+}
+
+SkColorFilterImageFilter::SkColorFilterImageFilter(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ fColorFilter = buffer.readFlattenableT<SkColorFilter>();
+}
+
+void SkColorFilterImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+
+ buffer.writeFlattenable(fColorFilter);
+}
+
+SkColorFilterImageFilter::~SkColorFilterImageFilter() {
+ SkSafeUnref(fColorFilter);
+}
+
+bool SkColorFilterImageFilter::onFilterImage(Proxy* proxy, const SkBitmap& source,
+ const SkMatrix& matrix,
+ SkBitmap* result,
+ SkIPoint* loc) {
+ SkBitmap src = source;
+ if (getInput(0) && !getInput(0)->filterImage(proxy, source, matrix, &src, loc)) {
+ return false;
+ }
+
+ SkIRect bounds;
+ src.getBounds(&bounds);
+ if (!this->applyCropRect(&bounds)) {
+ return false;
+ }
+
+ SkAutoTUnref<SkDevice> device(proxy->createDevice(bounds.width(), bounds.height()));
+ SkCanvas canvas(device.get());
+ SkPaint paint;
+
+ paint.setXfermodeMode(SkXfermode::kSrc_Mode);
+ paint.setColorFilter(fColorFilter);
+ canvas.drawSprite(src, -bounds.fLeft, -bounds.fTop, &paint);
+
+ *result = device.get()->accessBitmap(false);
+ loc->fX += bounds.fLeft;
+ loc->fY += bounds.fTop;
+ return true;
+}
+
+bool SkColorFilterImageFilter::asColorFilter(SkColorFilter** filter) const {
+ if (cropRect().isLargest()) {
+ if (filter) {
+ *filter = fColorFilter;
+ fColorFilter->ref();
+ }
+ return true;
+ }
+ return false;
+}
diff --git a/effects/SkColorFilters.cpp b/effects/SkColorFilters.cpp
new file mode 100644
index 00000000..41a201ef
--- /dev/null
+++ b/effects/SkColorFilters.cpp
@@ -0,0 +1,547 @@
+
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#include "SkBlitRow.h"
+#include "SkColorFilter.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkUtils.h"
+#include "SkString.h"
+
+#define ILLEGAL_XFERMODE_MODE ((SkXfermode::Mode)-1)
+
+// baseclass for filters that store a color and mode
+class SkModeColorFilter : public SkColorFilter {
+public:
+ SkModeColorFilter(SkColor color) {
+ fColor = color;
+ fMode = ILLEGAL_XFERMODE_MODE;
+ this->updateCache();
+ }
+
+ SkModeColorFilter(SkColor color, SkXfermode::Mode mode) {
+ fColor = color;
+ fMode = mode;
+ this->updateCache();
+ };
+
+ SkColor getColor() const { return fColor; }
+ SkXfermode::Mode getMode() const { return fMode; }
+ bool isModeValid() const { return ILLEGAL_XFERMODE_MODE != fMode; }
+ SkPMColor getPMColor() const { return fPMColor; }
+
+ virtual bool asColorMode(SkColor* color, SkXfermode::Mode* mode) const SK_OVERRIDE {
+ if (ILLEGAL_XFERMODE_MODE == fMode) {
+ return false;
+ }
+
+ if (color) {
+ *color = fColor;
+ }
+ if (mode) {
+ *mode = fMode;
+ }
+ return true;
+ }
+
+ virtual uint32_t getFlags() const SK_OVERRIDE {
+ return fProc16 ? (kAlphaUnchanged_Flag | kHasFilter16_Flag) : 0;
+ }
+
+ virtual void filterSpan(const SkPMColor shader[], int count,
+ SkPMColor result[]) const SK_OVERRIDE {
+ SkPMColor color = fPMColor;
+ SkXfermodeProc proc = fProc;
+
+ for (int i = 0; i < count; i++) {
+ result[i] = proc(color, shader[i]);
+ }
+ }
+
+ virtual void filterSpan16(const uint16_t shader[], int count,
+ uint16_t result[]) const SK_OVERRIDE {
+ SkASSERT(this->getFlags() & kHasFilter16_Flag);
+
+ SkPMColor color = fPMColor;
+ SkXfermodeProc16 proc16 = fProc16;
+
+ for (int i = 0; i < count; i++) {
+ result[i] = proc16(color, shader[i]);
+ }
+ }
+
+#ifdef SK_DEVELOPER
+ virtual void toString(SkString* str) const SK_OVERRIDE {
+ str->append("SkModeColorFilter: color: 0x");
+ str->appendHex(fColor);
+ str->append(" mode: ");
+ str->append(SkXfermode::ModeName(fMode));
+ }
+#endif
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkModeColorFilter)
+
+protected:
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
+ this->INHERITED::flatten(buffer);
+ buffer.writeColor(fColor);
+ buffer.writeUInt(fMode);
+ }
+
+ SkModeColorFilter(SkFlattenableReadBuffer& buffer) {
+ fColor = buffer.readColor();
+ fMode = (SkXfermode::Mode)buffer.readUInt();
+ this->updateCache();
+ }
+
+private:
+ SkColor fColor;
+ SkXfermode::Mode fMode;
+ // cache
+ SkPMColor fPMColor;
+ SkXfermodeProc fProc;
+ SkXfermodeProc16 fProc16;
+
+ void updateCache() {
+ fPMColor = SkPreMultiplyColor(fColor);
+ fProc = SkXfermode::GetProc(fMode);
+ fProc16 = SkXfermode::GetProc16(fMode, fColor);
+ }
+
+ typedef SkColorFilter INHERITED;
+};
+
+class Src_SkModeColorFilter : public SkModeColorFilter {
+public:
+ Src_SkModeColorFilter(SkColor color) : INHERITED(color, SkXfermode::kSrc_Mode) {}
+
+ virtual uint32_t getFlags() const SK_OVERRIDE {
+ if (SkGetPackedA32(this->getPMColor()) == 0xFF) {
+ return kAlphaUnchanged_Flag | kHasFilter16_Flag;
+ } else {
+ return 0;
+ }
+ }
+
+ virtual void filterSpan(const SkPMColor shader[], int count,
+ SkPMColor result[]) const SK_OVERRIDE {
+ sk_memset32(result, this->getPMColor(), count);
+ }
+
+ virtual void filterSpan16(const uint16_t shader[], int count,
+ uint16_t result[]) const SK_OVERRIDE {
+ SkASSERT(this->getFlags() & kHasFilter16_Flag);
+ sk_memset16(result, SkPixel32ToPixel16(this->getPMColor()), count);
+ }
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(Src_SkModeColorFilter)
+
+protected:
+ Src_SkModeColorFilter(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {}
+
+private:
+ typedef SkModeColorFilter INHERITED;
+};
+
+class SrcOver_SkModeColorFilter : public SkModeColorFilter {
+public:
+ SrcOver_SkModeColorFilter(SkColor color)
+ : INHERITED(color, SkXfermode::kSrcOver_Mode) {
+ fColor32Proc = SkBlitRow::ColorProcFactory();
+ }
+
+ virtual uint32_t getFlags() const SK_OVERRIDE {
+ if (SkGetPackedA32(this->getPMColor()) == 0xFF) {
+ return kAlphaUnchanged_Flag | kHasFilter16_Flag;
+ } else {
+ return 0;
+ }
+ }
+
+ virtual void filterSpan(const SkPMColor shader[], int count,
+ SkPMColor result[]) const SK_OVERRIDE {
+ fColor32Proc(result, shader, count, this->getPMColor());
+ }
+
+ virtual void filterSpan16(const uint16_t shader[], int count,
+ uint16_t result[]) const SK_OVERRIDE {
+ SkASSERT(this->getFlags() & kHasFilter16_Flag);
+ sk_memset16(result, SkPixel32ToPixel16(this->getPMColor()), count);
+ }
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SrcOver_SkModeColorFilter)
+
+protected:
+ SrcOver_SkModeColorFilter(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {
+ fColor32Proc = SkBlitRow::ColorProcFactory();
+ }
+
+private:
+
+ SkBlitRow::ColorProc fColor32Proc;
+
+ typedef SkModeColorFilter INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkColorFilter* SkColorFilter::CreateModeFilter(SkColor color,
+ SkXfermode::Mode mode) {
+ unsigned alpha = SkColorGetA(color);
+
+ // first collaps some modes if possible
+
+ if (SkXfermode::kClear_Mode == mode) {
+ color = 0;
+ mode = SkXfermode::kSrc_Mode;
+ } else if (SkXfermode::kSrcOver_Mode == mode) {
+ if (0 == alpha) {
+ mode = SkXfermode::kDst_Mode;
+ } else if (255 == alpha) {
+ mode = SkXfermode::kSrc_Mode;
+ }
+ // else just stay srcover
+ }
+
+ // weed out combinations that are noops, and just return null
+ if (SkXfermode::kDst_Mode == mode ||
+ (0 == alpha && (SkXfermode::kSrcOver_Mode == mode ||
+ SkXfermode::kDstOver_Mode == mode ||
+ SkXfermode::kDstOut_Mode == mode ||
+ SkXfermode::kSrcATop_Mode == mode ||
+ SkXfermode::kXor_Mode == mode ||
+ SkXfermode::kDarken_Mode == mode)) ||
+ (0xFF == alpha && SkXfermode::kDstIn_Mode == mode)) {
+ return NULL;
+ }
+
+ switch (mode) {
+ case SkXfermode::kSrc_Mode:
+ return SkNEW_ARGS(Src_SkModeColorFilter, (color));
+ case SkXfermode::kSrcOver_Mode:
+ return SkNEW_ARGS(SrcOver_SkModeColorFilter, (color));
+ default:
+ return SkNEW_ARGS(SkModeColorFilter, (color, mode));
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static inline unsigned pin(unsigned value, unsigned max) {
+ if (value > max) {
+ value = max;
+ }
+ return value;
+}
+
+class SkLightingColorFilter : public SkColorFilter {
+public:
+ SkLightingColorFilter(SkColor mul, SkColor add) : fMul(mul), fAdd(add) {}
+
+ virtual void filterSpan(const SkPMColor shader[], int count,
+ SkPMColor result[]) const SK_OVERRIDE {
+ unsigned scaleR = SkAlpha255To256(SkColorGetR(fMul));
+ unsigned scaleG = SkAlpha255To256(SkColorGetG(fMul));
+ unsigned scaleB = SkAlpha255To256(SkColorGetB(fMul));
+
+ unsigned addR = SkColorGetR(fAdd);
+ unsigned addG = SkColorGetG(fAdd);
+ unsigned addB = SkColorGetB(fAdd);
+
+ for (int i = 0; i < count; i++) {
+ SkPMColor c = shader[i];
+ if (c) {
+ unsigned a = SkGetPackedA32(c);
+ unsigned scaleA = SkAlpha255To256(a);
+ unsigned r = pin(SkAlphaMul(SkGetPackedR32(c), scaleR) + SkAlphaMul(addR, scaleA), a);
+ unsigned g = pin(SkAlphaMul(SkGetPackedG32(c), scaleG) + SkAlphaMul(addG, scaleA), a);
+ unsigned b = pin(SkAlphaMul(SkGetPackedB32(c), scaleB) + SkAlphaMul(addB, scaleA), a);
+ c = SkPackARGB32(a, r, g, b);
+ }
+ result[i] = c;
+ }
+ }
+
+#ifdef SK_DEVELOPER
+ virtual void toString(SkString* str) const SK_OVERRIDE {
+ str->append("SkLightingColorFilter: mul: 0x");
+ str->appendHex(fMul);
+ str->append(" add: 0x");
+ str->appendHex(fAdd);
+ }
+#endif
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLightingColorFilter)
+
+protected:
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
+ this->INHERITED::flatten(buffer);
+ buffer.writeColor(fMul);
+ buffer.writeColor(fAdd);
+ }
+
+ SkLightingColorFilter(SkFlattenableReadBuffer& buffer) {
+ fMul = buffer.readColor();
+ fAdd = buffer.readColor();
+ }
+
+ SkColor fMul, fAdd;
+
+private:
+ typedef SkColorFilter INHERITED;
+};
+
+class SkLightingColorFilter_JustAdd : public SkLightingColorFilter {
+public:
+ SkLightingColorFilter_JustAdd(SkColor mul, SkColor add)
+ : INHERITED(mul, add) {}
+
+ virtual void filterSpan(const SkPMColor shader[], int count,
+ SkPMColor result[]) const SK_OVERRIDE {
+ unsigned addR = SkColorGetR(fAdd);
+ unsigned addG = SkColorGetG(fAdd);
+ unsigned addB = SkColorGetB(fAdd);
+
+ for (int i = 0; i < count; i++) {
+ SkPMColor c = shader[i];
+ if (c) {
+ unsigned a = SkGetPackedA32(c);
+ unsigned scaleA = SkAlpha255To256(a);
+ unsigned r = pin(SkGetPackedR32(c) + SkAlphaMul(addR, scaleA), a);
+ unsigned g = pin(SkGetPackedG32(c) + SkAlphaMul(addG, scaleA), a);
+ unsigned b = pin(SkGetPackedB32(c) + SkAlphaMul(addB, scaleA), a);
+ c = SkPackARGB32(a, r, g, b);
+ }
+ result[i] = c;
+ }
+ }
+
+#ifdef SK_DEVELOPER
+ virtual void toString(SkString* str) const SK_OVERRIDE {
+ str->append("SkLightingColorFilter_JustAdd: add: 0x");
+ str->appendHex(fAdd);
+ }
+#endif
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLightingColorFilter_JustAdd)
+
+protected:
+ SkLightingColorFilter_JustAdd(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {}
+
+private:
+ typedef SkLightingColorFilter INHERITED;
+};
+
+class SkLightingColorFilter_JustMul : public SkLightingColorFilter {
+public:
+ SkLightingColorFilter_JustMul(SkColor mul, SkColor add)
+ : INHERITED(mul, add) {}
+
+ virtual void filterSpan(const SkPMColor shader[], int count,
+ SkPMColor result[]) const SK_OVERRIDE {
+ unsigned scaleR = SkAlpha255To256(SkColorGetR(fMul));
+ unsigned scaleG = SkAlpha255To256(SkColorGetG(fMul));
+ unsigned scaleB = SkAlpha255To256(SkColorGetB(fMul));
+
+ for (int i = 0; i < count; i++) {
+ SkPMColor c = shader[i];
+ if (c) {
+ unsigned a = SkGetPackedA32(c);
+ unsigned r = SkAlphaMul(SkGetPackedR32(c), scaleR);
+ unsigned g = SkAlphaMul(SkGetPackedG32(c), scaleG);
+ unsigned b = SkAlphaMul(SkGetPackedB32(c), scaleB);
+ c = SkPackARGB32(a, r, g, b);
+ }
+ result[i] = c;
+ }
+ }
+
+#ifdef SK_DEVELOPER
+ virtual void toString(SkString* str) const SK_OVERRIDE {
+ str->append("SkLightingColorFilter_JustMul: mul: 0x");
+ str->appendHex(fMul);
+ }
+#endif
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLightingColorFilter_JustMul)
+
+protected:
+ SkLightingColorFilter_JustMul(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {}
+
+private:
+ typedef SkLightingColorFilter INHERITED;
+};
+
+class SkLightingColorFilter_SingleMul : public SkLightingColorFilter {
+public:
+ SkLightingColorFilter_SingleMul(SkColor mul, SkColor add)
+ : INHERITED(mul, add) {
+ SkASSERT(SkColorGetR(add) == 0);
+ SkASSERT(SkColorGetG(add) == 0);
+ SkASSERT(SkColorGetB(add) == 0);
+ SkASSERT(SkColorGetR(mul) == SkColorGetG(mul));
+ SkASSERT(SkColorGetR(mul) == SkColorGetB(mul));
+ }
+
+ virtual uint32_t getFlags() const SK_OVERRIDE {
+ return this->INHERITED::getFlags() | (kAlphaUnchanged_Flag | kHasFilter16_Flag);
+ }
+
+ virtual void filterSpan16(const uint16_t shader[], int count,
+ uint16_t result[]) const SK_OVERRIDE {
+ // all mul components are the same
+ unsigned scale = SkAlpha255To256(SkColorGetR(fMul));
+
+ if (count > 0) {
+ do {
+ *result++ = SkAlphaMulRGB16(*shader++, scale);
+ } while (--count > 0);
+ }
+ }
+
+#ifdef SK_DEVELOPER
+ virtual void toString(SkString* str) const SK_OVERRIDE {
+ str->append("SkLightingColorFilter_SingleMul: mul: 0x");
+ str->appendHex(fMul);
+ }
+#endif
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLightingColorFilter_SingleMul)
+
+protected:
+ SkLightingColorFilter_SingleMul(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {}
+
+private:
+ typedef SkLightingColorFilter INHERITED;
+};
+
+class SkLightingColorFilter_NoPin : public SkLightingColorFilter {
+public:
+ SkLightingColorFilter_NoPin(SkColor mul, SkColor add)
+ : INHERITED(mul, add) {}
+
+ virtual void filterSpan(const SkPMColor shader[], int count,
+ SkPMColor result[]) const SK_OVERRIDE {
+ unsigned scaleR = SkAlpha255To256(SkColorGetR(fMul));
+ unsigned scaleG = SkAlpha255To256(SkColorGetG(fMul));
+ unsigned scaleB = SkAlpha255To256(SkColorGetB(fMul));
+
+ unsigned addR = SkColorGetR(fAdd);
+ unsigned addG = SkColorGetG(fAdd);
+ unsigned addB = SkColorGetB(fAdd);
+
+ for (int i = 0; i < count; i++) {
+ SkPMColor c = shader[i];
+ if (c) {
+ unsigned a = SkGetPackedA32(c);
+ unsigned scaleA = SkAlpha255To256(a);
+ unsigned r = SkAlphaMul(SkGetPackedR32(c), scaleR) + SkAlphaMul(addR, scaleA);
+ unsigned g = SkAlphaMul(SkGetPackedG32(c), scaleG) + SkAlphaMul(addG, scaleA);
+ unsigned b = SkAlphaMul(SkGetPackedB32(c), scaleB) + SkAlphaMul(addB, scaleA);
+ c = SkPackARGB32(a, r, g, b);
+ }
+ result[i] = c;
+ }
+ }
+
+#ifdef SK_DEVELOPER
+ virtual void toString(SkString* str) const SK_OVERRIDE {
+ str->append("SkLightingColorFilter_NoPin: mul: 0x");
+ str->appendHex(fMul);
+ str->append(" add: 0x");
+ str->appendHex(fAdd);
+ }
+#endif
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLightingColorFilter_NoPin)
+
+protected:
+ SkLightingColorFilter_NoPin(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {}
+
+private:
+ typedef SkLightingColorFilter INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkSimpleColorFilter : public SkColorFilter {
+public:
+ static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
+ return SkNEW(SkSimpleColorFilter);
+ }
+
+#ifdef SK_DEVELOPER
+ virtual void toString(SkString* str) const SK_OVERRIDE {
+ str->append("SkSimpleColorFilter");
+ }
+#endif
+
+protected:
+ void filterSpan(const SkPMColor src[], int count, SkPMColor
+ result[]) const SK_OVERRIDE {
+ if (result != src) {
+ memcpy(result, src, count * sizeof(SkPMColor));
+ }
+ }
+
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {}
+
+ virtual Factory getFactory() {
+ return CreateProc;
+ }
+
+};
+
+SkColorFilter* SkColorFilter::CreateLightingFilter(SkColor mul, SkColor add) {
+ mul &= 0x00FFFFFF;
+ add &= 0x00FFFFFF;
+
+ if (0xFFFFFF == mul) {
+ if (0 == add) {
+ return SkNEW(SkSimpleColorFilter); // no change to the colors
+ } else {
+ return SkNEW_ARGS(SkLightingColorFilter_JustAdd, (mul, add));
+ }
+ }
+
+ if (0 == add) {
+ if (SkColorGetR(mul) == SkColorGetG(mul) &&
+ SkColorGetR(mul) == SkColorGetB(mul)) {
+ return SkNEW_ARGS(SkLightingColorFilter_SingleMul, (mul, add));
+ } else {
+ return SkNEW_ARGS(SkLightingColorFilter_JustMul, (mul, add));
+ }
+ }
+
+ if (SkColorGetR(mul) + SkColorGetR(add) <= 255 &&
+ SkColorGetG(mul) + SkColorGetG(add) <= 255 &&
+ SkColorGetB(mul) + SkColorGetB(add) <= 255) {
+ return SkNEW_ARGS(SkLightingColorFilter_NoPin, (mul, add));
+ }
+
+ return SkNEW_ARGS(SkLightingColorFilter, (mul, add));
+}
+
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkColorFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkModeColorFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Src_SkModeColorFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SrcOver_SkModeColorFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkLightingColorFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkLightingColorFilter_JustAdd)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkLightingColorFilter_JustMul)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkLightingColorFilter_SingleMul)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkLightingColorFilter_NoPin)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkSimpleColorFilter)
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
diff --git a/effects/SkColorMatrix.cpp b/effects/SkColorMatrix.cpp
new file mode 100644
index 00000000..d6cb9405
--- /dev/null
+++ b/effects/SkColorMatrix.cpp
@@ -0,0 +1,161 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "SkColorMatrix.h"
+#include "SkFlattenableBuffers.h"
+
+#define kRScale 0
+#define kGScale 6
+#define kBScale 12
+#define kAScale 18
+
+void SkColorMatrix::setIdentity() {
+ memset(fMat, 0, sizeof(fMat));
+ fMat[kRScale] = fMat[kGScale] = fMat[kBScale] = fMat[kAScale] = SK_Scalar1;
+}
+
+void SkColorMatrix::setScale(SkScalar rScale, SkScalar gScale, SkScalar bScale,
+ SkScalar aScale) {
+ memset(fMat, 0, sizeof(fMat));
+ fMat[kRScale] = rScale;
+ fMat[kGScale] = gScale;
+ fMat[kBScale] = bScale;
+ fMat[kAScale] = aScale;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkColorMatrix::setRotate(Axis axis, SkScalar degrees) {
+ SkScalar S, C;
+
+ S = SkScalarSinCos(SkDegreesToRadians(degrees), &C);
+
+ this->setSinCos(axis, S, C);
+}
+
+void SkColorMatrix::setSinCos(Axis axis, SkScalar sine, SkScalar cosine) {
+ SkASSERT((unsigned)axis < 3);
+
+ static const uint8_t gRotateIndex[] = {
+ 6, 7, 11, 12,
+ 0, 10, 2, 12,
+ 0, 1, 5, 6,
+ };
+ const uint8_t* index = gRotateIndex + axis * 4;
+
+ this->setIdentity();
+ fMat[index[0]] = cosine;
+ fMat[index[1]] = sine;
+ fMat[index[2]] = -sine;
+ fMat[index[3]] = cosine;
+}
+
+void SkColorMatrix::preRotate(Axis axis, SkScalar degrees) {
+ SkColorMatrix tmp;
+ tmp.setRotate(axis, degrees);
+ this->preConcat(tmp);
+}
+
+void SkColorMatrix::postRotate(Axis axis, SkScalar degrees) {
+ SkColorMatrix tmp;
+ tmp.setRotate(axis, degrees);
+ this->postConcat(tmp);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkColorMatrix::setConcat(const SkColorMatrix& matA,
+ const SkColorMatrix& matB) {
+ SkScalar tmp[20];
+ SkScalar* result = fMat;
+
+ if (&matA == this || &matB == this) {
+ result = tmp;
+ }
+
+ const SkScalar* a = matA.fMat;
+ const SkScalar* b = matB.fMat;
+
+ int index = 0;
+ for (int j = 0; j < 20; j += 5) {
+ for (int i = 0; i < 4; i++) {
+ result[index++] = SkScalarMul(a[j + 0], b[i + 0]) +
+ SkScalarMul(a[j + 1], b[i + 5]) +
+ SkScalarMul(a[j + 2], b[i + 10]) +
+ SkScalarMul(a[j + 3], b[i + 15]);
+ }
+ result[index++] = SkScalarMul(a[j + 0], b[4]) +
+ SkScalarMul(a[j + 1], b[9]) +
+ SkScalarMul(a[j + 2], b[14]) +
+ SkScalarMul(a[j + 3], b[19]) +
+ a[j + 4];
+ }
+
+ if (fMat != result) {
+ memcpy(fMat, result, sizeof(fMat));
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void setrow(SkScalar row[], SkScalar r, SkScalar g, SkScalar b) {
+ row[0] = r;
+ row[1] = g;
+ row[2] = b;
+}
+
+static const SkScalar kHueR = SkFloatToScalar(0.213f);
+static const SkScalar kHueG = SkFloatToScalar(0.715f);
+static const SkScalar kHueB = SkFloatToScalar(0.072f);
+
+void SkColorMatrix::setSaturation(SkScalar sat) {
+ memset(fMat, 0, sizeof(fMat));
+
+ const SkScalar R = SkScalarMul(kHueR, SK_Scalar1 - sat);
+ const SkScalar G = SkScalarMul(kHueG, SK_Scalar1 - sat);
+ const SkScalar B = SkScalarMul(kHueB, SK_Scalar1 - sat);
+
+ setrow(fMat + 0, R + sat, G, B);
+ setrow(fMat + 5, R, G + sat, B);
+ setrow(fMat + 10, R, G, B + sat);
+ fMat[18] = SK_Scalar1;
+}
+
+static const SkScalar kR2Y = SkFloatToScalar(0.299f);
+static const SkScalar kG2Y = SkFloatToScalar(0.587f);
+static const SkScalar kB2Y = SkFloatToScalar(0.114f);
+
+static const SkScalar kR2U = SkFloatToScalar(-0.16874f);
+static const SkScalar kG2U = SkFloatToScalar(-0.33126f);
+static const SkScalar kB2U = SkFloatToScalar(0.5f);
+
+static const SkScalar kR2V = SkFloatToScalar(0.5f);
+static const SkScalar kG2V = SkFloatToScalar(-0.41869f);
+static const SkScalar kB2V = SkFloatToScalar(-0.08131f);
+
+void SkColorMatrix::setRGB2YUV() {
+ memset(fMat, 0, sizeof(fMat));
+
+ setrow(fMat + 0, kR2Y, kG2Y, kB2Y);
+ setrow(fMat + 5, kR2U, kG2U, kB2U);
+ setrow(fMat + 10, kR2V, kG2V, kB2V);
+ fMat[18] = SK_Scalar1;
+}
+
+static const SkScalar kV2R = SkFloatToScalar(1.402f);
+static const SkScalar kU2G = SkFloatToScalar(-0.34414f);
+static const SkScalar kV2G = SkFloatToScalar(-0.71414f);
+static const SkScalar kU2B = SkFloatToScalar(1.772f);
+
+void SkColorMatrix::setYUV2RGB() {
+ memset(fMat, 0, sizeof(fMat));
+
+ setrow(fMat + 0, SK_Scalar1, 0, kV2R);
+ setrow(fMat + 5, SK_Scalar1, kU2G, kV2G);
+ setrow(fMat + 10, SK_Scalar1, kU2B, 0);
+ fMat[18] = SK_Scalar1;
+}
diff --git a/effects/SkColorMatrixFilter.cpp b/effects/SkColorMatrixFilter.cpp
new file mode 100644
index 00000000..3d1fd6f6
--- /dev/null
+++ b/effects/SkColorMatrixFilter.cpp
@@ -0,0 +1,495 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "SkColorMatrixFilter.h"
+#include "SkColorMatrix.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkUnPreMultiply.h"
+#include "SkString.h"
+
+static int32_t rowmul4(const int32_t array[], unsigned r, unsigned g,
+ unsigned b, unsigned a) {
+ return array[0] * r + array[1] * g + array[2] * b + array[3] * a + array[4];
+}
+
+static int32_t rowmul3(const int32_t array[], unsigned r, unsigned g,
+ unsigned b) {
+ return array[0] * r + array[1] * g + array[2] * b + array[4];
+}
+
+static void General(const SkColorMatrixFilter::State& state,
+ unsigned r, unsigned g, unsigned b, unsigned a,
+ int32_t* SK_RESTRICT result) {
+ const int32_t* SK_RESTRICT array = state.fArray;
+ const int shift = state.fShift;
+
+ result[0] = rowmul4(&array[0], r, g, b, a) >> shift;
+ result[1] = rowmul4(&array[5], r, g, b, a) >> shift;
+ result[2] = rowmul4(&array[10], r, g, b, a) >> shift;
+ result[3] = rowmul4(&array[15], r, g, b, a) >> shift;
+}
+
+static void General16(const SkColorMatrixFilter::State& state,
+ unsigned r, unsigned g, unsigned b, unsigned a,
+ int32_t* SK_RESTRICT result) {
+ const int32_t* SK_RESTRICT array = state.fArray;
+
+ result[0] = rowmul4(&array[0], r, g, b, a) >> 16;
+ result[1] = rowmul4(&array[5], r, g, b, a) >> 16;
+ result[2] = rowmul4(&array[10], r, g, b, a) >> 16;
+ result[3] = rowmul4(&array[15], r, g, b, a) >> 16;
+}
+
+static void AffineAdd(const SkColorMatrixFilter::State& state,
+ unsigned r, unsigned g, unsigned b, unsigned a,
+ int32_t* SK_RESTRICT result) {
+ const int32_t* SK_RESTRICT array = state.fArray;
+ const int shift = state.fShift;
+
+ result[0] = rowmul3(&array[0], r, g, b) >> shift;
+ result[1] = rowmul3(&array[5], r, g, b) >> shift;
+ result[2] = rowmul3(&array[10], r, g, b) >> shift;
+ result[3] = a;
+}
+
+static void AffineAdd16(const SkColorMatrixFilter::State& state,
+ unsigned r, unsigned g, unsigned b, unsigned a,
+ int32_t* SK_RESTRICT result) {
+ const int32_t* SK_RESTRICT array = state.fArray;
+
+ result[0] = rowmul3(&array[0], r, g, b) >> 16;
+ result[1] = rowmul3(&array[5], r, g, b) >> 16;
+ result[2] = rowmul3(&array[10], r, g, b) >> 16;
+ result[3] = a;
+}
+
+static void ScaleAdd(const SkColorMatrixFilter::State& state,
+ unsigned r, unsigned g, unsigned b, unsigned a,
+ int32_t* SK_RESTRICT result) {
+ const int32_t* SK_RESTRICT array = state.fArray;
+ const int shift = state.fShift;
+
+ // cast to (int) to keep the expression signed for the shift
+ result[0] = (array[0] * (int)r + array[4]) >> shift;
+ result[1] = (array[6] * (int)g + array[9]) >> shift;
+ result[2] = (array[12] * (int)b + array[14]) >> shift;
+ result[3] = a;
+}
+
+static void ScaleAdd16(const SkColorMatrixFilter::State& state,
+ unsigned r, unsigned g, unsigned b, unsigned a,
+ int32_t* SK_RESTRICT result) {
+ const int32_t* SK_RESTRICT array = state.fArray;
+
+ // cast to (int) to keep the expression signed for the shift
+ result[0] = (array[0] * (int)r + array[4]) >> 16;
+ result[1] = (array[6] * (int)g + array[9]) >> 16;
+ result[2] = (array[12] * (int)b + array[14]) >> 16;
+ result[3] = a;
+}
+
+static void Add(const SkColorMatrixFilter::State& state,
+ unsigned r, unsigned g, unsigned b, unsigned a,
+ int32_t* SK_RESTRICT result) {
+ const int32_t* SK_RESTRICT array = state.fArray;
+ const int shift = state.fShift;
+
+ result[0] = r + (array[4] >> shift);
+ result[1] = g + (array[9] >> shift);
+ result[2] = b + (array[14] >> shift);
+ result[3] = a;
+}
+
+static void Add16(const SkColorMatrixFilter::State& state,
+ unsigned r, unsigned g, unsigned b, unsigned a,
+ int32_t* SK_RESTRICT result) {
+ const int32_t* SK_RESTRICT array = state.fArray;
+
+ result[0] = r + (array[4] >> 16);
+ result[1] = g + (array[9] >> 16);
+ result[2] = b + (array[14] >> 16);
+ result[3] = a;
+}
+
+#define kNO_ALPHA_FLAGS (SkColorFilter::kAlphaUnchanged_Flag | \
+ SkColorFilter::kHasFilter16_Flag)
+
+// src is [20] but some compilers won't accept __restrict__ on anything
+// but an raw pointer or reference
+void SkColorMatrixFilter::initState(const SkScalar* SK_RESTRICT src) {
+ int32_t* array = fState.fArray;
+ SkFixed max = 0;
+ for (int i = 0; i < 20; i++) {
+ SkFixed value = SkScalarToFixed(src[i]);
+ array[i] = value;
+ value = SkAbs32(value);
+ max = SkMax32(max, value);
+ }
+
+ /* All of fArray[] values must fit in 23 bits, to safely allow me to
+ multiply them by 8bit unsigned values, and get a signed answer without
+ overflow. This means clz needs to be 9 or bigger
+ */
+ int bits = SkCLZ(max);
+ int32_t one = SK_Fixed1;
+
+ fState.fShift = 16; // we are starting out as fixed 16.16
+ if (bits < 9) {
+ bits = 9 - bits;
+ fState.fShift -= bits;
+ for (int i = 0; i < 20; i++) {
+ array[i] >>= bits;
+ }
+ one >>= bits;
+ }
+
+ // check if we have to munge Alpha
+ int32_t changesAlpha = (array[15] | array[16] | array[17] |
+ (array[18] - one) | array[19]);
+ int32_t usesAlpha = (array[3] | array[8] | array[13]);
+ bool shiftIs16 = (16 == fState.fShift);
+
+ if (changesAlpha | usesAlpha) {
+ fProc = shiftIs16 ? General16 : General;
+ fFlags = changesAlpha ? 0 : SkColorFilter::kAlphaUnchanged_Flag;
+ } else {
+ fFlags = kNO_ALPHA_FLAGS;
+
+ int32_t needsScale = (array[0] - one) | // red axis
+ (array[6] - one) | // green axis
+ (array[12] - one); // blue axis
+
+ int32_t needs3x3 = array[1] | array[2] | // red off-axis
+ array[5] | array[7] | // green off-axis
+ array[10] | array[11]; // blue off-axis
+
+ if (needs3x3) {
+ fProc = shiftIs16 ? AffineAdd16 : AffineAdd;
+ } else if (needsScale) {
+ fProc = shiftIs16 ? ScaleAdd16 : ScaleAdd;
+ } else if (array[4] | array[9] | array[14]) { // needs add
+ fProc = shiftIs16 ? Add16 : Add;
+ } else {
+ fProc = NULL; // identity
+ }
+ }
+
+ /* preround our add values so we get a rounded shift. We do this after we
+ analyze the array, so we don't miss the case where the caller has zeros
+ which could make us accidentally take the General or Add case.
+ */
+ if (NULL != fProc) {
+ int32_t add = 1 << (fState.fShift - 1);
+ array[4] += add;
+ array[9] += add;
+ array[14] += add;
+ array[19] += add;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int32_t pin(int32_t value, int32_t max) {
+ if (value < 0) {
+ value = 0;
+ }
+ if (value > max) {
+ value = max;
+ }
+ return value;
+}
+
+SkColorMatrixFilter::SkColorMatrixFilter(const SkColorMatrix& cm) : fMatrix(cm) {
+ this->initState(cm.fMat);
+}
+
+SkColorMatrixFilter::SkColorMatrixFilter(const SkScalar array[20]) {
+ memcpy(fMatrix.fMat, array, 20 * sizeof(SkScalar));
+ this->initState(array);
+}
+
+uint32_t SkColorMatrixFilter::getFlags() const {
+ return this->INHERITED::getFlags() | fFlags;
+}
+
+void SkColorMatrixFilter::filterSpan(const SkPMColor src[], int count,
+ SkPMColor dst[]) const {
+ Proc proc = fProc;
+ const State& state = fState;
+ int32_t result[4];
+
+ if (NULL == proc) {
+ if (src != dst) {
+ memcpy(dst, src, count * sizeof(SkPMColor));
+ }
+ return;
+ }
+
+ const SkUnPreMultiply::Scale* table = SkUnPreMultiply::GetScaleTable();
+
+ for (int i = 0; i < count; i++) {
+ SkPMColor c = src[i];
+
+ unsigned r = SkGetPackedR32(c);
+ unsigned g = SkGetPackedG32(c);
+ unsigned b = SkGetPackedB32(c);
+ unsigned a = SkGetPackedA32(c);
+
+ // need our components to be un-premultiplied
+ if (255 != a) {
+ SkUnPreMultiply::Scale scale = table[a];
+ r = SkUnPreMultiply::ApplyScale(scale, r);
+ g = SkUnPreMultiply::ApplyScale(scale, g);
+ b = SkUnPreMultiply::ApplyScale(scale, b);
+
+ SkASSERT(r <= 255);
+ SkASSERT(g <= 255);
+ SkASSERT(b <= 255);
+ }
+
+ proc(state, r, g, b, a, result);
+
+ r = pin(result[0], SK_R32_MASK);
+ g = pin(result[1], SK_G32_MASK);
+ b = pin(result[2], SK_B32_MASK);
+ a = pin(result[3], SK_A32_MASK);
+ // re-prepremultiply if needed
+ dst[i] = SkPremultiplyARGBInline(a, r, g, b);
+ }
+}
+
+void SkColorMatrixFilter::filterSpan16(const uint16_t src[], int count,
+ uint16_t dst[]) const {
+ SkASSERT(fFlags & SkColorFilter::kHasFilter16_Flag);
+
+ Proc proc = fProc;
+ const State& state = fState;
+ int32_t result[4];
+
+ if (NULL == proc) {
+ if (src != dst) {
+ memcpy(dst, src, count * sizeof(uint16_t));
+ }
+ return;
+ }
+
+ for (int i = 0; i < count; i++) {
+ uint16_t c = src[i];
+
+ // expand to 8bit components (since our matrix translate is 8bit biased
+ unsigned r = SkPacked16ToR32(c);
+ unsigned g = SkPacked16ToG32(c);
+ unsigned b = SkPacked16ToB32(c);
+
+ proc(state, r, g, b, 0, result);
+
+ r = pin(result[0], SK_R32_MASK);
+ g = pin(result[1], SK_G32_MASK);
+ b = pin(result[2], SK_B32_MASK);
+
+ // now packed it back down to 16bits (hmmm, could dither...)
+ dst[i] = SkPack888ToRGB16(r, g, b);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkColorMatrixFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ SkASSERT(sizeof(fMatrix.fMat)/sizeof(SkScalar) == 20);
+ buffer.writeScalarArray(fMatrix.fMat, 20);
+}
+
+SkColorMatrixFilter::SkColorMatrixFilter(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {
+ SkASSERT(buffer.getArrayCount() == 20);
+ buffer.readScalarArray(fMatrix.fMat);
+ this->initState(fMatrix.fMat);
+}
+
+bool SkColorMatrixFilter::asColorMatrix(SkScalar matrix[20]) const {
+ if (matrix) {
+ memcpy(matrix, fMatrix.fMat, 20 * sizeof(SkScalar));
+ }
+ return true;
+}
+
+#if SK_SUPPORT_GPU
+#include "GrEffect.h"
+#include "GrTBackendEffectFactory.h"
+#include "gl/GrGLEffect.h"
+
+class ColorMatrixEffect : public GrEffect {
+public:
+ static GrEffectRef* Create(const SkColorMatrix& matrix) {
+ AutoEffectUnref effect(SkNEW_ARGS(ColorMatrixEffect, (matrix)));
+ return CreateEffectRef(effect);
+ }
+
+ static const char* Name() { return "Color Matrix"; }
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<ColorMatrixEffect>::getInstance();
+ }
+
+ virtual void getConstantColorComponents(GrColor* color,
+ uint32_t* validFlags) const SK_OVERRIDE {
+ // We only bother to check whether the alpha channel will be constant. If SkColorMatrix had
+ // type flags it might be worth checking the other components.
+
+ // The matrix is defined such the 4th row determines the output alpha. The first four
+ // columns of that row multiply the input r, g, b, and a, respectively, and the last column
+ // is the "translation".
+ static const uint32_t kRGBAFlags[] = {
+ kR_GrColorComponentFlag,
+ kG_GrColorComponentFlag,
+ kB_GrColorComponentFlag,
+ kA_GrColorComponentFlag
+ };
+ static const int kShifts[] = {
+ GrColor_SHIFT_R, GrColor_SHIFT_G, GrColor_SHIFT_B, GrColor_SHIFT_A,
+ };
+ enum {
+ kAlphaRowStartIdx = 15,
+ kAlphaRowTranslateIdx = 19,
+ };
+
+ SkScalar outputA = 0;
+ for (int i = 0; i < 4; ++i) {
+ // If any relevant component of the color to be passed through the matrix is non-const
+ // then we can't know the final result.
+ if (0 != fMatrix.fMat[kAlphaRowStartIdx + i]) {
+ if (!(*validFlags & kRGBAFlags[i])) {
+ *validFlags = 0;
+ return;
+ } else {
+ uint32_t component = (*color >> kShifts[i]) & 0xFF;
+ outputA += fMatrix.fMat[kAlphaRowStartIdx + i] * component;
+ }
+ }
+ }
+ outputA += fMatrix.fMat[kAlphaRowTranslateIdx];
+ *validFlags = kA_GrColorComponentFlag;
+ // We pin the color to [0,1]. This would happen to the *final* color output from the frag
+ // shader but currently the effect does not pin its own output. So in the case of over/
+ // underflow this may deviate from the actual result. Maybe the effect should pin its
+ // result if the matrix could over/underflow for any component?
+ *color = static_cast<uint8_t>(SkScalarPin(outputA, 0, 255)) << GrColor_SHIFT_A;
+ }
+
+ GR_DECLARE_EFFECT_TEST;
+
+ class GLEffect : public GrGLEffect {
+ public:
+ // this class always generates the same code.
+ static EffectKey GenKey(const GrDrawEffect&, const GrGLCaps&) { return 0; }
+
+ GLEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect&)
+ : INHERITED(factory)
+ , fMatrixHandle(GrGLUniformManager::kInvalidUniformHandle)
+ , fVectorHandle(GrGLUniformManager::kInvalidUniformHandle) {}
+
+ virtual void emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE {
+ fMatrixHandle = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kMat44f_GrSLType,
+ "ColorMatrix");
+ fVectorHandle = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec4f_GrSLType,
+ "ColorMatrixVector");
+
+ if (NULL == inputColor) {
+ // could optimize this case, but we aren't for now.
+ inputColor = GrGLSLOnesVecf(4);
+ }
+ // The max() is to guard against 0 / 0 during unpremul when the incoming color is
+ // transparent black.
+ builder->fsCodeAppendf("\tfloat nonZeroAlpha = max(%s.a, 0.00001);\n", inputColor);
+ builder->fsCodeAppendf("\t%s = %s * vec4(%s.rgb / nonZeroAlpha, nonZeroAlpha) + %s;\n",
+ outputColor,
+ builder->getUniformCStr(fMatrixHandle),
+ inputColor,
+ builder->getUniformCStr(fVectorHandle));
+ builder->fsCodeAppendf("\t%s.rgb *= %s.a;\n", outputColor, outputColor);
+ }
+
+ virtual void setData(const GrGLUniformManager& uniManager,
+ const GrDrawEffect& drawEffect) SK_OVERRIDE {
+ const ColorMatrixEffect& cme = drawEffect.castEffect<ColorMatrixEffect>();
+ const float* m = cme.fMatrix.fMat;
+ // The GL matrix is transposed from SkColorMatrix.
+ GrGLfloat mt[] = {
+ m[0], m[5], m[10], m[15],
+ m[1], m[6], m[11], m[16],
+ m[2], m[7], m[12], m[17],
+ m[3], m[8], m[13], m[18],
+ };
+ static const float kScale = 1.0f / 255.0f;
+ GrGLfloat vec[] = {
+ m[4] * kScale, m[9] * kScale, m[14] * kScale, m[19] * kScale,
+ };
+ uniManager.setMatrix4fv(fMatrixHandle, 0, 1, mt);
+ uniManager.set4fv(fVectorHandle, 0, 1, vec);
+ }
+
+ private:
+ GrGLUniformManager::UniformHandle fMatrixHandle;
+ GrGLUniformManager::UniformHandle fVectorHandle;
+ };
+
+private:
+ ColorMatrixEffect(const SkColorMatrix& matrix) : fMatrix(matrix) {}
+
+ virtual bool onIsEqual(const GrEffect& s) const {
+ const ColorMatrixEffect& cme = CastEffect<ColorMatrixEffect>(s);
+ return cme.fMatrix == fMatrix;
+ }
+
+ SkColorMatrix fMatrix;
+
+ typedef GrGLEffect INHERITED;
+};
+
+GR_DEFINE_EFFECT_TEST(ColorMatrixEffect);
+
+GrEffectRef* ColorMatrixEffect::TestCreate(SkMWCRandom* random,
+ GrContext*,
+ const GrDrawTargetCaps&,
+ GrTexture* dummyTextures[2]) {
+ SkColorMatrix colorMatrix;
+ for (size_t i = 0; i < SK_ARRAY_COUNT(colorMatrix.fMat); ++i) {
+ colorMatrix.fMat[i] = random->nextSScalar1();
+ }
+ return ColorMatrixEffect::Create(colorMatrix);
+}
+
+GrEffectRef* SkColorMatrixFilter::asNewEffect(GrContext*) const {
+ return ColorMatrixEffect::Create(fMatrix);
+}
+
+#endif
+
+#ifdef SK_DEVELOPER
+void SkColorMatrixFilter::toString(SkString* str) const {
+ str->append("SkColorMatrixFilter: ");
+
+ str->append("matrix: (");
+ for (int i = 0; i < 20; ++i) {
+ str->appendScalar(fMatrix.fMat[i]);
+ if (i < 19) {
+ str->append(", ");
+ }
+ }
+ str->append(")");
+}
+#endif
diff --git a/effects/SkComposeImageFilter.cpp b/effects/SkComposeImageFilter.cpp
new file mode 100644
index 00000000..2ba2f3df
--- /dev/null
+++ b/effects/SkComposeImageFilter.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBitmap.h"
+#include "SkComposeImageFilter.h"
+#include "SkFlattenableBuffers.h"
+
+SkComposeImageFilter::~SkComposeImageFilter() {
+}
+
+bool SkComposeImageFilter::onFilterImage(Proxy* proxy,
+ const SkBitmap& src,
+ const SkMatrix& ctm,
+ SkBitmap* result,
+ SkIPoint* loc) {
+ SkImageFilter* outer = getInput(0);
+ SkImageFilter* inner = getInput(1);
+
+ if (!outer && !inner) {
+ return false;
+ }
+
+ if (!outer || !inner) {
+ return (outer ? outer : inner)->filterImage(proxy, src, ctm, result, loc);
+ }
+
+ SkBitmap tmp;
+ return inner->filterImage(proxy, src, ctm, &tmp, loc) &&
+ outer->filterImage(proxy, tmp, ctm, result, loc);
+}
+
+bool SkComposeImageFilter::onFilterBounds(const SkIRect& src,
+ const SkMatrix& ctm,
+ SkIRect* dst) {
+ SkImageFilter* outer = getInput(0);
+ SkImageFilter* inner = getInput(1);
+
+ if (!outer && !inner) {
+ return false;
+ }
+
+ if (!outer || !inner) {
+ return (outer ? outer : inner)->filterBounds(src, ctm, dst);
+ }
+
+ SkIRect tmp;
+ return inner->filterBounds(src, ctm, &tmp) &&
+ outer->filterBounds(tmp, ctm, dst);
+}
+
+SkComposeImageFilter::SkComposeImageFilter(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+}
diff --git a/effects/SkCornerPathEffect.cpp b/effects/SkCornerPathEffect.cpp
new file mode 100644
index 00000000..f4c31473
--- /dev/null
+++ b/effects/SkCornerPathEffect.cpp
@@ -0,0 +1,137 @@
+
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#include "SkCornerPathEffect.h"
+#include "SkPath.h"
+#include "SkPoint.h"
+#include "SkFlattenableBuffers.h"
+
+SkCornerPathEffect::SkCornerPathEffect(SkScalar radius) : fRadius(radius) {}
+SkCornerPathEffect::~SkCornerPathEffect() {}
+
+static bool ComputeStep(const SkPoint& a, const SkPoint& b, SkScalar radius,
+ SkPoint* step) {
+ SkScalar dist = SkPoint::Distance(a, b);
+
+ step->set(b.fX - a.fX, b.fY - a.fY);
+
+ if (dist <= radius * 2) {
+ step->scale(SK_ScalarHalf);
+ return false;
+ } else {
+ step->scale(SkScalarDiv(radius, dist));
+ return true;
+ }
+}
+
+bool SkCornerPathEffect::filterPath(SkPath* dst, const SkPath& src,
+ SkStrokeRec*, const SkRect*) const {
+ if (0 == fRadius) {
+ return false;
+ }
+
+ SkPath::Iter iter(src, false);
+ SkPath::Verb verb, prevVerb = (SkPath::Verb)-1;
+ SkPoint pts[4];
+
+ bool closed;
+ SkPoint moveTo, lastCorner;
+ SkVector firstStep, step;
+ bool prevIsValid = true;
+
+ // to avoid warnings
+ moveTo.set(0, 0);
+ firstStep.set(0, 0);
+ lastCorner.set(0, 0);
+
+ for (;;) {
+ switch (verb = iter.next(pts, false)) {
+ case SkPath::kMove_Verb:
+ // close out the previous (open) contour
+ if (SkPath::kLine_Verb == prevVerb) {
+ dst->lineTo(lastCorner);
+ }
+ closed = iter.isClosedContour();
+ if (closed) {
+ moveTo = pts[0];
+ prevIsValid = false;
+ } else {
+ dst->moveTo(pts[0]);
+ prevIsValid = true;
+ }
+ break;
+ case SkPath::kLine_Verb: {
+ bool drawSegment = ComputeStep(pts[0], pts[1], fRadius, &step);
+ // prev corner
+ if (!prevIsValid) {
+ dst->moveTo(moveTo + step);
+ prevIsValid = true;
+ } else {
+ dst->quadTo(pts[0].fX, pts[0].fY, pts[0].fX + step.fX,
+ pts[0].fY + step.fY);
+ }
+ if (drawSegment) {
+ dst->lineTo(pts[1].fX - step.fX, pts[1].fY - step.fY);
+ }
+ lastCorner = pts[1];
+ prevIsValid = true;
+ break;
+ }
+ case SkPath::kQuad_Verb:
+ // TBD - just replicate the curve for now
+ if (!prevIsValid) {
+ dst->moveTo(pts[0]);
+ prevIsValid = true;
+ }
+ dst->quadTo(pts[1], pts[2]);
+ lastCorner = pts[2];
+ firstStep.set(0, 0);
+ break;
+ case SkPath::kCubic_Verb:
+ if (!prevIsValid) {
+ dst->moveTo(pts[0]);
+ prevIsValid = true;
+ }
+ // TBD - just replicate the curve for now
+ dst->cubicTo(pts[1], pts[2], pts[3]);
+ lastCorner = pts[3];
+ firstStep.set(0, 0);
+ break;
+ case SkPath::kClose_Verb:
+ if (firstStep.fX || firstStep.fY) {
+ dst->quadTo(lastCorner.fX, lastCorner.fY,
+ lastCorner.fX + firstStep.fX,
+ lastCorner.fY + firstStep.fY);
+ }
+ dst->close();
+ break;
+ case SkPath::kConic_Verb:
+ SkASSERT(0);
+ break;
+ case SkPath::kDone_Verb:
+ goto DONE;
+ }
+
+ if (SkPath::kMove_Verb == prevVerb) {
+ firstStep = step;
+ }
+ prevVerb = verb;
+ }
+DONE:
+ return true;
+}
+
+void SkCornerPathEffect::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeScalar(fRadius);
+}
+
+SkCornerPathEffect::SkCornerPathEffect(SkFlattenableReadBuffer& buffer) {
+ fRadius = buffer.readScalar();
+}
diff --git a/effects/SkDashPathEffect.cpp b/effects/SkDashPathEffect.cpp
new file mode 100644
index 00000000..be065919
--- /dev/null
+++ b/effects/SkDashPathEffect.cpp
@@ -0,0 +1,559 @@
+
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#include "SkDashPathEffect.h"
+#include "SkFlattenableBuffers.h"
+#include "SkPathMeasure.h"
+
+static inline int is_even(int x) {
+ return (~x) << 31;
+}
+
+static SkScalar FindFirstInterval(const SkScalar intervals[], SkScalar phase,
+ int32_t* index, int count) {
+ for (int i = 0; i < count; ++i) {
+ if (phase > intervals[i]) {
+ phase -= intervals[i];
+ } else {
+ *index = i;
+ return intervals[i] - phase;
+ }
+ }
+ // If we get here, phase "appears" to be larger than our length. This
+ // shouldn't happen with perfect precision, but we can accumulate errors
+ // during the initial length computation (rounding can make our sum be too
+ // big or too small. In that event, we just have to eat the error here.
+ *index = 0;
+ return intervals[0];
+}
+
+SkDashPathEffect::SkDashPathEffect(const SkScalar intervals[], int count,
+ SkScalar phase, bool scaleToFit)
+ : fScaleToFit(scaleToFit) {
+ SkASSERT(intervals);
+ SkASSERT(count > 1 && SkAlign2(count) == count);
+
+ fIntervals = (SkScalar*)sk_malloc_throw(sizeof(SkScalar) * count);
+ fCount = count;
+
+ SkScalar len = 0;
+ for (int i = 0; i < count; i++) {
+ SkASSERT(intervals[i] >= 0);
+ fIntervals[i] = intervals[i];
+ len += intervals[i];
+ }
+ fIntervalLength = len;
+
+ // watch out for values that might make us go out of bounds
+ if ((len > 0) && SkScalarIsFinite(phase) && SkScalarIsFinite(len)) {
+
+ // Adjust phase to be between 0 and len, "flipping" phase if negative.
+ // e.g., if len is 100, then phase of -20 (or -120) is equivalent to 80
+ if (phase < 0) {
+ phase = -phase;
+ if (phase > len) {
+ phase = SkScalarMod(phase, len);
+ }
+ phase = len - phase;
+
+ // Due to finite precision, it's possible that phase == len,
+ // even after the subtract (if len >>> phase), so fix that here.
+ // This fixes http://crbug.com/124652 .
+ SkASSERT(phase <= len);
+ if (phase == len) {
+ phase = 0;
+ }
+ } else if (phase >= len) {
+ phase = SkScalarMod(phase, len);
+ }
+ SkASSERT(phase >= 0 && phase < len);
+
+ fInitialDashLength = FindFirstInterval(intervals, phase,
+ &fInitialDashIndex, count);
+
+ SkASSERT(fInitialDashLength >= 0);
+ SkASSERT(fInitialDashIndex >= 0 && fInitialDashIndex < fCount);
+ } else {
+ fInitialDashLength = -1; // signal bad dash intervals
+ }
+}
+
+SkDashPathEffect::~SkDashPathEffect() {
+ sk_free(fIntervals);
+}
+
+static void outset_for_stroke(SkRect* rect, const SkStrokeRec& rec) {
+ SkScalar radius = SkScalarHalf(rec.getWidth());
+ if (0 == radius) {
+ radius = SK_Scalar1; // hairlines
+ }
+ if (SkPaint::kMiter_Join == rec.getJoin()) {
+ radius = SkScalarMul(radius, rec.getMiter());
+ }
+ rect->outset(radius, radius);
+}
+
+// Only handles lines for now. If returns true, dstPath is the new (smaller)
+// path. If returns false, then dstPath parameter is ignored.
+static bool cull_path(const SkPath& srcPath, const SkStrokeRec& rec,
+ const SkRect* cullRect, SkScalar intervalLength,
+ SkPath* dstPath) {
+ if (NULL == cullRect) {
+ return false;
+ }
+
+ SkPoint pts[2];
+ if (!srcPath.isLine(pts)) {
+ return false;
+ }
+
+ SkRect bounds = *cullRect;
+ outset_for_stroke(&bounds, rec);
+
+ SkScalar dx = pts[1].x() - pts[0].x();
+ SkScalar dy = pts[1].y() - pts[0].y();
+
+ // just do horizontal lines for now (lazy)
+ if (dy) {
+ return false;
+ }
+
+ SkScalar minX = pts[0].fX;
+ SkScalar maxX = pts[1].fX;
+
+ if (maxX < bounds.fLeft || minX > bounds.fRight) {
+ return false;
+ }
+
+ if (dx < 0) {
+ SkTSwap(minX, maxX);
+ }
+
+ // Now we actually perform the chop, removing the excess to the left and
+ // right of the bounds (keeping our new line "in phase" with the dash,
+ // hence the (mod intervalLength).
+
+ if (minX < bounds.fLeft) {
+ minX = bounds.fLeft - SkScalarMod(bounds.fLeft - minX,
+ intervalLength);
+ }
+ if (maxX > bounds.fRight) {
+ maxX = bounds.fRight + SkScalarMod(maxX - bounds.fRight,
+ intervalLength);
+ }
+
+ SkASSERT(maxX >= minX);
+ if (dx < 0) {
+ SkTSwap(minX, maxX);
+ }
+ pts[0].fX = minX;
+ pts[1].fX = maxX;
+
+ dstPath->moveTo(pts[0]);
+ dstPath->lineTo(pts[1]);
+ return true;
+}
+
+class SpecialLineRec {
+public:
+ bool init(const SkPath& src, SkPath* dst, SkStrokeRec* rec,
+ int intervalCount, SkScalar intervalLength) {
+ if (rec->isHairlineStyle() || !src.isLine(fPts)) {
+ return false;
+ }
+
+ // can relax this in the future, if we handle square and round caps
+ if (SkPaint::kButt_Cap != rec->getCap()) {
+ return false;
+ }
+
+ SkScalar pathLength = SkPoint::Distance(fPts[0], fPts[1]);
+
+ fTangent = fPts[1] - fPts[0];
+ if (fTangent.isZero()) {
+ return false;
+ }
+
+ fPathLength = pathLength;
+ fTangent.scale(SkScalarInvert(pathLength));
+ fTangent.rotateCCW(&fNormal);
+ fNormal.scale(SkScalarHalf(rec->getWidth()));
+
+ // now estimate how many quads will be added to the path
+ // resulting segments = pathLen * intervalCount / intervalLen
+ // resulting points = 4 * segments
+
+ SkScalar ptCount = SkScalarMulDiv(pathLength,
+ SkIntToScalar(intervalCount),
+ intervalLength);
+ int n = SkScalarCeilToInt(ptCount) << 2;
+ dst->incReserve(n);
+
+ // we will take care of the stroking
+ rec->setFillStyle();
+ return true;
+ }
+
+ void addSegment(SkScalar d0, SkScalar d1, SkPath* path) const {
+ SkASSERT(d0 < fPathLength);
+ // clamp the segment to our length
+ if (d1 > fPathLength) {
+ d1 = fPathLength;
+ }
+
+ SkScalar x0 = fPts[0].fX + SkScalarMul(fTangent.fX, d0);
+ SkScalar x1 = fPts[0].fX + SkScalarMul(fTangent.fX, d1);
+ SkScalar y0 = fPts[0].fY + SkScalarMul(fTangent.fY, d0);
+ SkScalar y1 = fPts[0].fY + SkScalarMul(fTangent.fY, d1);
+
+ SkPoint pts[4];
+ pts[0].set(x0 + fNormal.fX, y0 + fNormal.fY); // moveTo
+ pts[1].set(x1 + fNormal.fX, y1 + fNormal.fY); // lineTo
+ pts[2].set(x1 - fNormal.fX, y1 - fNormal.fY); // lineTo
+ pts[3].set(x0 - fNormal.fX, y0 - fNormal.fY); // lineTo
+
+ path->addPoly(pts, SK_ARRAY_COUNT(pts), false);
+ }
+
+private:
+ SkPoint fPts[2];
+ SkVector fTangent;
+ SkVector fNormal;
+ SkScalar fPathLength;
+};
+
+bool SkDashPathEffect::filterPath(SkPath* dst, const SkPath& src,
+ SkStrokeRec* rec, const SkRect* cullRect) const {
+ // we do nothing if the src wants to be filled, or if our dashlength is 0
+ if (rec->isFillStyle() || fInitialDashLength < 0) {
+ return false;
+ }
+
+ const SkScalar* intervals = fIntervals;
+ SkScalar dashCount = 0;
+ int segCount = 0;
+
+ SkPath cullPathStorage;
+ const SkPath* srcPtr = &src;
+ if (cull_path(src, *rec, cullRect, fIntervalLength, &cullPathStorage)) {
+ srcPtr = &cullPathStorage;
+ }
+
+ SpecialLineRec lineRec;
+ bool specialLine = lineRec.init(*srcPtr, dst, rec, fCount >> 1, fIntervalLength);
+
+ SkPathMeasure meas(*srcPtr, false);
+
+ do {
+ bool skipFirstSegment = meas.isClosed();
+ bool addedSegment = false;
+ SkScalar length = meas.getLength();
+ int index = fInitialDashIndex;
+ SkScalar scale = SK_Scalar1;
+
+ // Since the path length / dash length ratio may be arbitrarily large, we can exert
+ // significant memory pressure while attempting to build the filtered path. To avoid this,
+ // we simply give up dashing beyond a certain threshold.
+ //
+ // The original bug report (http://crbug.com/165432) is based on a path yielding more than
+ // 90 million dash segments and crashing the memory allocator. A limit of 1 million
+ // segments seems reasonable: at 2 verbs per segment * 9 bytes per verb, this caps the
+ // maximum dash memory overhead at roughly 17MB per path.
+ static const SkScalar kMaxDashCount = 1000000;
+ dashCount += length * (fCount >> 1) / fIntervalLength;
+ if (dashCount > kMaxDashCount) {
+ dst->reset();
+ return false;
+ }
+
+ if (fScaleToFit) {
+ if (fIntervalLength >= length) {
+ scale = SkScalarDiv(length, fIntervalLength);
+ } else {
+ SkScalar div = SkScalarDiv(length, fIntervalLength);
+ int n = SkScalarFloor(div);
+ scale = SkScalarDiv(length, n * fIntervalLength);
+ }
+ }
+
+ // Using double precision to avoid looping indefinitely due to single precision rounding
+ // (for extreme path_length/dash_length ratios). See test_infinite_dash() unittest.
+ double distance = 0;
+ double dlen = SkScalarMul(fInitialDashLength, scale);
+
+ while (distance < length) {
+ SkASSERT(dlen >= 0);
+ addedSegment = false;
+ if (is_even(index) && dlen > 0 && !skipFirstSegment) {
+ addedSegment = true;
+ ++segCount;
+
+ if (specialLine) {
+ lineRec.addSegment(SkDoubleToScalar(distance),
+ SkDoubleToScalar(distance + dlen),
+ dst);
+ } else {
+ meas.getSegment(SkDoubleToScalar(distance),
+ SkDoubleToScalar(distance + dlen),
+ dst, true);
+ }
+ }
+ distance += dlen;
+
+ // clear this so we only respect it the first time around
+ skipFirstSegment = false;
+
+ // wrap around our intervals array if necessary
+ index += 1;
+ SkASSERT(index <= fCount);
+ if (index == fCount) {
+ index = 0;
+ }
+
+ // fetch our next dlen
+ dlen = SkScalarMul(intervals[index], scale);
+ }
+
+ // extend if we ended on a segment and we need to join up with the (skipped) initial segment
+ if (meas.isClosed() && is_even(fInitialDashIndex) &&
+ fInitialDashLength > 0) {
+ meas.getSegment(0, SkScalarMul(fInitialDashLength, scale), dst, !addedSegment);
+ ++segCount;
+ }
+ } while (meas.nextContour());
+
+ if (segCount > 1) {
+ dst->setConvexity(SkPath::kConcave_Convexity);
+ }
+
+ return true;
+}
+
+// Currently asPoints is more restrictive then it needs to be. In the future
+// we need to:
+// allow kRound_Cap capping (could allow rotations in the matrix with this)
+// allow paths to be returned
+bool SkDashPathEffect::asPoints(PointData* results,
+ const SkPath& src,
+ const SkStrokeRec& rec,
+ const SkMatrix& matrix,
+ const SkRect* cullRect) const {
+ // width < 0 -> fill && width == 0 -> hairline so requiring width > 0 rules both out
+ if (fInitialDashLength < 0 || 0 >= rec.getWidth()) {
+ return false;
+ }
+
+ // TODO: this next test could be eased up. We could allow any number of
+ // intervals as long as all the ons match and all the offs match.
+ // Additionally, they do not necessarily need to be integers.
+ // We cannot allow arbitrary intervals since we want the returned points
+ // to be uniformly sized.
+ if (fCount != 2 ||
+ !SkScalarNearlyEqual(fIntervals[0], fIntervals[1]) ||
+ !SkScalarIsInt(fIntervals[0]) ||
+ !SkScalarIsInt(fIntervals[1])) {
+ return false;
+ }
+
+ // TODO: this next test could be eased up. The rescaling should not impact
+ // the equality of the ons & offs. However, we would need to remove the
+ // integer intervals restriction first
+ if (fScaleToFit) {
+ return false;
+ }
+
+ SkPoint pts[2];
+
+ if (!src.isLine(pts)) {
+ return false;
+ }
+
+ // TODO: this test could be eased up to allow circles
+ if (SkPaint::kButt_Cap != rec.getCap()) {
+ return false;
+ }
+
+ // TODO: this test could be eased up for circles. Rotations could be allowed.
+ if (!matrix.rectStaysRect()) {
+ return false;
+ }
+
+ SkScalar length = SkPoint::Distance(pts[1], pts[0]);
+
+ SkVector tangent = pts[1] - pts[0];
+ if (tangent.isZero()) {
+ return false;
+ }
+
+ tangent.scale(SkScalarInvert(length));
+
+ // TODO: make this test for horizontal & vertical lines more robust
+ bool isXAxis = true;
+ if (SK_Scalar1 == tangent.fX || -SK_Scalar1 == tangent.fX) {
+ results->fSize.set(SkScalarHalf(fIntervals[0]), SkScalarHalf(rec.getWidth()));
+ } else if (SK_Scalar1 == tangent.fY || -SK_Scalar1 == tangent.fY) {
+ results->fSize.set(SkScalarHalf(rec.getWidth()), SkScalarHalf(fIntervals[0]));
+ isXAxis = false;
+ } else if (SkPaint::kRound_Cap != rec.getCap()) {
+ // Angled lines don't have axis-aligned boxes.
+ return false;
+ }
+
+ if (NULL != results) {
+ results->fFlags = 0;
+ SkScalar clampedInitialDashLength = SkMinScalar(length, fInitialDashLength);
+
+ if (SkPaint::kRound_Cap == rec.getCap()) {
+ results->fFlags |= PointData::kCircles_PointFlag;
+ }
+
+ results->fNumPoints = 0;
+ SkScalar len2 = length;
+ if (clampedInitialDashLength > 0 || 0 == fInitialDashIndex) {
+ SkASSERT(len2 >= clampedInitialDashLength);
+ if (0 == fInitialDashIndex) {
+ if (clampedInitialDashLength > 0) {
+ if (clampedInitialDashLength >= fIntervals[0]) {
+ ++results->fNumPoints; // partial first dash
+ }
+ len2 -= clampedInitialDashLength;
+ }
+ len2 -= fIntervals[1]; // also skip first space
+ if (len2 < 0) {
+ len2 = 0;
+ }
+ } else {
+ len2 -= clampedInitialDashLength; // skip initial partial empty
+ }
+ }
+ int numMidPoints = SkScalarFloorToInt(SkScalarDiv(len2, fIntervalLength));
+ results->fNumPoints += numMidPoints;
+ len2 -= numMidPoints * fIntervalLength;
+ bool partialLast = false;
+ if (len2 > 0) {
+ if (len2 < fIntervals[0]) {
+ partialLast = true;
+ } else {
+ ++numMidPoints;
+ ++results->fNumPoints;
+ }
+ }
+
+ results->fPoints = new SkPoint[results->fNumPoints];
+
+ SkScalar distance = 0;
+ int curPt = 0;
+
+ if (clampedInitialDashLength > 0 || 0 == fInitialDashIndex) {
+ SkASSERT(clampedInitialDashLength <= length);
+
+ if (0 == fInitialDashIndex) {
+ if (clampedInitialDashLength > 0) {
+ // partial first block
+ SkASSERT(SkPaint::kRound_Cap != rec.getCap()); // can't handle partial circles
+ SkScalar x = pts[0].fX + SkScalarMul(tangent.fX, SkScalarHalf(clampedInitialDashLength));
+ SkScalar y = pts[0].fY + SkScalarMul(tangent.fY, SkScalarHalf(clampedInitialDashLength));
+ SkScalar halfWidth, halfHeight;
+ if (isXAxis) {
+ halfWidth = SkScalarHalf(clampedInitialDashLength);
+ halfHeight = SkScalarHalf(rec.getWidth());
+ } else {
+ halfWidth = SkScalarHalf(rec.getWidth());
+ halfHeight = SkScalarHalf(clampedInitialDashLength);
+ }
+ if (clampedInitialDashLength < fIntervals[0]) {
+ // This one will not be like the others
+ results->fFirst.addRect(x - halfWidth, y - halfHeight,
+ x + halfWidth, y + halfHeight);
+ } else {
+ SkASSERT(curPt < results->fNumPoints);
+ results->fPoints[curPt].set(x, y);
+ ++curPt;
+ }
+
+ distance += clampedInitialDashLength;
+ }
+
+ distance += fIntervals[1]; // skip over the next blank block too
+ } else {
+ distance += clampedInitialDashLength;
+ }
+ }
+
+ if (0 != numMidPoints) {
+ distance += SkScalarHalf(fIntervals[0]);
+
+ for (int i = 0; i < numMidPoints; ++i) {
+ SkScalar x = pts[0].fX + SkScalarMul(tangent.fX, distance);
+ SkScalar y = pts[0].fY + SkScalarMul(tangent.fY, distance);
+
+ SkASSERT(curPt < results->fNumPoints);
+ results->fPoints[curPt].set(x, y);
+ ++curPt;
+
+ distance += fIntervalLength;
+ }
+
+ distance -= SkScalarHalf(fIntervals[0]);
+ }
+
+ if (partialLast) {
+ // partial final block
+ SkASSERT(SkPaint::kRound_Cap != rec.getCap()); // can't handle partial circles
+ SkScalar temp = length - distance;
+ SkASSERT(temp < fIntervals[0]);
+ SkScalar x = pts[0].fX + SkScalarMul(tangent.fX, distance + SkScalarHalf(temp));
+ SkScalar y = pts[0].fY + SkScalarMul(tangent.fY, distance + SkScalarHalf(temp));
+ SkScalar halfWidth, halfHeight;
+ if (isXAxis) {
+ halfWidth = SkScalarHalf(temp);
+ halfHeight = SkScalarHalf(rec.getWidth());
+ } else {
+ halfWidth = SkScalarHalf(rec.getWidth());
+ halfHeight = SkScalarHalf(temp);
+ }
+ results->fLast.addRect(x - halfWidth, y - halfHeight,
+ x + halfWidth, y + halfHeight);
+ }
+
+ SkASSERT(curPt == results->fNumPoints);
+ }
+
+ return true;
+}
+
+SkFlattenable::Factory SkDashPathEffect::getFactory() {
+ return fInitialDashLength < 0 ? NULL : CreateProc;
+}
+
+void SkDashPathEffect::flatten(SkFlattenableWriteBuffer& buffer) const {
+ SkASSERT(fInitialDashLength >= 0);
+
+ this->INHERITED::flatten(buffer);
+ buffer.writeInt(fInitialDashIndex);
+ buffer.writeScalar(fInitialDashLength);
+ buffer.writeScalar(fIntervalLength);
+ buffer.writeBool(fScaleToFit);
+ buffer.writeScalarArray(fIntervals, fCount);
+}
+
+SkFlattenable* SkDashPathEffect::CreateProc(SkFlattenableReadBuffer& buffer) {
+ return SkNEW_ARGS(SkDashPathEffect, (buffer));
+}
+
+SkDashPathEffect::SkDashPathEffect(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ fInitialDashIndex = buffer.readInt();
+ fInitialDashLength = buffer.readScalar();
+ fIntervalLength = buffer.readScalar();
+ fScaleToFit = buffer.readBool();
+
+ fCount = buffer.getArrayCount();
+ fIntervals = (SkScalar*)sk_malloc_throw(sizeof(SkScalar) * fCount);
+ buffer.readScalarArray(fIntervals);
+}
diff --git a/effects/SkDiscretePathEffect.cpp b/effects/SkDiscretePathEffect.cpp
new file mode 100644
index 00000000..2c95208a
--- /dev/null
+++ b/effects/SkDiscretePathEffect.cpp
@@ -0,0 +1,82 @@
+
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#include "SkDiscretePathEffect.h"
+#include "SkFlattenableBuffers.h"
+#include "SkPathMeasure.h"
+#include "SkRandom.h"
+
+static void Perterb(SkPoint* p, const SkVector& tangent, SkScalar scale) {
+ SkVector normal = tangent;
+ normal.rotateCCW();
+ normal.setLength(scale);
+ *p += normal;
+}
+
+
+SkDiscretePathEffect::SkDiscretePathEffect(SkScalar segLength, SkScalar deviation)
+ : fSegLength(segLength), fPerterb(deviation)
+{
+}
+
+bool SkDiscretePathEffect::filterPath(SkPath* dst, const SkPath& src,
+ SkStrokeRec* rec, const SkRect*) const {
+ bool doFill = rec->isFillStyle();
+
+ SkPathMeasure meas(src, doFill);
+ uint32_t seed = SkScalarRound(meas.getLength());
+ SkRandom rand(seed ^ ((seed << 16) | (seed >> 16)));
+ SkScalar scale = fPerterb;
+ SkPoint p;
+ SkVector v;
+
+ do {
+ SkScalar length = meas.getLength();
+
+ if (fSegLength * (2 + doFill) > length) {
+ meas.getSegment(0, length, dst, true); // to short for us to mangle
+ } else {
+ int n = SkScalarRound(SkScalarDiv(length, fSegLength));
+ SkScalar delta = length / n;
+ SkScalar distance = 0;
+
+ if (meas.isClosed()) {
+ n -= 1;
+ distance += delta/2;
+ }
+
+ if (meas.getPosTan(distance, &p, &v)) {
+ Perterb(&p, v, SkScalarMul(rand.nextSScalar1(), scale));
+ dst->moveTo(p);
+ }
+ while (--n >= 0) {
+ distance += delta;
+ if (meas.getPosTan(distance, &p, &v)) {
+ Perterb(&p, v, SkScalarMul(rand.nextSScalar1(), scale));
+ dst->lineTo(p);
+ }
+ }
+ if (meas.isClosed()) {
+ dst->close();
+ }
+ }
+ } while (meas.nextContour());
+ return true;
+}
+
+void SkDiscretePathEffect::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeScalar(fSegLength);
+ buffer.writeScalar(fPerterb);
+}
+
+SkDiscretePathEffect::SkDiscretePathEffect(SkFlattenableReadBuffer& buffer) {
+ fSegLength = buffer.readScalar();
+ fPerterb = buffer.readScalar();
+}
diff --git a/effects/SkDisplacementMapEffect.cpp b/effects/SkDisplacementMapEffect.cpp
new file mode 100644
index 00000000..dad0623d
--- /dev/null
+++ b/effects/SkDisplacementMapEffect.cpp
@@ -0,0 +1,541 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkDisplacementMapEffect.h"
+#include "SkFlattenableBuffers.h"
+#include "SkUnPreMultiply.h"
+#include "SkColorPriv.h"
+#if SK_SUPPORT_GPU
+#include "GrContext.h"
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
+#include "GrTBackendEffectFactory.h"
+#include "SkImageFilterUtils.h"
+#endif
+
+namespace {
+
+template<SkDisplacementMapEffect::ChannelSelectorType type>
+uint32_t getValue(SkColor, const SkUnPreMultiply::Scale*) {
+ SkASSERT(!"Unknown channel selector");
+ return 0;
+}
+
+template<> uint32_t getValue<SkDisplacementMapEffect::kR_ChannelSelectorType>(
+ SkColor l, const SkUnPreMultiply::Scale* table) {
+ return SkUnPreMultiply::ApplyScale(table[SkGetPackedA32(l)], SkGetPackedR32(l));
+}
+
+template<> uint32_t getValue<SkDisplacementMapEffect::kG_ChannelSelectorType>(
+ SkColor l, const SkUnPreMultiply::Scale* table) {
+ return SkUnPreMultiply::ApplyScale(table[SkGetPackedA32(l)], SkGetPackedG32(l));
+}
+
+template<> uint32_t getValue<SkDisplacementMapEffect::kB_ChannelSelectorType>(
+ SkColor l, const SkUnPreMultiply::Scale* table) {
+ return SkUnPreMultiply::ApplyScale(table[SkGetPackedA32(l)], SkGetPackedB32(l));
+}
+
+template<> uint32_t getValue<SkDisplacementMapEffect::kA_ChannelSelectorType>(
+ SkColor l, const SkUnPreMultiply::Scale*) {
+ return SkGetPackedA32(l);
+}
+
+template<SkDisplacementMapEffect::ChannelSelectorType typeX,
+ SkDisplacementMapEffect::ChannelSelectorType typeY>
+void computeDisplacement(SkScalar scale, SkBitmap* dst, SkBitmap* displ, SkBitmap* src)
+{
+ static const SkScalar Inv8bit = SkScalarDiv(SK_Scalar1, SkFloatToScalar(255.0f));
+ const int dstW = displ->width();
+ const int dstH = displ->height();
+ const int srcW = src->width();
+ const int srcH = src->height();
+ const SkScalar scaleForColor = SkScalarMul(scale, Inv8bit);
+ const SkScalar scaleAdj = SK_ScalarHalf - SkScalarMul(scale, SK_ScalarHalf);
+ const SkUnPreMultiply::Scale* table = SkUnPreMultiply::GetScaleTable();
+ for (int y = 0; y < dstH; ++y) {
+ const SkPMColor* displPtr = displ->getAddr32(0, y);
+ SkPMColor* dstPtr = dst->getAddr32(0, y);
+ for (int x = 0; x < dstW; ++x, ++displPtr, ++dstPtr) {
+ const SkScalar displX = SkScalarMul(scaleForColor,
+ SkIntToScalar(getValue<typeX>(*displPtr, table))) + scaleAdj;
+ const SkScalar displY = SkScalarMul(scaleForColor,
+ SkIntToScalar(getValue<typeY>(*displPtr, table))) + scaleAdj;
+ // Truncate the displacement values
+ const int srcX = x + SkScalarTruncToInt(displX);
+ const int srcY = y + SkScalarTruncToInt(displY);
+ *dstPtr = ((srcX < 0) || (srcX >= srcW) || (srcY < 0) || (srcY >= srcH)) ?
+ 0 : *(src->getAddr32(srcX, srcY));
+ }
+ }
+}
+
+template<SkDisplacementMapEffect::ChannelSelectorType typeX>
+void computeDisplacement(SkDisplacementMapEffect::ChannelSelectorType yChannelSelector,
+ SkScalar scale, SkBitmap* dst, SkBitmap* displ, SkBitmap* src)
+{
+ switch (yChannelSelector) {
+ case SkDisplacementMapEffect::kR_ChannelSelectorType:
+ computeDisplacement<typeX, SkDisplacementMapEffect::kR_ChannelSelectorType>(
+ scale, dst, displ, src);
+ break;
+ case SkDisplacementMapEffect::kG_ChannelSelectorType:
+ computeDisplacement<typeX, SkDisplacementMapEffect::kG_ChannelSelectorType>(
+ scale, dst, displ, src);
+ break;
+ case SkDisplacementMapEffect::kB_ChannelSelectorType:
+ computeDisplacement<typeX, SkDisplacementMapEffect::kB_ChannelSelectorType>(
+ scale, dst, displ, src);
+ break;
+ case SkDisplacementMapEffect::kA_ChannelSelectorType:
+ computeDisplacement<typeX, SkDisplacementMapEffect::kA_ChannelSelectorType>(
+ scale, dst, displ, src);
+ break;
+ case SkDisplacementMapEffect::kUnknown_ChannelSelectorType:
+ default:
+ SkASSERT(!"Unknown Y channel selector");
+ }
+}
+
+void computeDisplacement(SkDisplacementMapEffect::ChannelSelectorType xChannelSelector,
+ SkDisplacementMapEffect::ChannelSelectorType yChannelSelector,
+ SkScalar scale, SkBitmap* dst, SkBitmap* displ, SkBitmap* src)
+{
+ switch (xChannelSelector) {
+ case SkDisplacementMapEffect::kR_ChannelSelectorType:
+ computeDisplacement<SkDisplacementMapEffect::kR_ChannelSelectorType>(
+ yChannelSelector, scale, dst, displ, src);
+ break;
+ case SkDisplacementMapEffect::kG_ChannelSelectorType:
+ computeDisplacement<SkDisplacementMapEffect::kG_ChannelSelectorType>(
+ yChannelSelector, scale, dst, displ, src);
+ break;
+ case SkDisplacementMapEffect::kB_ChannelSelectorType:
+ computeDisplacement<SkDisplacementMapEffect::kB_ChannelSelectorType>(
+ yChannelSelector, scale, dst, displ, src);
+ break;
+ case SkDisplacementMapEffect::kA_ChannelSelectorType:
+ computeDisplacement<SkDisplacementMapEffect::kA_ChannelSelectorType>(
+ yChannelSelector, scale, dst, displ, src);
+ break;
+ case SkDisplacementMapEffect::kUnknown_ChannelSelectorType:
+ default:
+ SkASSERT(!"Unknown X channel selector");
+ }
+}
+
+} // end namespace
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkDisplacementMapEffect::SkDisplacementMapEffect(ChannelSelectorType xChannelSelector,
+ ChannelSelectorType yChannelSelector,
+ SkScalar scale,
+ SkImageFilter* displacement,
+ SkImageFilter* color)
+ : INHERITED(displacement, color)
+ , fXChannelSelector(xChannelSelector)
+ , fYChannelSelector(yChannelSelector)
+ , fScale(scale)
+{
+}
+
+SkDisplacementMapEffect::~SkDisplacementMapEffect() {
+}
+
+SkDisplacementMapEffect::SkDisplacementMapEffect(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer)
+{
+ fXChannelSelector = (SkDisplacementMapEffect::ChannelSelectorType) buffer.readInt();
+ fYChannelSelector = (SkDisplacementMapEffect::ChannelSelectorType) buffer.readInt();
+ fScale = buffer.readScalar();
+}
+
+void SkDisplacementMapEffect::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeInt((int) fXChannelSelector);
+ buffer.writeInt((int) fYChannelSelector);
+ buffer.writeScalar(fScale);
+}
+
+bool SkDisplacementMapEffect::onFilterImage(Proxy* proxy,
+ const SkBitmap& src,
+ const SkMatrix& ctm,
+ SkBitmap* dst,
+ SkIPoint* offset) {
+ SkBitmap displ, color = src;
+ SkImageFilter* colorInput = getColorInput();
+ SkImageFilter* displacementInput = getDisplacementInput();
+ SkASSERT(NULL != displacementInput);
+ if ((colorInput && !colorInput->filterImage(proxy, src, ctm, &color, offset)) ||
+ !displacementInput->filterImage(proxy, src, ctm, &displ, offset)) {
+ return false;
+ }
+ if ((displ.config() != SkBitmap::kARGB_8888_Config) ||
+ (color.config() != SkBitmap::kARGB_8888_Config)) {
+ return false;
+ }
+
+ SkAutoLockPixels alp_displacement(displ), alp_color(color);
+ if (!displ.getPixels() || !color.getPixels()) {
+ return false;
+ }
+ dst->setConfig(displ.config(), displ.width(), displ.height());
+ dst->allocPixels();
+ if (!dst->getPixels()) {
+ return false;
+ }
+
+ computeDisplacement(fXChannelSelector, fYChannelSelector, fScale, dst, &displ, &color);
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+class GrGLDisplacementMapEffect : public GrGLEffect {
+public:
+ GrGLDisplacementMapEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect);
+ virtual ~GrGLDisplacementMapEffect();
+
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ static inline EffectKey GenKey(const GrDrawEffect&, const GrGLCaps&);
+
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+private:
+ static const GrEffect::CoordsType kCoordsType = GrEffect::kLocal_CoordsType;
+
+ SkDisplacementMapEffect::ChannelSelectorType fXChannelSelector;
+ SkDisplacementMapEffect::ChannelSelectorType fYChannelSelector;
+ GrGLEffectMatrix fDisplacementEffectMatrix;
+ GrGLEffectMatrix fColorEffectMatrix;
+ GrGLUniformManager::UniformHandle fScaleUni;
+
+ typedef GrGLEffect INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class GrDisplacementMapEffect : public GrEffect {
+public:
+ static GrEffectRef* Create(SkDisplacementMapEffect::ChannelSelectorType xChannelSelector,
+ SkDisplacementMapEffect::ChannelSelectorType yChannelSelector,
+ SkScalar scale, GrTexture* displacement, GrTexture* color) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrDisplacementMapEffect, (xChannelSelector,
+ yChannelSelector,
+ scale,
+ displacement,
+ color)));
+ return CreateEffectRef(effect);
+ }
+
+ virtual ~GrDisplacementMapEffect();
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+ SkDisplacementMapEffect::ChannelSelectorType xChannelSelector() const
+ { return fXChannelSelector; }
+ SkDisplacementMapEffect::ChannelSelectorType yChannelSelector() const
+ { return fYChannelSelector; }
+ SkScalar scale() const { return fScale; }
+
+ typedef GrGLDisplacementMapEffect GLEffect;
+ static const char* Name() { return "DisplacementMap"; }
+
+ virtual void getConstantColorComponents(GrColor* color, uint32_t* validFlags) const SK_OVERRIDE;
+
+private:
+
+ virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE;
+
+ GrDisplacementMapEffect(SkDisplacementMapEffect::ChannelSelectorType xChannelSelector,
+ SkDisplacementMapEffect::ChannelSelectorType yChannelSelector,
+ SkScalar scale, GrTexture* displacement, GrTexture* color);
+
+ GR_DECLARE_EFFECT_TEST;
+
+ GrTextureAccess fDisplacementAccess;
+ GrTextureAccess fColorAccess;
+ SkDisplacementMapEffect::ChannelSelectorType fXChannelSelector;
+ SkDisplacementMapEffect::ChannelSelectorType fYChannelSelector;
+ SkScalar fScale;
+
+ typedef GrEffect INHERITED;
+};
+
+bool SkDisplacementMapEffect::filterImageGPU(Proxy* proxy, const SkBitmap& src, const SkMatrix& ctm,
+ SkBitmap* result, SkIPoint* offset) {
+ SkBitmap colorBM;
+ SkIPoint colorOffset = SkIPoint::Make(0, 0);
+ if (!SkImageFilterUtils::GetInputResultGPU(getColorInput(), proxy, src, ctm, &colorBM,
+ &colorOffset)) {
+ return false;
+ }
+ GrTexture* color = colorBM.getTexture();
+ SkBitmap displacementBM;
+ SkIPoint displacementOffset = SkIPoint::Make(0, 0);
+ if (!SkImageFilterUtils::GetInputResultGPU(getDisplacementInput(), proxy, src, ctm,
+ &displacementBM, &displacementOffset)) {
+ return false;
+ }
+ GrTexture* displacement = displacementBM.getTexture();
+ GrContext* context = color->getContext();
+
+ GrTextureDesc desc;
+ desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
+ desc.fWidth = src.width();
+ desc.fHeight = src.height();
+ desc.fConfig = kSkia8888_GrPixelConfig;
+
+ GrAutoScratchTexture ast(context, desc);
+ SkAutoTUnref<GrTexture> dst(ast.detach());
+
+ GrContext::AutoRenderTarget art(context, dst->asRenderTarget());
+
+ GrPaint paint;
+ paint.addColorEffect(
+ GrDisplacementMapEffect::Create(fXChannelSelector,
+ fYChannelSelector,
+ fScale,
+ displacement,
+ color))->unref();
+ SkRect srcRect;
+ src.getBounds(&srcRect);
+ SkRect dstRect = srcRect;
+ dstRect.offset(SkIntToScalar(colorOffset.fX), SkIntToScalar(colorOffset.fY));
+ context->drawRectToRect(paint, srcRect, dstRect);
+ return SkImageFilterUtils::WrapTexture(dst, src.width(), src.height(), result);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrDisplacementMapEffect::GrDisplacementMapEffect(
+ SkDisplacementMapEffect::ChannelSelectorType xChannelSelector,
+ SkDisplacementMapEffect::ChannelSelectorType yChannelSelector,
+ SkScalar scale,
+ GrTexture* displacement,
+ GrTexture* color)
+ : fDisplacementAccess(displacement)
+ , fColorAccess(color)
+ , fXChannelSelector(xChannelSelector)
+ , fYChannelSelector(yChannelSelector)
+ , fScale(scale) {
+ this->addTextureAccess(&fDisplacementAccess);
+ this->addTextureAccess(&fColorAccess);
+}
+
+GrDisplacementMapEffect::~GrDisplacementMapEffect() {
+}
+
+bool GrDisplacementMapEffect::onIsEqual(const GrEffect& sBase) const {
+ const GrDisplacementMapEffect& s = CastEffect<GrDisplacementMapEffect>(sBase);
+ return fDisplacementAccess.getTexture() == s.fDisplacementAccess.getTexture() &&
+ fColorAccess.getTexture() == s.fColorAccess.getTexture() &&
+ fXChannelSelector == s.fXChannelSelector &&
+ fYChannelSelector == s.fYChannelSelector &&
+ fScale == s.fScale;
+}
+
+const GrBackendEffectFactory& GrDisplacementMapEffect::getFactory() const {
+ return GrTBackendEffectFactory<GrDisplacementMapEffect>::getInstance();
+}
+
+void GrDisplacementMapEffect::getConstantColorComponents(GrColor*,
+ uint32_t* validFlags) const {
+ // Any displacement offset bringing a pixel out of bounds will output a color of (0,0,0,0),
+ // so the only way we'd get a constant alpha is if the input color image has a constant alpha
+ // and no displacement offset push any texture coordinates out of bounds OR if the constant
+ // alpha is 0. Since this isn't trivial to compute at this point, let's assume the output is
+ // not of constant color when a displacement effect is applied.
+ *validFlags = 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_EFFECT_TEST(GrDisplacementMapEffect);
+
+GrEffectRef* GrDisplacementMapEffect::TestCreate(SkMWCRandom* random,
+ GrContext*,
+ const GrDrawTargetCaps&,
+ GrTexture* textures[]) {
+ int texIdxDispl = random->nextBool() ? GrEffectUnitTest::kSkiaPMTextureIdx :
+ GrEffectUnitTest::kAlphaTextureIdx;
+ int texIdxColor = random->nextBool() ? GrEffectUnitTest::kSkiaPMTextureIdx :
+ GrEffectUnitTest::kAlphaTextureIdx;
+ static const int kMaxComponent = 4;
+ SkDisplacementMapEffect::ChannelSelectorType xChannelSelector =
+ static_cast<SkDisplacementMapEffect::ChannelSelectorType>(
+ random->nextRangeU(1, kMaxComponent));
+ SkDisplacementMapEffect::ChannelSelectorType yChannelSelector =
+ static_cast<SkDisplacementMapEffect::ChannelSelectorType>(
+ random->nextRangeU(1, kMaxComponent));
+ SkScalar scale = random->nextRangeScalar(0, SkFloatToScalar(100.0f));
+
+ return GrDisplacementMapEffect::Create(xChannelSelector, yChannelSelector, scale,
+ textures[texIdxDispl], textures[texIdxColor]);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrGLDisplacementMapEffect::GrGLDisplacementMapEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect)
+ : INHERITED(factory)
+ , fXChannelSelector(drawEffect.castEffect<GrDisplacementMapEffect>().xChannelSelector())
+ , fYChannelSelector(drawEffect.castEffect<GrDisplacementMapEffect>().yChannelSelector())
+ , fDisplacementEffectMatrix(kCoordsType)
+ , fColorEffectMatrix(kCoordsType) {
+}
+
+GrGLDisplacementMapEffect::~GrGLDisplacementMapEffect() {
+}
+
+void GrGLDisplacementMapEffect::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect&,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ sk_ignore_unused_variable(inputColor);
+
+ fScaleUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec2f_GrSLType, "Scale");
+ const char* scaleUni = builder->getUniformCStr(fScaleUni);
+
+ const char* dCoordsIn;
+ GrSLType dCoordsType = fDisplacementEffectMatrix.emitCode(
+ builder, key, &dCoordsIn, NULL, "DISPL");
+ const char* cCoordsIn;
+ GrSLType cCoordsType = fColorEffectMatrix.emitCode(
+ builder, key, &cCoordsIn, NULL, "COLOR");
+
+ const char* dColor = "dColor";
+ const char* cCoords = "cCoords";
+ const char* outOfBounds = "outOfBounds";
+ const char* nearZero = "1e-6"; // Since 6.10352e−5 is the smallest half float, use
+ // a number smaller than that to approximate 0, but
+ // leave room for 32-bit float GPU rounding errors.
+
+ builder->fsCodeAppendf("\t\tvec4 %s = ", dColor);
+ builder->appendTextureLookup(GrGLShaderBuilder::kFragment_ShaderType,
+ samplers[0],
+ dCoordsIn,
+ dCoordsType);
+ builder->fsCodeAppend(";\n");
+
+ // Unpremultiply the displacement
+ builder->fsCodeAppendf("\t\t%s.rgb = (%s.a < %s) ? vec3(0.0) : clamp(%s.rgb / %s.a, 0.0, 1.0);",
+ dColor, dColor, nearZero, dColor, dColor);
+
+ builder->fsCodeAppendf("\t\tvec2 %s = %s + %s*(%s.",
+ cCoords, cCoordsIn, scaleUni, dColor);
+
+ switch (fXChannelSelector) {
+ case SkDisplacementMapEffect::kR_ChannelSelectorType:
+ builder->fsCodeAppend("r");
+ break;
+ case SkDisplacementMapEffect::kG_ChannelSelectorType:
+ builder->fsCodeAppend("g");
+ break;
+ case SkDisplacementMapEffect::kB_ChannelSelectorType:
+ builder->fsCodeAppend("b");
+ break;
+ case SkDisplacementMapEffect::kA_ChannelSelectorType:
+ builder->fsCodeAppend("a");
+ break;
+ case SkDisplacementMapEffect::kUnknown_ChannelSelectorType:
+ default:
+ SkASSERT(!"Unknown X channel selector");
+ }
+
+ switch (fYChannelSelector) {
+ case SkDisplacementMapEffect::kR_ChannelSelectorType:
+ builder->fsCodeAppend("r");
+ break;
+ case SkDisplacementMapEffect::kG_ChannelSelectorType:
+ builder->fsCodeAppend("g");
+ break;
+ case SkDisplacementMapEffect::kB_ChannelSelectorType:
+ builder->fsCodeAppend("b");
+ break;
+ case SkDisplacementMapEffect::kA_ChannelSelectorType:
+ builder->fsCodeAppend("a");
+ break;
+ case SkDisplacementMapEffect::kUnknown_ChannelSelectorType:
+ default:
+ SkASSERT(!"Unknown Y channel selector");
+ }
+ builder->fsCodeAppend("-vec2(0.5));\t\t");
+
+ // FIXME : This can be achieved with a "clamp to border" texture repeat mode and
+ // a 0 border color instead of computing if cCoords is out of bounds here.
+ builder->fsCodeAppendf(
+ "bool %s = (%s.x < 0.0) || (%s.y < 0.0) || (%s.x > 1.0) || (%s.y > 1.0);\t\t",
+ outOfBounds, cCoords, cCoords, cCoords, cCoords);
+ builder->fsCodeAppendf("%s = %s ? vec4(0.0) : ", outputColor, outOfBounds);
+ builder->appendTextureLookup(GrGLShaderBuilder::kFragment_ShaderType,
+ samplers[1],
+ cCoords,
+ cCoordsType);
+ builder->fsCodeAppend(";\n");
+}
+
+void GrGLDisplacementMapEffect::setData(const GrGLUniformManager& uman,
+ const GrDrawEffect& drawEffect) {
+ const GrDisplacementMapEffect& displacementMap =
+ drawEffect.castEffect<GrDisplacementMapEffect>();
+ GrTexture* displTex = displacementMap.texture(0);
+ GrTexture* colorTex = displacementMap.texture(1);
+ fDisplacementEffectMatrix.setData(uman,
+ GrEffect::MakeDivByTextureWHMatrix(displTex),
+ drawEffect,
+ displTex);
+ fColorEffectMatrix.setData(uman,
+ GrEffect::MakeDivByTextureWHMatrix(colorTex),
+ drawEffect,
+ colorTex);
+
+ SkScalar scaleX = SkScalarDiv(displacementMap.scale(), SkIntToScalar(colorTex->width()));
+ SkScalar scaleY = SkScalarDiv(displacementMap.scale(), SkIntToScalar(colorTex->height()));
+ uman.set2f(fScaleUni, SkScalarToFloat(scaleX),
+ colorTex->origin() == kTopLeft_GrSurfaceOrigin ?
+ SkScalarToFloat(scaleY) : SkScalarToFloat(-scaleY));
+}
+
+GrGLEffect::EffectKey GrGLDisplacementMapEffect::GenKey(const GrDrawEffect& drawEffect,
+ const GrGLCaps&) {
+ const GrDisplacementMapEffect& displacementMap =
+ drawEffect.castEffect<GrDisplacementMapEffect>();
+
+ GrTexture* displTex = displacementMap.texture(0);
+ GrTexture* colorTex = displacementMap.texture(1);
+
+ EffectKey displKey = GrGLEffectMatrix::GenKey(GrEffect::MakeDivByTextureWHMatrix(displTex),
+ drawEffect,
+ kCoordsType,
+ displTex);
+
+ EffectKey colorKey = GrGLEffectMatrix::GenKey(GrEffect::MakeDivByTextureWHMatrix(colorTex),
+ drawEffect,
+ kCoordsType,
+ colorTex);
+
+ colorKey <<= GrGLEffectMatrix::kKeyBits;
+ EffectKey xKey = displacementMap.xChannelSelector() << (2 * GrGLEffectMatrix::kKeyBits);
+ EffectKey yKey = displacementMap.yChannelSelector() << (2 * GrGLEffectMatrix::kKeyBits +
+ SkDisplacementMapEffect::kKeyBits);
+
+ return xKey | yKey | displKey | colorKey;
+}
+#endif
diff --git a/effects/SkDropShadowImageFilter.cpp b/effects/SkDropShadowImageFilter.cpp
new file mode 100644
index 00000000..b8bbfd6f
--- /dev/null
+++ b/effects/SkDropShadowImageFilter.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkDropShadowImageFilter.h"
+
+#include "SkBitmap.h"
+#include "SkBlurImageFilter.h"
+#include "SkCanvas.h"
+#include "SkColorMatrixFilter.h"
+#include "SkDevice.h"
+#include "SkFlattenableBuffers.h"
+
+SkDropShadowImageFilter::SkDropShadowImageFilter(SkScalar dx, SkScalar dy, SkScalar sigma, SkColor color, SkImageFilter* input)
+ : SkImageFilter(input)
+ , fDx(dx)
+ , fDy(dy)
+ , fSigma(sigma)
+ , fColor(color)
+{
+}
+
+SkDropShadowImageFilter::SkDropShadowImageFilter(SkFlattenableReadBuffer& buffer) : INHERITED(buffer)
+{
+ fDx = buffer.readScalar();
+ fDy = buffer.readScalar();
+ fSigma = buffer.readScalar();
+ fColor = buffer.readColor();
+}
+
+void SkDropShadowImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const
+{
+ this->INHERITED::flatten(buffer);
+ buffer.writeScalar(fDx);
+ buffer.writeScalar(fDy);
+ buffer.writeScalar(fSigma);
+ buffer.writeColor(fColor);
+}
+
+bool SkDropShadowImageFilter::onFilterImage(Proxy* proxy, const SkBitmap& source, const SkMatrix& matrix, SkBitmap* result, SkIPoint* loc)
+{
+ SkBitmap src = source;
+ if (getInput(0) && !getInput(0)->filterImage(proxy, source, matrix, &src, loc))
+ return false;
+
+ SkAutoTUnref<SkDevice> device(proxy->createDevice(src.width(), src.height()));
+ SkCanvas canvas(device.get());
+
+ SkAutoTUnref<SkImageFilter> blurFilter(new SkBlurImageFilter(fSigma, fSigma));
+ SkAutoTUnref<SkColorFilter> colorFilter(SkColorFilter::CreateModeFilter(fColor, SkXfermode::kSrcIn_Mode));
+ SkPaint paint;
+ paint.setImageFilter(blurFilter.get());
+ paint.setColorFilter(colorFilter.get());
+ paint.setXfermodeMode(SkXfermode::kSrcOver_Mode);
+ canvas.drawBitmap(src, fDx, fDy, &paint);
+ canvas.drawBitmap(src, 0, 0);
+ *result = device->accessBitmap(false);
+ return true;
+}
diff --git a/effects/SkEmbossMask.cpp b/effects/SkEmbossMask.cpp
new file mode 100644
index 00000000..32e9b232
--- /dev/null
+++ b/effects/SkEmbossMask.cpp
@@ -0,0 +1,163 @@
+
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#include "SkEmbossMask.h"
+#include "SkMath.h"
+
+static inline int nonzero_to_one(int x) {
+#if 0
+ return x != 0;
+#else
+ return ((unsigned)(x | -x)) >> 31;
+#endif
+}
+
+static inline int neq_to_one(int x, int max) {
+#if 0
+ return x != max;
+#else
+ SkASSERT(x >= 0 && x <= max);
+ return ((unsigned)(x - max)) >> 31;
+#endif
+}
+
+static inline int neq_to_mask(int x, int max) {
+#if 0
+ return -(x != max);
+#else
+ SkASSERT(x >= 0 && x <= max);
+ return (x - max) >> 31;
+#endif
+}
+
+static inline unsigned div255(unsigned x) {
+ SkASSERT(x <= (255*255));
+ return x * ((1 << 24) / 255) >> 24;
+}
+
+#define kDelta 32 // small enough to show off angle differences
+
+#include "SkEmbossMask_Table.h"
+
+#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
+
+#include <stdio.h>
+
+void SkEmbossMask_BuildTable() {
+ // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
+
+ FILE* file = ::fopen("SkEmbossMask_Table.h", "w");
+ SkASSERT(file);
+ ::fprintf(file, "#include \"SkTypes.h\"\n\n");
+ ::fprintf(file, "static const U16 gInvSqrtTable[128 * 128] = {\n");
+ for (int dx = 0; dx <= 255/2; dx++) {
+ for (int dy = 0; dy <= 255/2; dy++) {
+ if ((dy & 15) == 0)
+ ::fprintf(file, "\t");
+
+ uint16_t value = SkToU16((1 << 15) / SkSqrt32(dx * dx + dy * dy + kDelta*kDelta/4));
+
+ ::fprintf(file, "0x%04X", value);
+ if (dx * 128 + dy < 128*128-1) {
+ ::fprintf(file, ", ");
+ }
+ if ((dy & 15) == 15) {
+ ::fprintf(file, "\n");
+ }
+ }
+ }
+ ::fprintf(file, "};\n#define kDeltaUsedToBuildTable\t%d\n", kDelta);
+ ::fclose(file);
+}
+
+#endif
+
+void SkEmbossMask::Emboss(SkMask* mask, const SkEmbossMaskFilter::Light& light) {
+ SkASSERT(kDelta == kDeltaUsedToBuildTable);
+
+ SkASSERT(mask->fFormat == SkMask::k3D_Format);
+
+ int specular = light.fSpecular;
+ int ambient = light.fAmbient;
+ SkFixed lx = SkScalarToFixed(light.fDirection[0]);
+ SkFixed ly = SkScalarToFixed(light.fDirection[1]);
+ SkFixed lz = SkScalarToFixed(light.fDirection[2]);
+ SkFixed lz_dot_nz = lz * kDelta;
+ int lz_dot8 = lz >> 8;
+
+ size_t planeSize = mask->computeImageSize();
+ uint8_t* alpha = mask->fImage;
+ uint8_t* multiply = (uint8_t*)alpha + planeSize;
+ uint8_t* additive = multiply + planeSize;
+
+ int rowBytes = mask->fRowBytes;
+ int maxy = mask->fBounds.height() - 1;
+ int maxx = mask->fBounds.width() - 1;
+
+ int prev_row = 0;
+ for (int y = 0; y <= maxy; y++) {
+ int next_row = neq_to_mask(y, maxy) & rowBytes;
+
+ for (int x = 0; x <= maxx; x++) {
+ if (alpha[x]) {
+ int nx = alpha[x + neq_to_one(x, maxx)] - alpha[x - nonzero_to_one(x)];
+ int ny = alpha[x + next_row] - alpha[x - prev_row];
+
+ SkFixed numer = lx * nx + ly * ny + lz_dot_nz;
+ int mul = ambient;
+ int add = 0;
+
+ if (numer > 0) { // preflight when numer/denom will be <= 0
+#if 0
+ int denom = SkSqrt32(nx * nx + ny * ny + kDelta*kDelta);
+ SkFixed dot = numer / denom;
+ dot >>= 8; // now dot is 2^8 instead of 2^16
+#else
+ // can use full numer, but then we need to call SkFixedMul, since
+ // numer is 24 bits, and our table is 12 bits
+
+ // SkFixed dot = SkFixedMul(numer, gTable[]) >> 8
+ SkFixed dot = (unsigned)(numer >> 4) * gInvSqrtTable[(SkAbs32(nx) >> 1 << 7) | (SkAbs32(ny) >> 1)] >> 20;
+#endif
+ mul = SkFastMin32(mul + dot, 255);
+
+ // now for the reflection
+
+ // R = 2 (Light * Normal) Normal - Light
+ // hilite = R * Eye(0, 0, 1)
+
+ int hilite = (2 * dot - lz_dot8) * lz_dot8 >> 8;
+ if (hilite > 0) {
+ // pin hilite to 255, since our fast math is also a little sloppy
+ hilite = SkClampMax(hilite, 255);
+
+ // specular is 4.4
+ // would really like to compute the fractional part of this
+ // and then possibly cache a 256 table for a given specular
+ // value in the light, and just pass that in to this function.
+ add = hilite;
+ for (int i = specular >> 4; i > 0; --i) {
+ add = div255(add * hilite);
+ }
+ }
+ }
+ multiply[x] = SkToU8(mul);
+ additive[x] = SkToU8(add);
+
+ // multiply[x] = 0xFF;
+ // additive[x] = 0;
+ // ((uint8_t*)alpha)[x] = alpha[x] * multiply[x] >> 8;
+ }
+ }
+ alpha += rowBytes;
+ multiply += rowBytes;
+ additive += rowBytes;
+ prev_row = rowBytes;
+ }
+}
diff --git a/effects/SkEmbossMask.h b/effects/SkEmbossMask.h
new file mode 100644
index 00000000..7053be2b
--- /dev/null
+++ b/effects/SkEmbossMask.h
@@ -0,0 +1,20 @@
+
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#ifndef SkEmbossMask_DEFINED
+#define SkEmbossMask_DEFINED
+
+#include "SkEmbossMaskFilter.h"
+
+class SkEmbossMask {
+public:
+ static void Emboss(SkMask* mask, const SkEmbossMaskFilter::Light&);
+};
+
+#endif
diff --git a/effects/SkEmbossMaskFilter.cpp b/effects/SkEmbossMaskFilter.cpp
new file mode 100644
index 00000000..391cba55
--- /dev/null
+++ b/effects/SkEmbossMaskFilter.cpp
@@ -0,0 +1,155 @@
+
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#include "SkEmbossMaskFilter.h"
+#include "SkBlurMaskFilter.h"
+#include "SkBlurMask.h"
+#include "SkEmbossMask.h"
+#include "SkFlattenableBuffers.h"
+#include "SkString.h"
+
+static inline int pin2byte(int n) {
+ if (n < 0) {
+ n = 0;
+ } else if (n > 0xFF) {
+ n = 0xFF;
+ }
+ return n;
+}
+
+SkMaskFilter* SkBlurMaskFilter::CreateEmboss(const SkScalar direction[3],
+ SkScalar ambient, SkScalar specular,
+ SkScalar blurRadius) {
+ if (direction == NULL) {
+ return NULL;
+ }
+
+ // ambient should be 0...1 as a scalar
+ int am = pin2byte(SkScalarToFixed(ambient) >> 8);
+
+ // specular should be 0..15.99 as a scalar
+ int sp = pin2byte(SkScalarToFixed(specular) >> 12);
+
+ SkEmbossMaskFilter::Light light;
+
+ memcpy(light.fDirection, direction, sizeof(light.fDirection));
+ light.fAmbient = SkToU8(am);
+ light.fSpecular = SkToU8(sp);
+
+ return SkNEW_ARGS(SkEmbossMaskFilter, (light, blurRadius));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void normalize(SkScalar v[3]) {
+ SkScalar mag = SkScalarSquare(v[0]) + SkScalarSquare(v[1]) + SkScalarSquare(v[2]);
+ mag = SkScalarSqrt(mag);
+
+ for (int i = 0; i < 3; i++) {
+ v[i] = SkScalarDiv(v[i], mag);
+ }
+}
+
+SkEmbossMaskFilter::SkEmbossMaskFilter(const Light& light, SkScalar blurRadius)
+ : fLight(light), fBlurRadius(blurRadius) {
+ normalize(fLight.fDirection);
+}
+
+SkMask::Format SkEmbossMaskFilter::getFormat() const {
+ return SkMask::k3D_Format;
+}
+
+bool SkEmbossMaskFilter::filterMask(SkMask* dst, const SkMask& src,
+ const SkMatrix& matrix, SkIPoint* margin) const {
+ SkScalar radius = matrix.mapRadius(fBlurRadius);
+
+ if (!SkBlurMask::Blur(dst, src, radius, SkBlurMask::kInner_Style,
+ SkBlurMask::kLow_Quality)) {
+ return false;
+ }
+
+ dst->fFormat = SkMask::k3D_Format;
+ if (margin) {
+ margin->set(SkScalarCeil(radius), SkScalarCeil(radius));
+ }
+
+ if (src.fImage == NULL) {
+ return true;
+ }
+
+ // create a larger buffer for the other two channels (should force fBlur to do this for us)
+
+ {
+ uint8_t* alphaPlane = dst->fImage;
+ size_t planeSize = dst->computeImageSize();
+ if (0 == planeSize) {
+ return false; // too big to allocate, abort
+ }
+ dst->fImage = SkMask::AllocImage(planeSize * 3);
+ memcpy(dst->fImage, alphaPlane, planeSize);
+ SkMask::FreeImage(alphaPlane);
+ }
+
+ // run the light direction through the matrix...
+ Light light = fLight;
+ matrix.mapVectors((SkVector*)(void*)light.fDirection,
+ (SkVector*)(void*)fLight.fDirection, 1);
+
+ // now restore the length of the XY component
+ // cast to SkVector so we can call setLength (this double cast silences alias warnings)
+ SkVector* vec = (SkVector*)(void*)light.fDirection;
+ vec->setLength(light.fDirection[0],
+ light.fDirection[1],
+ SkPoint::Length(fLight.fDirection[0], fLight.fDirection[1]));
+
+ SkEmbossMask::Emboss(dst, light);
+
+ // restore original alpha
+ memcpy(dst->fImage, src.fImage, src.computeImageSize());
+
+ return true;
+}
+
+SkEmbossMaskFilter::SkEmbossMaskFilter(SkFlattenableReadBuffer& buffer)
+ : SkMaskFilter(buffer) {
+ SkASSERT(buffer.getArrayCount() == sizeof(Light));
+ buffer.readByteArray(&fLight);
+ SkASSERT(fLight.fPad == 0); // for the font-cache lookup to be clean
+ fBlurRadius = buffer.readScalar();
+}
+
+void SkEmbossMaskFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+
+ Light tmpLight = fLight;
+ tmpLight.fPad = 0; // for the font-cache lookup to be clean
+ buffer.writeByteArray(&tmpLight, sizeof(tmpLight));
+ buffer.writeScalar(fBlurRadius);
+}
+
+#ifdef SK_DEVELOPER
+void SkEmbossMaskFilter::toString(SkString* str) const {
+ str->append("SkEmbossMaskFilter: (");
+
+ str->append("direction: (");
+ str->appendScalar(fLight.fDirection[0]);
+ str->append(", ");
+ str->appendScalar(fLight.fDirection[1]);
+ str->append(", ");
+ str->appendScalar(fLight.fDirection[2]);
+ str->append(") ");
+
+ str->appendf("ambient: %d specular: %d ",
+ fLight.fAmbient, fLight.fSpecular);
+
+ str->append("blurRadius: ");
+ str->appendScalar(fBlurRadius);
+ str->append(")");
+}
+#endif
diff --git a/effects/SkEmbossMask_Table.h b/effects/SkEmbossMask_Table.h
new file mode 100644
index 00000000..0d331ee1
--- /dev/null
+++ b/effects/SkEmbossMask_Table.h
@@ -0,0 +1,1038 @@
+
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#include "SkTypes.h"
+
+static const uint16_t gInvSqrtTable[128 * 128] = {
+ 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0787, 0x0787, 0x0787, 0x071C, 0x071C, 0x06BC, 0x0666, 0x0666, 0x0618, 0x0618,
+ 0x05D1, 0x0590, 0x0555, 0x0555, 0x051E, 0x04EC, 0x04BD, 0x0492, 0x0492, 0x0469, 0x0444, 0x0421, 0x0400, 0x03E0, 0x03C3, 0x03C3,
+ 0x03A8, 0x038E, 0x0375, 0x035E, 0x0348, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C,
+ 0x028F, 0x0282, 0x0276, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8,
+ 0x01F8, 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199,
+ 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155,
+ 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124,
+ 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0787, 0x0787, 0x0787, 0x071C, 0x071C, 0x06BC, 0x0666, 0x0666, 0x0618, 0x0618,
+ 0x05D1, 0x0590, 0x0555, 0x0555, 0x051E, 0x04EC, 0x04BD, 0x0492, 0x0492, 0x0469, 0x0444, 0x0421, 0x0400, 0x03E0, 0x03C3, 0x03C3,
+ 0x03A8, 0x038E, 0x0375, 0x035E, 0x0348, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C,
+ 0x028F, 0x0282, 0x0276, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8,
+ 0x01F8, 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199,
+ 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155,
+ 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124,
+ 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0787, 0x0787, 0x071C, 0x071C, 0x071C, 0x06BC, 0x0666, 0x0666, 0x0618, 0x05D1,
+ 0x05D1, 0x0590, 0x0555, 0x0555, 0x051E, 0x04EC, 0x04BD, 0x0492, 0x0492, 0x0469, 0x0444, 0x0421, 0x0400, 0x03E0, 0x03C3, 0x03C3,
+ 0x03A8, 0x038E, 0x0375, 0x035E, 0x0348, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C,
+ 0x028F, 0x0282, 0x0276, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8,
+ 0x01F0, 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199,
+ 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155,
+ 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124,
+ 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0787, 0x0787, 0x0787, 0x071C, 0x071C, 0x06BC, 0x06BC, 0x0666, 0x0666, 0x0618, 0x05D1,
+ 0x05D1, 0x0590, 0x0555, 0x051E, 0x051E, 0x04EC, 0x04BD, 0x0492, 0x0469, 0x0469, 0x0444, 0x0421, 0x0400, 0x03E0, 0x03C3, 0x03A8,
+ 0x03A8, 0x038E, 0x0375, 0x035E, 0x0348, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C,
+ 0x028F, 0x0282, 0x0276, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8,
+ 0x01F0, 0x01E9, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199,
+ 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155,
+ 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124,
+ 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0787, 0x0787, 0x0787, 0x071C, 0x071C, 0x06BC, 0x06BC, 0x0666, 0x0618, 0x0618, 0x05D1,
+ 0x05D1, 0x0590, 0x0555, 0x051E, 0x051E, 0x04EC, 0x04BD, 0x0492, 0x0469, 0x0469, 0x0444, 0x0421, 0x0400, 0x03E0, 0x03C3, 0x03A8,
+ 0x038E, 0x038E, 0x0375, 0x035E, 0x0348, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02C8, 0x02C8, 0x02B9, 0x02AA, 0x029C,
+ 0x028F, 0x0282, 0x0276, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8,
+ 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199,
+ 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155,
+ 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124,
+ 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x0800, 0x0800, 0x0800, 0x0787, 0x0787, 0x0787, 0x0787, 0x071C, 0x071C, 0x06BC, 0x06BC, 0x0666, 0x0666, 0x0618, 0x0618, 0x05D1,
+ 0x0590, 0x0590, 0x0555, 0x051E, 0x04EC, 0x04EC, 0x04BD, 0x0492, 0x0469, 0x0444, 0x0444, 0x0421, 0x0400, 0x03E0, 0x03C3, 0x03A8,
+ 0x038E, 0x0375, 0x0375, 0x035E, 0x0348, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x02AA, 0x029C,
+ 0x028F, 0x0282, 0x0276, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8,
+ 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199,
+ 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155,
+ 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124,
+ 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x0787, 0x0787, 0x0787, 0x0787, 0x0787, 0x0787, 0x071C, 0x071C, 0x071C, 0x06BC, 0x06BC, 0x0666, 0x0666, 0x0618, 0x05D1, 0x05D1,
+ 0x0590, 0x0555, 0x0555, 0x051E, 0x04EC, 0x04BD, 0x04BD, 0x0492, 0x0469, 0x0444, 0x0421, 0x0421, 0x0400, 0x03E0, 0x03C3, 0x03A8,
+ 0x038E, 0x0375, 0x035E, 0x035E, 0x0348, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x028F,
+ 0x028F, 0x0282, 0x0276, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8,
+ 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199,
+ 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155,
+ 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124,
+ 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x0787, 0x0787, 0x0787, 0x0787, 0x0787, 0x071C, 0x071C, 0x071C, 0x06BC, 0x06BC, 0x0666, 0x0666, 0x0618, 0x0618, 0x05D1, 0x0590,
+ 0x0590, 0x0555, 0x051E, 0x051E, 0x04EC, 0x04BD, 0x0492, 0x0492, 0x0469, 0x0444, 0x0421, 0x0400, 0x03E0, 0x03E0, 0x03C3, 0x03A8,
+ 0x038E, 0x0375, 0x035E, 0x0348, 0x0333, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x028F,
+ 0x0282, 0x0276, 0x0276, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8,
+ 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A9, 0x01A4, 0x019E, 0x0199,
+ 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155,
+ 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124,
+ 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x0787, 0x0787, 0x071C, 0x071C, 0x071C, 0x071C, 0x071C, 0x06BC, 0x06BC, 0x0666, 0x0666, 0x0618, 0x0618, 0x05D1, 0x05D1, 0x0590,
+ 0x0555, 0x0555, 0x051E, 0x04EC, 0x04EC, 0x04BD, 0x0492, 0x0469, 0x0469, 0x0444, 0x0421, 0x0400, 0x03E0, 0x03C3, 0x03C3, 0x03A8,
+ 0x038E, 0x0375, 0x035E, 0x0348, 0x0333, 0x031F, 0x030C, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x028F,
+ 0x0282, 0x0276, 0x026A, 0x025E, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8,
+ 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194,
+ 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155,
+ 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124,
+ 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x071C, 0x071C, 0x071C, 0x071C, 0x071C, 0x06BC, 0x06BC, 0x06BC, 0x0666, 0x0666, 0x0666, 0x0618, 0x0618, 0x05D1, 0x0590, 0x0590,
+ 0x0555, 0x051E, 0x051E, 0x04EC, 0x04BD, 0x04BD, 0x0492, 0x0469, 0x0444, 0x0421, 0x0421, 0x0400, 0x03E0, 0x03C3, 0x03A8, 0x038E,
+ 0x038E, 0x0375, 0x035E, 0x0348, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x028F,
+ 0x0282, 0x0276, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8,
+ 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194,
+ 0x018F, 0x018A, 0x0186, 0x0181, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155,
+ 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124,
+ 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x071C, 0x071C, 0x071C, 0x06BC, 0x06BC, 0x06BC, 0x06BC, 0x0666, 0x0666, 0x0666, 0x0618, 0x0618, 0x05D1, 0x05D1, 0x0590, 0x0555,
+ 0x0555, 0x051E, 0x04EC, 0x04EC, 0x04BD, 0x0492, 0x0492, 0x0469, 0x0444, 0x0421, 0x0400, 0x0400, 0x03E0, 0x03C3, 0x03A8, 0x038E,
+ 0x0375, 0x035E, 0x035E, 0x0348, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02C8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x028F,
+ 0x0282, 0x0276, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8,
+ 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194,
+ 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155,
+ 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124,
+ 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x06BC, 0x06BC, 0x06BC, 0x06BC, 0x06BC, 0x0666, 0x0666, 0x0666, 0x0618, 0x0618, 0x0618, 0x05D1, 0x05D1, 0x0590, 0x0590, 0x0555,
+ 0x051E, 0x051E, 0x04EC, 0x04BD, 0x04BD, 0x0492, 0x0469, 0x0444, 0x0444, 0x0421, 0x0400, 0x03E0, 0x03C3, 0x03C3, 0x03A8, 0x038E,
+ 0x0375, 0x035E, 0x0348, 0x0333, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x029C, 0x028F,
+ 0x0282, 0x0276, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x0200, 0x01F8,
+ 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194,
+ 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0158, 0x0155,
+ 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124,
+ 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x0666, 0x0666, 0x0666, 0x0666, 0x0666, 0x0666, 0x0666, 0x0618, 0x0618, 0x0618, 0x05D1, 0x05D1, 0x0590, 0x0590, 0x0555, 0x051E,
+ 0x051E, 0x04EC, 0x04EC, 0x04BD, 0x0492, 0x0469, 0x0469, 0x0444, 0x0421, 0x0400, 0x0400, 0x03E0, 0x03C3, 0x03A8, 0x038E, 0x038E,
+ 0x0375, 0x035E, 0x0348, 0x0333, 0x031F, 0x030C, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x028F, 0x0282,
+ 0x0276, 0x0276, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0,
+ 0x01E9, 0x01E1, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194,
+ 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151,
+ 0x014E, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124,
+ 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x0666, 0x0666, 0x0666, 0x0666, 0x0618, 0x0618, 0x0618, 0x0618, 0x05D1, 0x05D1, 0x05D1, 0x0590, 0x0590, 0x0555, 0x0555, 0x051E,
+ 0x04EC, 0x04EC, 0x04BD, 0x0492, 0x0492, 0x0469, 0x0444, 0x0444, 0x0421, 0x0400, 0x03E0, 0x03E0, 0x03C3, 0x03A8, 0x038E, 0x0375,
+ 0x035E, 0x035E, 0x0348, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x028F, 0x0282,
+ 0x0276, 0x026A, 0x025E, 0x0253, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0,
+ 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194,
+ 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151,
+ 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124,
+ 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x0618, 0x0618, 0x0618, 0x0618, 0x0618, 0x0618, 0x05D1, 0x05D1, 0x05D1, 0x0590, 0x0590, 0x0590, 0x0555, 0x0555, 0x051E, 0x04EC,
+ 0x04EC, 0x04BD, 0x04BD, 0x0492, 0x0469, 0x0469, 0x0444, 0x0421, 0x0400, 0x0400, 0x03E0, 0x03C3, 0x03A8, 0x03A8, 0x038E, 0x0375,
+ 0x035E, 0x0348, 0x0333, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02B9, 0x02AA, 0x029C, 0x028F, 0x0282,
+ 0x0276, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0,
+ 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194,
+ 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151,
+ 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121,
+ 0x011F, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x0618, 0x0618, 0x05D1, 0x05D1, 0x05D1, 0x05D1, 0x05D1, 0x0590, 0x0590, 0x0590, 0x0555, 0x0555, 0x051E, 0x051E, 0x04EC, 0x04EC,
+ 0x04BD, 0x04BD, 0x0492, 0x0469, 0x0469, 0x0444, 0x0421, 0x0421, 0x0400, 0x03E0, 0x03C3, 0x03C3, 0x03A8, 0x038E, 0x0375, 0x0375,
+ 0x035E, 0x0348, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02FA, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x028F, 0x028F, 0x0282,
+ 0x0276, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0,
+ 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x0194,
+ 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151,
+ 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121,
+ 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x05D1, 0x05D1, 0x05D1, 0x05D1, 0x05D1, 0x0590, 0x0590, 0x0590, 0x0555, 0x0555, 0x0555, 0x051E, 0x051E, 0x04EC, 0x04EC, 0x04BD,
+ 0x04BD, 0x0492, 0x0492, 0x0469, 0x0444, 0x0444, 0x0421, 0x0400, 0x0400, 0x03E0, 0x03C3, 0x03A8, 0x038E, 0x038E, 0x0375, 0x035E,
+ 0x0348, 0x0333, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x028F, 0x0282, 0x0276,
+ 0x026A, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01F0,
+ 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F,
+ 0x018A, 0x0186, 0x0181, 0x017D, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151,
+ 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121,
+ 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE,
+ 0x0590, 0x0590, 0x0590, 0x0590, 0x0590, 0x0590, 0x0555, 0x0555, 0x0555, 0x051E, 0x051E, 0x051E, 0x04EC, 0x04EC, 0x04BD, 0x04BD,
+ 0x0492, 0x0492, 0x0469, 0x0444, 0x0444, 0x0421, 0x0400, 0x0400, 0x03E0, 0x03C3, 0x03C3, 0x03A8, 0x038E, 0x0375, 0x035E, 0x035E,
+ 0x0348, 0x0333, 0x031F, 0x030C, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x02AA, 0x029C, 0x028F, 0x0282, 0x0276,
+ 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01E9,
+ 0x01E1, 0x01DA, 0x01D4, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F,
+ 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151,
+ 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121,
+ 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE,
+ 0x0555, 0x0555, 0x0555, 0x0555, 0x0555, 0x0555, 0x0555, 0x051E, 0x051E, 0x051E, 0x04EC, 0x04EC, 0x04EC, 0x04BD, 0x04BD, 0x0492,
+ 0x0492, 0x0469, 0x0444, 0x0444, 0x0421, 0x0421, 0x0400, 0x03E0, 0x03C3, 0x03C3, 0x03A8, 0x038E, 0x038E, 0x0375, 0x035E, 0x0348,
+ 0x0333, 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x028F, 0x0282, 0x0282, 0x0276,
+ 0x026A, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01E9,
+ 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F,
+ 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E,
+ 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121,
+ 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE,
+ 0x0555, 0x0555, 0x0555, 0x051E, 0x051E, 0x051E, 0x051E, 0x051E, 0x04EC, 0x04EC, 0x04EC, 0x04BD, 0x04BD, 0x0492, 0x0492, 0x0469,
+ 0x0469, 0x0444, 0x0444, 0x0421, 0x0421, 0x0400, 0x03E0, 0x03E0, 0x03C3, 0x03A8, 0x03A8, 0x038E, 0x0375, 0x035E, 0x035E, 0x0348,
+ 0x0333, 0x031F, 0x030C, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02B9, 0x02AA, 0x029C, 0x028F, 0x0282, 0x0276, 0x026A,
+ 0x025E, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x0200, 0x01F8, 0x01F0, 0x01E9,
+ 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x019E, 0x0199, 0x0194, 0x018F,
+ 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E,
+ 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121,
+ 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE,
+ 0x051E, 0x051E, 0x051E, 0x051E, 0x051E, 0x04EC, 0x04EC, 0x04EC, 0x04EC, 0x04BD, 0x04BD, 0x04BD, 0x0492, 0x0492, 0x0469, 0x0469,
+ 0x0444, 0x0444, 0x0421, 0x0421, 0x0400, 0x03E0, 0x03E0, 0x03C3, 0x03A8, 0x03A8, 0x038E, 0x0375, 0x0375, 0x035E, 0x0348, 0x0333,
+ 0x0333, 0x031F, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x028F, 0x028F, 0x0282, 0x0276, 0x026A,
+ 0x025E, 0x0253, 0x0249, 0x023E, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01E9, 0x01E1,
+ 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F, 0x018A,
+ 0x0186, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E,
+ 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0129, 0x0127, 0x0124, 0x0121,
+ 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE,
+ 0x04EC, 0x04EC, 0x04EC, 0x04EC, 0x04EC, 0x04EC, 0x04BD, 0x04BD, 0x04BD, 0x04BD, 0x0492, 0x0492, 0x0469, 0x0469, 0x0469, 0x0444,
+ 0x0444, 0x0421, 0x0421, 0x0400, 0x03E0, 0x03E0, 0x03C3, 0x03A8, 0x03A8, 0x038E, 0x0375, 0x0375, 0x035E, 0x0348, 0x0348, 0x0333,
+ 0x031F, 0x030C, 0x02FA, 0x02FA, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02B9, 0x02AA, 0x029C, 0x028F, 0x0282, 0x0276, 0x026A, 0x026A,
+ 0x025E, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01E9, 0x01E1,
+ 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F, 0x018A,
+ 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E,
+ 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F,
+ 0x011C, 0x011A, 0x0118, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE,
+ 0x04BD, 0x04BD, 0x04BD, 0x04BD, 0x04BD, 0x04BD, 0x04BD, 0x0492, 0x0492, 0x0492, 0x0492, 0x0469, 0x0469, 0x0444, 0x0444, 0x0421,
+ 0x0421, 0x0400, 0x0400, 0x03E0, 0x03E0, 0x03C3, 0x03C3, 0x03A8, 0x038E, 0x038E, 0x0375, 0x035E, 0x0348, 0x0348, 0x0333, 0x031F,
+ 0x030C, 0x030C, 0x02FA, 0x02E8, 0x02D8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x028F, 0x028F, 0x0282, 0x0276, 0x026A, 0x025E,
+ 0x0253, 0x0249, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x0200, 0x01F8, 0x01F0, 0x01E9, 0x01E1,
+ 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F, 0x018A,
+ 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E,
+ 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F,
+ 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE,
+ 0x0492, 0x0492, 0x0492, 0x0492, 0x0492, 0x0492, 0x0492, 0x0492, 0x0469, 0x0469, 0x0469, 0x0444, 0x0444, 0x0444, 0x0421, 0x0421,
+ 0x0400, 0x0400, 0x03E0, 0x03E0, 0x03C3, 0x03A8, 0x03A8, 0x038E, 0x038E, 0x0375, 0x035E, 0x035E, 0x0348, 0x0333, 0x031F, 0x031F,
+ 0x030C, 0x02FA, 0x02E8, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x02AA, 0x029C, 0x028F, 0x0282, 0x0276, 0x026A, 0x026A, 0x025E,
+ 0x0253, 0x0249, 0x023E, 0x0234, 0x022B, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01E9, 0x01E1, 0x01E1,
+ 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x0194, 0x018F, 0x018A,
+ 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A,
+ 0x0147, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F,
+ 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC,
+ 0x0492, 0x0492, 0x0492, 0x0469, 0x0469, 0x0469, 0x0469, 0x0469, 0x0469, 0x0444, 0x0444, 0x0444, 0x0421, 0x0421, 0x0400, 0x0400,
+ 0x0400, 0x03E0, 0x03C3, 0x03C3, 0x03A8, 0x03A8, 0x038E, 0x038E, 0x0375, 0x035E, 0x035E, 0x0348, 0x0333, 0x0333, 0x031F, 0x030C,
+ 0x02FA, 0x02FA, 0x02E8, 0x02D8, 0x02C8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x028F, 0x028F, 0x0282, 0x0276, 0x026A, 0x025E, 0x0253,
+ 0x0249, 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01E9, 0x01E1, 0x01DA,
+ 0x01D4, 0x01CD, 0x01C7, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F, 0x018A, 0x0186,
+ 0x0181, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A,
+ 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F,
+ 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC,
+ 0x0469, 0x0469, 0x0469, 0x0469, 0x0469, 0x0444, 0x0444, 0x0444, 0x0444, 0x0421, 0x0421, 0x0421, 0x0400, 0x0400, 0x0400, 0x03E0,
+ 0x03E0, 0x03C3, 0x03C3, 0x03A8, 0x03A8, 0x038E, 0x038E, 0x0375, 0x035E, 0x035E, 0x0348, 0x0333, 0x0333, 0x031F, 0x030C, 0x030C,
+ 0x02FA, 0x02E8, 0x02D8, 0x02D8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x029C, 0x028F, 0x0282, 0x0276, 0x026A, 0x026A, 0x025E, 0x0253,
+ 0x0249, 0x023E, 0x0234, 0x022B, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01F0, 0x01E9, 0x01E1, 0x01DA,
+ 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F, 0x018A, 0x0186,
+ 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A,
+ 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0124, 0x0121, 0x011F,
+ 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC,
+ 0x0444, 0x0444, 0x0444, 0x0444, 0x0444, 0x0444, 0x0421, 0x0421, 0x0421, 0x0421, 0x0400, 0x0400, 0x0400, 0x03E0, 0x03E0, 0x03C3,
+ 0x03C3, 0x03C3, 0x03A8, 0x03A8, 0x038E, 0x0375, 0x0375, 0x035E, 0x035E, 0x0348, 0x0333, 0x0333, 0x031F, 0x030C, 0x030C, 0x02FA,
+ 0x02E8, 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02B9, 0x02AA, 0x029C, 0x028F, 0x0282, 0x0282, 0x0276, 0x026A, 0x025E, 0x0253, 0x0249,
+ 0x0249, 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4,
+ 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0199, 0x0194, 0x018F, 0x018A, 0x0186,
+ 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A,
+ 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C,
+ 0x011A, 0x0118, 0x0115, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC,
+ 0x0421, 0x0421, 0x0421, 0x0421, 0x0421, 0x0421, 0x0421, 0x0400, 0x0400, 0x0400, 0x0400, 0x03E0, 0x03E0, 0x03E0, 0x03C3, 0x03C3,
+ 0x03A8, 0x03A8, 0x038E, 0x038E, 0x0375, 0x0375, 0x035E, 0x035E, 0x0348, 0x0333, 0x0333, 0x031F, 0x030C, 0x030C, 0x02FA, 0x02E8,
+ 0x02E8, 0x02D8, 0x02C8, 0x02B9, 0x02B9, 0x02AA, 0x029C, 0x028F, 0x028F, 0x0282, 0x0276, 0x026A, 0x025E, 0x025E, 0x0253, 0x0249,
+ 0x023E, 0x0234, 0x022B, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F8, 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4,
+ 0x01CD, 0x01C7, 0x01C0, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F, 0x018A, 0x0186, 0x0181,
+ 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x0147,
+ 0x0144, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C,
+ 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC,
+ 0x0400, 0x0400, 0x0400, 0x0400, 0x0400, 0x0400, 0x0400, 0x03E0, 0x03E0, 0x03E0, 0x03E0, 0x03C3, 0x03C3, 0x03C3, 0x03A8, 0x03A8,
+ 0x038E, 0x038E, 0x038E, 0x0375, 0x0375, 0x035E, 0x0348, 0x0348, 0x0333, 0x0333, 0x031F, 0x030C, 0x030C, 0x02FA, 0x02E8, 0x02E8,
+ 0x02D8, 0x02C8, 0x02C8, 0x02B9, 0x02AA, 0x029C, 0x029C, 0x028F, 0x0282, 0x0276, 0x0276, 0x026A, 0x025E, 0x0253, 0x0249, 0x023E,
+ 0x023E, 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01DA, 0x01D4,
+ 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F, 0x018A, 0x0186, 0x0181,
+ 0x017D, 0x0178, 0x0174, 0x0170, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x0147,
+ 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C,
+ 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA,
+ 0x03E0, 0x03E0, 0x03E0, 0x03E0, 0x03E0, 0x03E0, 0x03E0, 0x03E0, 0x03C3, 0x03C3, 0x03C3, 0x03C3, 0x03A8, 0x03A8, 0x03A8, 0x038E,
+ 0x038E, 0x0375, 0x0375, 0x035E, 0x035E, 0x0348, 0x0348, 0x0333, 0x0333, 0x031F, 0x030C, 0x030C, 0x02FA, 0x02E8, 0x02E8, 0x02D8,
+ 0x02C8, 0x02C8, 0x02B9, 0x02AA, 0x02AA, 0x029C, 0x028F, 0x0282, 0x0282, 0x0276, 0x026A, 0x025E, 0x0253, 0x0253, 0x0249, 0x023E,
+ 0x0234, 0x022B, 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F8, 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD,
+ 0x01C7, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x0194, 0x018F, 0x018A, 0x0186, 0x0181,
+ 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x0147,
+ 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0124, 0x0121, 0x011F, 0x011C,
+ 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA,
+ 0x03C3, 0x03C3, 0x03C3, 0x03C3, 0x03C3, 0x03C3, 0x03C3, 0x03C3, 0x03C3, 0x03A8, 0x03A8, 0x03A8, 0x038E, 0x038E, 0x038E, 0x0375,
+ 0x0375, 0x035E, 0x035E, 0x035E, 0x0348, 0x0348, 0x0333, 0x031F, 0x031F, 0x030C, 0x030C, 0x02FA, 0x02E8, 0x02E8, 0x02D8, 0x02C8,
+ 0x02C8, 0x02B9, 0x02AA, 0x02AA, 0x029C, 0x028F, 0x028F, 0x0282, 0x0276, 0x026A, 0x025E, 0x025E, 0x0253, 0x0249, 0x023E, 0x0234,
+ 0x0234, 0x022B, 0x0222, 0x0219, 0x0210, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01E9, 0x01E1, 0x01E1, 0x01DA, 0x01D4, 0x01CD,
+ 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D,
+ 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x014A, 0x0147,
+ 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A,
+ 0x0118, 0x0115, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA,
+ 0x03C3, 0x03C3, 0x03C3, 0x03A8, 0x03A8, 0x03A8, 0x03A8, 0x03A8, 0x03A8, 0x038E, 0x038E, 0x038E, 0x038E, 0x0375, 0x0375, 0x0375,
+ 0x035E, 0x035E, 0x0348, 0x0348, 0x0333, 0x0333, 0x031F, 0x031F, 0x030C, 0x030C, 0x02FA, 0x02E8, 0x02E8, 0x02D8, 0x02C8, 0x02C8,
+ 0x02B9, 0x02AA, 0x02AA, 0x029C, 0x028F, 0x028F, 0x0282, 0x0276, 0x026A, 0x026A, 0x025E, 0x0253, 0x0249, 0x0249, 0x023E, 0x0234,
+ 0x022B, 0x0222, 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F8, 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7,
+ 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0199, 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D,
+ 0x0178, 0x0174, 0x0170, 0x016C, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144,
+ 0x0141, 0x013E, 0x013B, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A,
+ 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA,
+ 0x03A8, 0x03A8, 0x03A8, 0x03A8, 0x038E, 0x038E, 0x038E, 0x038E, 0x038E, 0x038E, 0x0375, 0x0375, 0x0375, 0x035E, 0x035E, 0x035E,
+ 0x0348, 0x0348, 0x0333, 0x0333, 0x0333, 0x031F, 0x030C, 0x030C, 0x02FA, 0x02FA, 0x02E8, 0x02E8, 0x02D8, 0x02C8, 0x02C8, 0x02B9,
+ 0x02AA, 0x02AA, 0x029C, 0x028F, 0x028F, 0x0282, 0x0276, 0x0276, 0x026A, 0x025E, 0x0253, 0x0253, 0x0249, 0x023E, 0x0234, 0x022B,
+ 0x022B, 0x0222, 0x0219, 0x0210, 0x0208, 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01CD, 0x01C7,
+ 0x01C0, 0x01BA, 0x01B4, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F, 0x018A, 0x0186, 0x0186, 0x0181, 0x017D,
+ 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144,
+ 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A,
+ 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FC, 0x00FA,
+ 0x038E, 0x038E, 0x038E, 0x038E, 0x038E, 0x0375, 0x0375, 0x0375, 0x0375, 0x0375, 0x035E, 0x035E, 0x035E, 0x035E, 0x0348, 0x0348,
+ 0x0333, 0x0333, 0x0333, 0x031F, 0x031F, 0x030C, 0x030C, 0x02FA, 0x02FA, 0x02E8, 0x02E8, 0x02D8, 0x02C8, 0x02C8, 0x02B9, 0x02AA,
+ 0x02AA, 0x029C, 0x028F, 0x028F, 0x0282, 0x0276, 0x0276, 0x026A, 0x025E, 0x0253, 0x0253, 0x0249, 0x023E, 0x0234, 0x0234, 0x022B,
+ 0x0222, 0x0219, 0x0210, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C7,
+ 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x019E, 0x0199, 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178,
+ 0x0174, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x014A, 0x0147, 0x0144,
+ 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011C, 0x011A,
+ 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8,
+ 0x0375, 0x0375, 0x0375, 0x0375, 0x0375, 0x0375, 0x035E, 0x035E, 0x035E, 0x035E, 0x035E, 0x0348, 0x0348, 0x0348, 0x0333, 0x0333,
+ 0x0333, 0x031F, 0x031F, 0x030C, 0x030C, 0x02FA, 0x02FA, 0x02E8, 0x02E8, 0x02D8, 0x02D8, 0x02C8, 0x02C8, 0x02B9, 0x02AA, 0x02AA,
+ 0x029C, 0x028F, 0x028F, 0x0282, 0x0276, 0x0276, 0x026A, 0x025E, 0x025E, 0x0253, 0x0249, 0x023E, 0x023E, 0x0234, 0x022B, 0x0222,
+ 0x0222, 0x0219, 0x0210, 0x0208, 0x0200, 0x0200, 0x01F8, 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0,
+ 0x01BA, 0x01B4, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178,
+ 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141,
+ 0x013E, 0x013B, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118,
+ 0x0115, 0x0113, 0x0111, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8,
+ 0x035E, 0x035E, 0x035E, 0x035E, 0x035E, 0x035E, 0x035E, 0x0348, 0x0348, 0x0348, 0x0348, 0x0333, 0x0333, 0x0333, 0x0333, 0x031F,
+ 0x031F, 0x030C, 0x030C, 0x030C, 0x02FA, 0x02FA, 0x02E8, 0x02E8, 0x02D8, 0x02D8, 0x02C8, 0x02B9, 0x02B9, 0x02AA, 0x02AA, 0x029C,
+ 0x028F, 0x028F, 0x0282, 0x0276, 0x0276, 0x026A, 0x025E, 0x025E, 0x0253, 0x0249, 0x0249, 0x023E, 0x0234, 0x022B, 0x022B, 0x0222,
+ 0x0219, 0x0210, 0x0208, 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01E9, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C7, 0x01C0,
+ 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x019E, 0x0199, 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x017D, 0x0178,
+ 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0155, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141,
+ 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118,
+ 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8,
+ 0x0348, 0x0348, 0x0348, 0x0348, 0x0348, 0x0348, 0x0348, 0x0333, 0x0333, 0x0333, 0x0333, 0x0333, 0x031F, 0x031F, 0x031F, 0x030C,
+ 0x030C, 0x030C, 0x02FA, 0x02FA, 0x02E8, 0x02E8, 0x02D8, 0x02D8, 0x02C8, 0x02C8, 0x02B9, 0x02B9, 0x02AA, 0x02AA, 0x029C, 0x028F,
+ 0x028F, 0x0282, 0x0276, 0x0276, 0x026A, 0x025E, 0x025E, 0x0253, 0x0249, 0x0249, 0x023E, 0x0234, 0x022B, 0x022B, 0x0222, 0x0219,
+ 0x0210, 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA,
+ 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174,
+ 0x0170, 0x016C, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141,
+ 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011F, 0x011C, 0x011A, 0x0118,
+ 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F8,
+ 0x0333, 0x0333, 0x0333, 0x0333, 0x0333, 0x0333, 0x0333, 0x0333, 0x031F, 0x031F, 0x031F, 0x031F, 0x030C, 0x030C, 0x030C, 0x02FA,
+ 0x02FA, 0x02FA, 0x02E8, 0x02E8, 0x02D8, 0x02D8, 0x02D8, 0x02C8, 0x02C8, 0x02B9, 0x02B9, 0x02AA, 0x029C, 0x029C, 0x028F, 0x028F,
+ 0x0282, 0x0276, 0x0276, 0x026A, 0x025E, 0x025E, 0x0253, 0x0249, 0x0249, 0x023E, 0x0234, 0x0234, 0x022B, 0x0222, 0x0219, 0x0219,
+ 0x0210, 0x0208, 0x0200, 0x01F8, 0x01F8, 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01C0, 0x01BA,
+ 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x019E, 0x0199, 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x017D, 0x0178, 0x0174,
+ 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E,
+ 0x013B, 0x0138, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115,
+ 0x0113, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6,
+ 0x031F, 0x031F, 0x031F, 0x031F, 0x031F, 0x031F, 0x031F, 0x031F, 0x030C, 0x030C, 0x030C, 0x030C, 0x030C, 0x02FA, 0x02FA, 0x02FA,
+ 0x02E8, 0x02E8, 0x02E8, 0x02D8, 0x02D8, 0x02C8, 0x02C8, 0x02B9, 0x02B9, 0x02AA, 0x02AA, 0x029C, 0x029C, 0x028F, 0x028F, 0x0282,
+ 0x0276, 0x0276, 0x026A, 0x025E, 0x025E, 0x0253, 0x0249, 0x0249, 0x023E, 0x0234, 0x0234, 0x022B, 0x0222, 0x0219, 0x0219, 0x0210,
+ 0x0208, 0x0200, 0x0200, 0x01F8, 0x01F0, 0x01E9, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01B4,
+ 0x01AF, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170,
+ 0x016C, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E,
+ 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115,
+ 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6,
+ 0x030C, 0x030C, 0x030C, 0x030C, 0x030C, 0x030C, 0x030C, 0x030C, 0x030C, 0x02FA, 0x02FA, 0x02FA, 0x02FA, 0x02E8, 0x02E8, 0x02E8,
+ 0x02D8, 0x02D8, 0x02D8, 0x02C8, 0x02C8, 0x02B9, 0x02B9, 0x02AA, 0x02AA, 0x029C, 0x029C, 0x028F, 0x028F, 0x0282, 0x0282, 0x0276,
+ 0x0276, 0x026A, 0x025E, 0x025E, 0x0253, 0x0249, 0x0249, 0x023E, 0x0234, 0x0234, 0x022B, 0x0222, 0x0222, 0x0219, 0x0210, 0x0208,
+ 0x0208, 0x0200, 0x01F8, 0x01F0, 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01BA, 0x01B4,
+ 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0199, 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x017D, 0x0178, 0x0174, 0x0170,
+ 0x016C, 0x0168, 0x0164, 0x0160, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013E,
+ 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011C, 0x011A, 0x0118, 0x0115,
+ 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00F8, 0x00F6,
+ 0x02FA, 0x02FA, 0x02FA, 0x02FA, 0x02FA, 0x02FA, 0x02FA, 0x02FA, 0x02FA, 0x02E8, 0x02E8, 0x02E8, 0x02E8, 0x02D8, 0x02D8, 0x02D8,
+ 0x02D8, 0x02C8, 0x02C8, 0x02B9, 0x02B9, 0x02B9, 0x02AA, 0x02AA, 0x029C, 0x029C, 0x028F, 0x028F, 0x0282, 0x0282, 0x0276, 0x026A,
+ 0x026A, 0x025E, 0x025E, 0x0253, 0x0249, 0x0249, 0x023E, 0x0234, 0x0234, 0x022B, 0x0222, 0x0222, 0x0219, 0x0210, 0x0208, 0x0208,
+ 0x0200, 0x01F8, 0x01F0, 0x01F0, 0x01E9, 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01C0, 0x01BA, 0x01B4, 0x01AF,
+ 0x01A9, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F, 0x018A, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x0170,
+ 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B,
+ 0x0138, 0x0135, 0x0132, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113,
+ 0x0111, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4,
+ 0x02E8, 0x02E8, 0x02E8, 0x02E8, 0x02E8, 0x02E8, 0x02E8, 0x02E8, 0x02E8, 0x02E8, 0x02D8, 0x02D8, 0x02D8, 0x02D8, 0x02C8, 0x02C8,
+ 0x02C8, 0x02B9, 0x02B9, 0x02B9, 0x02AA, 0x02AA, 0x029C, 0x029C, 0x028F, 0x028F, 0x0282, 0x0282, 0x0276, 0x0276, 0x026A, 0x026A,
+ 0x025E, 0x0253, 0x0253, 0x0249, 0x0249, 0x023E, 0x0234, 0x0234, 0x022B, 0x0222, 0x0222, 0x0219, 0x0210, 0x0210, 0x0208, 0x0200,
+ 0x01F8, 0x01F8, 0x01F0, 0x01E9, 0x01E1, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01AF,
+ 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0194, 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C,
+ 0x0168, 0x0164, 0x0160, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013B,
+ 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113,
+ 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4,
+ 0x02E8, 0x02E8, 0x02E8, 0x02D8, 0x02D8, 0x02D8, 0x02D8, 0x02D8, 0x02D8, 0x02D8, 0x02C8, 0x02C8, 0x02C8, 0x02C8, 0x02B9, 0x02B9,
+ 0x02B9, 0x02AA, 0x02AA, 0x02AA, 0x029C, 0x029C, 0x028F, 0x028F, 0x028F, 0x0282, 0x0282, 0x0276, 0x0276, 0x026A, 0x025E, 0x025E,
+ 0x0253, 0x0253, 0x0249, 0x0249, 0x023E, 0x0234, 0x0234, 0x022B, 0x0222, 0x0222, 0x0219, 0x0210, 0x0210, 0x0208, 0x0200, 0x01F8,
+ 0x01F8, 0x01F0, 0x01E9, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01CD, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01BA, 0x01B4, 0x01AF, 0x01A9,
+ 0x01A4, 0x019E, 0x019E, 0x0199, 0x0194, 0x018F, 0x018A, 0x0186, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x016C,
+ 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138,
+ 0x0135, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x011A, 0x0118, 0x0115, 0x0113,
+ 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4,
+ 0x02D8, 0x02D8, 0x02D8, 0x02D8, 0x02C8, 0x02C8, 0x02C8, 0x02C8, 0x02C8, 0x02C8, 0x02C8, 0x02B9, 0x02B9, 0x02B9, 0x02B9, 0x02AA,
+ 0x02AA, 0x02AA, 0x029C, 0x029C, 0x028F, 0x028F, 0x028F, 0x0282, 0x0282, 0x0276, 0x0276, 0x026A, 0x026A, 0x025E, 0x025E, 0x0253,
+ 0x0253, 0x0249, 0x023E, 0x023E, 0x0234, 0x0234, 0x022B, 0x0222, 0x0222, 0x0219, 0x0210, 0x0210, 0x0208, 0x0200, 0x0200, 0x01F8,
+ 0x01F0, 0x01E9, 0x01E9, 0x01E1, 0x01DA, 0x01D4, 0x01D4, 0x01CD, 0x01C7, 0x01C0, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A9,
+ 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168,
+ 0x0164, 0x0160, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138,
+ 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111,
+ 0x010E, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2,
+ 0x02C8, 0x02C8, 0x02C8, 0x02C8, 0x02C8, 0x02B9, 0x02B9, 0x02B9, 0x02B9, 0x02B9, 0x02B9, 0x02AA, 0x02AA, 0x02AA, 0x02AA, 0x029C,
+ 0x029C, 0x029C, 0x028F, 0x028F, 0x028F, 0x0282, 0x0282, 0x0276, 0x0276, 0x026A, 0x026A, 0x025E, 0x025E, 0x0253, 0x0253, 0x0249,
+ 0x0249, 0x023E, 0x023E, 0x0234, 0x022B, 0x022B, 0x0222, 0x0222, 0x0219, 0x0210, 0x0210, 0x0208, 0x0200, 0x0200, 0x01F8, 0x01F0,
+ 0x01E9, 0x01E9, 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A4,
+ 0x019E, 0x0199, 0x0199, 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x016C, 0x0168,
+ 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0138,
+ 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111,
+ 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2,
+ 0x02B9, 0x02B9, 0x02B9, 0x02B9, 0x02B9, 0x02AA, 0x02AA, 0x02AA, 0x02AA, 0x02AA, 0x02AA, 0x029C, 0x029C, 0x029C, 0x029C, 0x028F,
+ 0x028F, 0x028F, 0x0282, 0x0282, 0x0282, 0x0276, 0x0276, 0x026A, 0x026A, 0x026A, 0x025E, 0x025E, 0x0253, 0x0253, 0x0249, 0x0249,
+ 0x023E, 0x0234, 0x0234, 0x022B, 0x022B, 0x0222, 0x0219, 0x0219, 0x0210, 0x0210, 0x0208, 0x0200, 0x0200, 0x01F8, 0x01F0, 0x01E9,
+ 0x01E9, 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01CD, 0x01C7, 0x01C7, 0x01C0, 0x01BA, 0x01B4, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x019E,
+ 0x019E, 0x0199, 0x0194, 0x018F, 0x018A, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164,
+ 0x0160, 0x015C, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135,
+ 0x0132, 0x012F, 0x012C, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0113, 0x0111,
+ 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2,
+ 0x02AA, 0x02AA, 0x02AA, 0x02AA, 0x02AA, 0x02AA, 0x029C, 0x029C, 0x029C, 0x029C, 0x029C, 0x029C, 0x028F, 0x028F, 0x028F, 0x028F,
+ 0x0282, 0x0282, 0x0282, 0x0276, 0x0276, 0x026A, 0x026A, 0x026A, 0x025E, 0x025E, 0x0253, 0x0253, 0x0249, 0x0249, 0x023E, 0x023E,
+ 0x0234, 0x0234, 0x022B, 0x022B, 0x0222, 0x0219, 0x0219, 0x0210, 0x0208, 0x0208, 0x0200, 0x0200, 0x01F8, 0x01F0, 0x01F0, 0x01E9,
+ 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01CD, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x01A4, 0x019E,
+ 0x0199, 0x0194, 0x018F, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0168, 0x0164,
+ 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x013B, 0x0138, 0x0135,
+ 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E,
+ 0x010C, 0x010A, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0,
+ 0x029C, 0x029C, 0x029C, 0x029C, 0x029C, 0x029C, 0x028F, 0x028F, 0x028F, 0x028F, 0x028F, 0x028F, 0x0282, 0x0282, 0x0282, 0x0282,
+ 0x0276, 0x0276, 0x0276, 0x026A, 0x026A, 0x026A, 0x025E, 0x025E, 0x0253, 0x0253, 0x0249, 0x0249, 0x023E, 0x023E, 0x0234, 0x0234,
+ 0x022B, 0x022B, 0x0222, 0x0222, 0x0219, 0x0219, 0x0210, 0x0208, 0x0208, 0x0200, 0x01F8, 0x01F8, 0x01F0, 0x01E9, 0x01E9, 0x01E1,
+ 0x01DA, 0x01DA, 0x01D4, 0x01CD, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01BA, 0x01B4, 0x01AF, 0x01A9, 0x01A9, 0x01A4, 0x019E, 0x0199,
+ 0x0194, 0x0194, 0x018F, 0x018A, 0x0186, 0x0181, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160,
+ 0x015C, 0x0158, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132,
+ 0x012F, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E,
+ 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0,
+ 0x028F, 0x028F, 0x028F, 0x028F, 0x028F, 0x028F, 0x028F, 0x0282, 0x0282, 0x0282, 0x0282, 0x0282, 0x0276, 0x0276, 0x0276, 0x0276,
+ 0x026A, 0x026A, 0x026A, 0x025E, 0x025E, 0x025E, 0x0253, 0x0253, 0x0249, 0x0249, 0x0249, 0x023E, 0x023E, 0x0234, 0x0234, 0x022B,
+ 0x022B, 0x0222, 0x0222, 0x0219, 0x0210, 0x0210, 0x0208, 0x0208, 0x0200, 0x01F8, 0x01F8, 0x01F0, 0x01E9, 0x01E9, 0x01E1, 0x01DA,
+ 0x01DA, 0x01D4, 0x01CD, 0x01CD, 0x01C7, 0x01C0, 0x01BA, 0x01BA, 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x0199, 0x0199,
+ 0x0194, 0x018F, 0x018A, 0x018A, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0164, 0x0160,
+ 0x015C, 0x0158, 0x0155, 0x0151, 0x014E, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x013B, 0x0138, 0x0135, 0x0132,
+ 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010E,
+ 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F2, 0x00F0,
+ 0x0282, 0x0282, 0x0282, 0x0282, 0x0282, 0x0282, 0x0282, 0x0276, 0x0276, 0x0276, 0x0276, 0x0276, 0x0276, 0x026A, 0x026A, 0x026A,
+ 0x026A, 0x025E, 0x025E, 0x025E, 0x0253, 0x0253, 0x0249, 0x0249, 0x0249, 0x023E, 0x023E, 0x0234, 0x0234, 0x022B, 0x022B, 0x0222,
+ 0x0222, 0x0219, 0x0219, 0x0210, 0x0210, 0x0208, 0x0200, 0x0200, 0x01F8, 0x01F8, 0x01F0, 0x01E9, 0x01E9, 0x01E1, 0x01DA, 0x01DA,
+ 0x01D4, 0x01CD, 0x01CD, 0x01C7, 0x01C0, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A4, 0x019E, 0x019E, 0x0199, 0x0194,
+ 0x018F, 0x018F, 0x018A, 0x0186, 0x0181, 0x017D, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0168, 0x0164, 0x0160, 0x015C,
+ 0x0158, 0x0155, 0x0155, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F,
+ 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C,
+ 0x010A, 0x0108, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00EF,
+ 0x0276, 0x0276, 0x0276, 0x0276, 0x0276, 0x0276, 0x0276, 0x0276, 0x026A, 0x026A, 0x026A, 0x026A, 0x026A, 0x025E, 0x025E, 0x025E,
+ 0x025E, 0x0253, 0x0253, 0x0253, 0x0249, 0x0249, 0x0249, 0x023E, 0x023E, 0x0234, 0x0234, 0x022B, 0x022B, 0x022B, 0x0222, 0x0222,
+ 0x0219, 0x0210, 0x0210, 0x0208, 0x0208, 0x0200, 0x0200, 0x01F8, 0x01F0, 0x01F0, 0x01E9, 0x01E9, 0x01E1, 0x01DA, 0x01DA, 0x01D4,
+ 0x01CD, 0x01CD, 0x01C7, 0x01C0, 0x01C0, 0x01BA, 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A4, 0x01A4, 0x019E, 0x0199, 0x0194, 0x018F,
+ 0x018F, 0x018A, 0x0186, 0x0181, 0x0181, 0x017D, 0x0178, 0x0174, 0x0170, 0x0170, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x015C,
+ 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0138, 0x0135, 0x0132, 0x012F,
+ 0x012C, 0x0129, 0x0127, 0x0124, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x0111, 0x010E, 0x010C,
+ 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00EF,
+ 0x026A, 0x026A, 0x026A, 0x026A, 0x026A, 0x026A, 0x026A, 0x026A, 0x025E, 0x025E, 0x025E, 0x025E, 0x025E, 0x0253, 0x0253, 0x0253,
+ 0x0253, 0x0249, 0x0249, 0x0249, 0x023E, 0x023E, 0x023E, 0x0234, 0x0234, 0x022B, 0x022B, 0x022B, 0x0222, 0x0222, 0x0219, 0x0219,
+ 0x0210, 0x0210, 0x0208, 0x0208, 0x0200, 0x01F8, 0x01F8, 0x01F0, 0x01F0, 0x01E9, 0x01E9, 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01CD,
+ 0x01CD, 0x01C7, 0x01C0, 0x01C0, 0x01BA, 0x01B4, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x01A4, 0x019E, 0x0199, 0x0194, 0x0194, 0x018F,
+ 0x018A, 0x0186, 0x0186, 0x0181, 0x017D, 0x0178, 0x0174, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0164, 0x0160, 0x015C, 0x0158,
+ 0x0155, 0x0151, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C,
+ 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A,
+ 0x0108, 0x0106, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00EF,
+ 0x025E, 0x025E, 0x025E, 0x025E, 0x025E, 0x025E, 0x025E, 0x025E, 0x025E, 0x0253, 0x0253, 0x0253, 0x0253, 0x0253, 0x0249, 0x0249,
+ 0x0249, 0x023E, 0x023E, 0x023E, 0x023E, 0x0234, 0x0234, 0x022B, 0x022B, 0x022B, 0x0222, 0x0222, 0x0219, 0x0219, 0x0210, 0x0210,
+ 0x0208, 0x0208, 0x0200, 0x0200, 0x01F8, 0x01F8, 0x01F0, 0x01F0, 0x01E9, 0x01E1, 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01CD, 0x01CD,
+ 0x01C7, 0x01C0, 0x01C0, 0x01BA, 0x01B4, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x01A4, 0x019E, 0x0199, 0x0199, 0x0194, 0x018F, 0x018A,
+ 0x0186, 0x0186, 0x0181, 0x017D, 0x0178, 0x0178, 0x0174, 0x0170, 0x016C, 0x0168, 0x0168, 0x0164, 0x0160, 0x015C, 0x0158, 0x0158,
+ 0x0155, 0x0151, 0x014E, 0x014A, 0x0147, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0135, 0x0132, 0x012F, 0x012C,
+ 0x0129, 0x0127, 0x0124, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x0111, 0x010E, 0x010C, 0x010A,
+ 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00ED,
+ 0x0253, 0x0253, 0x0253, 0x0253, 0x0253, 0x0253, 0x0253, 0x0253, 0x0253, 0x0249, 0x0249, 0x0249, 0x0249, 0x0249, 0x023E, 0x023E,
+ 0x023E, 0x023E, 0x0234, 0x0234, 0x0234, 0x022B, 0x022B, 0x022B, 0x0222, 0x0222, 0x0219, 0x0219, 0x0210, 0x0210, 0x0210, 0x0208,
+ 0x0208, 0x0200, 0x0200, 0x01F8, 0x01F0, 0x01F0, 0x01E9, 0x01E9, 0x01E1, 0x01E1, 0x01DA, 0x01D4, 0x01D4, 0x01CD, 0x01CD, 0x01C7,
+ 0x01C0, 0x01C0, 0x01BA, 0x01B4, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x01A4, 0x019E, 0x0199, 0x0199, 0x0194, 0x018F, 0x018A, 0x018A,
+ 0x0186, 0x0181, 0x017D, 0x017D, 0x0178, 0x0174, 0x0170, 0x016C, 0x016C, 0x0168, 0x0164, 0x0160, 0x015C, 0x015C, 0x0158, 0x0155,
+ 0x0151, 0x014E, 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x012C,
+ 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108,
+ 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00ED,
+ 0x0249, 0x0249, 0x0249, 0x0249, 0x0249, 0x0249, 0x0249, 0x0249, 0x0249, 0x023E, 0x023E, 0x023E, 0x023E, 0x023E, 0x0234, 0x0234,
+ 0x0234, 0x0234, 0x022B, 0x022B, 0x022B, 0x0222, 0x0222, 0x0222, 0x0219, 0x0219, 0x0210, 0x0210, 0x0210, 0x0208, 0x0208, 0x0200,
+ 0x0200, 0x01F8, 0x01F8, 0x01F0, 0x01F0, 0x01E9, 0x01E9, 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01D4, 0x01CD, 0x01C7, 0x01C7, 0x01C0,
+ 0x01BA, 0x01BA, 0x01B4, 0x01B4, 0x01AF, 0x01A9, 0x01A4, 0x01A4, 0x019E, 0x0199, 0x0199, 0x0194, 0x018F, 0x018A, 0x018A, 0x0186,
+ 0x0181, 0x017D, 0x017D, 0x0178, 0x0174, 0x0170, 0x0170, 0x016C, 0x0168, 0x0164, 0x0164, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151,
+ 0x0151, 0x014E, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x0132, 0x012F, 0x012C, 0x0129,
+ 0x0127, 0x0124, 0x0121, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108,
+ 0x0106, 0x0104, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00ED, 0x00ED,
+ 0x023E, 0x023E, 0x023E, 0x023E, 0x023E, 0x023E, 0x023E, 0x023E, 0x023E, 0x023E, 0x0234, 0x0234, 0x0234, 0x0234, 0x0234, 0x022B,
+ 0x022B, 0x022B, 0x0222, 0x0222, 0x0222, 0x0219, 0x0219, 0x0219, 0x0210, 0x0210, 0x0210, 0x0208, 0x0208, 0x0200, 0x0200, 0x01F8,
+ 0x01F8, 0x01F0, 0x01F0, 0x01E9, 0x01E9, 0x01E1, 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01CD, 0x01CD, 0x01C7, 0x01C7, 0x01C0, 0x01BA,
+ 0x01BA, 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A4, 0x01A4, 0x019E, 0x0199, 0x0199, 0x0194, 0x018F, 0x018F, 0x018A, 0x0186, 0x0181,
+ 0x0181, 0x017D, 0x0178, 0x0174, 0x0174, 0x0170, 0x016C, 0x0168, 0x0164, 0x0164, 0x0160, 0x015C, 0x0158, 0x0158, 0x0155, 0x0151,
+ 0x014E, 0x014A, 0x0147, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0129,
+ 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0108,
+ 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00ED, 0x00EB,
+ 0x0234, 0x0234, 0x0234, 0x0234, 0x0234, 0x0234, 0x0234, 0x0234, 0x0234, 0x0234, 0x022B, 0x022B, 0x022B, 0x022B, 0x022B, 0x0222,
+ 0x0222, 0x0222, 0x0222, 0x0219, 0x0219, 0x0219, 0x0210, 0x0210, 0x0210, 0x0208, 0x0208, 0x0200, 0x0200, 0x01F8, 0x01F8, 0x01F8,
+ 0x01F0, 0x01F0, 0x01E9, 0x01E9, 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01D4, 0x01CD, 0x01CD, 0x01C7, 0x01C7, 0x01C0, 0x01BA, 0x01BA,
+ 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A4, 0x01A4, 0x019E, 0x0199, 0x0199, 0x0194, 0x018F, 0x018F, 0x018A, 0x0186, 0x0181, 0x0181,
+ 0x017D, 0x0178, 0x0174, 0x0174, 0x0170, 0x016C, 0x0168, 0x0168, 0x0164, 0x0160, 0x015C, 0x015C, 0x0158, 0x0155, 0x0151, 0x014E,
+ 0x014E, 0x014A, 0x0147, 0x0144, 0x0141, 0x013E, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012F, 0x012C, 0x0129, 0x0127,
+ 0x0124, 0x0121, 0x011F, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106,
+ 0x0104, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00EB,
+ 0x022B, 0x022B, 0x022B, 0x022B, 0x022B, 0x022B, 0x022B, 0x022B, 0x022B, 0x022B, 0x0222, 0x0222, 0x0222, 0x0222, 0x0222, 0x0219,
+ 0x0219, 0x0219, 0x0219, 0x0210, 0x0210, 0x0210, 0x0208, 0x0208, 0x0208, 0x0200, 0x0200, 0x01F8, 0x01F8, 0x01F8, 0x01F0, 0x01F0,
+ 0x01E9, 0x01E9, 0x01E1, 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01D4, 0x01CD, 0x01C7, 0x01C7, 0x01C0, 0x01C0, 0x01BA, 0x01BA, 0x01B4,
+ 0x01AF, 0x01AF, 0x01A9, 0x01A4, 0x01A4, 0x019E, 0x0199, 0x0199, 0x0194, 0x018F, 0x018F, 0x018A, 0x0186, 0x0181, 0x0181, 0x017D,
+ 0x0178, 0x0178, 0x0174, 0x0170, 0x016C, 0x016C, 0x0168, 0x0164, 0x0160, 0x0160, 0x015C, 0x0158, 0x0155, 0x0151, 0x0151, 0x014E,
+ 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0127,
+ 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106,
+ 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA,
+ 0x0222, 0x0222, 0x0222, 0x0222, 0x0222, 0x0222, 0x0222, 0x0222, 0x0222, 0x0222, 0x0222, 0x0219, 0x0219, 0x0219, 0x0219, 0x0210,
+ 0x0210, 0x0210, 0x0210, 0x0208, 0x0208, 0x0208, 0x0200, 0x0200, 0x0200, 0x01F8, 0x01F8, 0x01F8, 0x01F0, 0x01F0, 0x01E9, 0x01E9,
+ 0x01E1, 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01D4, 0x01CD, 0x01CD, 0x01C7, 0x01C7, 0x01C0, 0x01C0, 0x01BA, 0x01B4, 0x01B4, 0x01AF,
+ 0x01AF, 0x01A9, 0x01A4, 0x01A4, 0x019E, 0x0199, 0x0199, 0x0194, 0x018F, 0x018F, 0x018A, 0x0186, 0x0186, 0x0181, 0x017D, 0x0178,
+ 0x0178, 0x0174, 0x0170, 0x016C, 0x016C, 0x0168, 0x0164, 0x0160, 0x0160, 0x015C, 0x0158, 0x0155, 0x0155, 0x0151, 0x014E, 0x014A,
+ 0x0147, 0x0147, 0x0144, 0x0141, 0x013E, 0x013B, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x012C, 0x0129, 0x0127, 0x0124,
+ 0x0121, 0x011F, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104,
+ 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA,
+ 0x0219, 0x0219, 0x0219, 0x0219, 0x0219, 0x0219, 0x0219, 0x0219, 0x0219, 0x0219, 0x0219, 0x0210, 0x0210, 0x0210, 0x0210, 0x0210,
+ 0x0208, 0x0208, 0x0208, 0x0200, 0x0200, 0x0200, 0x0200, 0x01F8, 0x01F8, 0x01F0, 0x01F0, 0x01F0, 0x01E9, 0x01E9, 0x01E1, 0x01E1,
+ 0x01DA, 0x01DA, 0x01DA, 0x01D4, 0x01D4, 0x01CD, 0x01CD, 0x01C7, 0x01C0, 0x01C0, 0x01BA, 0x01BA, 0x01B4, 0x01B4, 0x01AF, 0x01A9,
+ 0x01A9, 0x01A4, 0x01A4, 0x019E, 0x0199, 0x0199, 0x0194, 0x018F, 0x018F, 0x018A, 0x0186, 0x0186, 0x0181, 0x017D, 0x0178, 0x0178,
+ 0x0174, 0x0170, 0x0170, 0x016C, 0x0168, 0x0164, 0x0164, 0x0160, 0x015C, 0x0158, 0x0158, 0x0155, 0x0151, 0x014E, 0x014A, 0x014A,
+ 0x0147, 0x0144, 0x0141, 0x013E, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0124,
+ 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104,
+ 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00EA,
+ 0x0210, 0x0210, 0x0210, 0x0210, 0x0210, 0x0210, 0x0210, 0x0210, 0x0210, 0x0210, 0x0210, 0x0208, 0x0208, 0x0208, 0x0208, 0x0208,
+ 0x0200, 0x0200, 0x0200, 0x0200, 0x01F8, 0x01F8, 0x01F8, 0x01F0, 0x01F0, 0x01F0, 0x01E9, 0x01E9, 0x01E1, 0x01E1, 0x01E1, 0x01DA,
+ 0x01DA, 0x01D4, 0x01D4, 0x01CD, 0x01CD, 0x01C7, 0x01C7, 0x01C0, 0x01C0, 0x01BA, 0x01BA, 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A9,
+ 0x01A4, 0x019E, 0x019E, 0x0199, 0x0199, 0x0194, 0x018F, 0x018F, 0x018A, 0x0186, 0x0186, 0x0181, 0x017D, 0x0178, 0x0178, 0x0174,
+ 0x0170, 0x0170, 0x016C, 0x0168, 0x0164, 0x0164, 0x0160, 0x015C, 0x0158, 0x0158, 0x0155, 0x0151, 0x014E, 0x014E, 0x014A, 0x0147,
+ 0x0144, 0x0141, 0x0141, 0x013E, 0x013B, 0x0138, 0x0135, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0129, 0x0127, 0x0124, 0x0121,
+ 0x011F, 0x011C, 0x011A, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102,
+ 0x0100, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00E8,
+ 0x0208, 0x0208, 0x0208, 0x0208, 0x0208, 0x0208, 0x0208, 0x0208, 0x0208, 0x0208, 0x0208, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200,
+ 0x01F8, 0x01F8, 0x01F8, 0x01F8, 0x01F0, 0x01F0, 0x01F0, 0x01E9, 0x01E9, 0x01E9, 0x01E1, 0x01E1, 0x01DA, 0x01DA, 0x01DA, 0x01D4,
+ 0x01D4, 0x01CD, 0x01CD, 0x01C7, 0x01C7, 0x01C0, 0x01C0, 0x01BA, 0x01BA, 0x01B4, 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A4, 0x01A4,
+ 0x019E, 0x019E, 0x0199, 0x0194, 0x0194, 0x018F, 0x018A, 0x018A, 0x0186, 0x0181, 0x0181, 0x017D, 0x0178, 0x0178, 0x0174, 0x0170,
+ 0x0170, 0x016C, 0x0168, 0x0164, 0x0164, 0x0160, 0x015C, 0x015C, 0x0158, 0x0155, 0x0151, 0x0151, 0x014E, 0x014A, 0x0147, 0x0144,
+ 0x0144, 0x0141, 0x013E, 0x013B, 0x0138, 0x0138, 0x0135, 0x0132, 0x012F, 0x012C, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F,
+ 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0106, 0x0104, 0x0102,
+ 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00EA, 0x00E8,
+ 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x01F8, 0x01F8, 0x01F8, 0x01F8,
+ 0x01F0, 0x01F0, 0x01F0, 0x01F0, 0x01E9, 0x01E9, 0x01E9, 0x01E1, 0x01E1, 0x01E1, 0x01DA, 0x01DA, 0x01DA, 0x01D4, 0x01D4, 0x01CD,
+ 0x01CD, 0x01C7, 0x01C7, 0x01C7, 0x01C0, 0x01C0, 0x01BA, 0x01BA, 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A9, 0x01A4, 0x01A4, 0x019E,
+ 0x0199, 0x0199, 0x0194, 0x0194, 0x018F, 0x018A, 0x018A, 0x0186, 0x0181, 0x0181, 0x017D, 0x0178, 0x0178, 0x0174, 0x0170, 0x0170,
+ 0x016C, 0x0168, 0x0168, 0x0164, 0x0160, 0x015C, 0x015C, 0x0158, 0x0155, 0x0151, 0x0151, 0x014E, 0x014A, 0x0147, 0x0147, 0x0144,
+ 0x0141, 0x013E, 0x013B, 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0124, 0x0121, 0x011F,
+ 0x011C, 0x011A, 0x0118, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100,
+ 0x00FE, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E6,
+ 0x01F8, 0x01F8, 0x01F8, 0x01F8, 0x01F8, 0x01F8, 0x01F8, 0x01F8, 0x01F8, 0x01F8, 0x01F8, 0x01F8, 0x01F0, 0x01F0, 0x01F0, 0x01F0,
+ 0x01F0, 0x01E9, 0x01E9, 0x01E9, 0x01E1, 0x01E1, 0x01E1, 0x01E1, 0x01DA, 0x01DA, 0x01D4, 0x01D4, 0x01D4, 0x01CD, 0x01CD, 0x01C7,
+ 0x01C7, 0x01C7, 0x01C0, 0x01C0, 0x01BA, 0x01BA, 0x01B4, 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A9, 0x01A4, 0x019E, 0x019E, 0x0199,
+ 0x0199, 0x0194, 0x018F, 0x018F, 0x018A, 0x018A, 0x0186, 0x0181, 0x0181, 0x017D, 0x0178, 0x0178, 0x0174, 0x0170, 0x0170, 0x016C,
+ 0x0168, 0x0168, 0x0164, 0x0160, 0x015C, 0x015C, 0x0158, 0x0155, 0x0151, 0x0151, 0x014E, 0x014A, 0x0147, 0x0147, 0x0144, 0x0141,
+ 0x013E, 0x013E, 0x013B, 0x0138, 0x0135, 0x0132, 0x0132, 0x012F, 0x012C, 0x0129, 0x0127, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C,
+ 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100,
+ 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E6,
+ 0x01F8, 0x01F8, 0x01F0, 0x01F0, 0x01F0, 0x01F0, 0x01F0, 0x01F0, 0x01F0, 0x01F0, 0x01F0, 0x01F0, 0x01E9, 0x01E9, 0x01E9, 0x01E9,
+ 0x01E9, 0x01E1, 0x01E1, 0x01E1, 0x01E1, 0x01DA, 0x01DA, 0x01DA, 0x01D4, 0x01D4, 0x01D4, 0x01CD, 0x01CD, 0x01C7, 0x01C7, 0x01C7,
+ 0x01C0, 0x01C0, 0x01BA, 0x01BA, 0x01B4, 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A9, 0x01A4, 0x01A4, 0x019E, 0x019E, 0x0199, 0x0194,
+ 0x0194, 0x018F, 0x018F, 0x018A, 0x0186, 0x0186, 0x0181, 0x0181, 0x017D, 0x0178, 0x0178, 0x0174, 0x0170, 0x0170, 0x016C, 0x0168,
+ 0x0168, 0x0164, 0x0160, 0x015C, 0x015C, 0x0158, 0x0155, 0x0155, 0x0151, 0x014E, 0x014A, 0x014A, 0x0147, 0x0144, 0x0141, 0x0141,
+ 0x013E, 0x013B, 0x0138, 0x0135, 0x0135, 0x0132, 0x012F, 0x012C, 0x012C, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011F, 0x011C,
+ 0x011A, 0x0118, 0x0115, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE,
+ 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E6, 0x00E5,
+ 0x01F0, 0x01F0, 0x01F0, 0x01E9, 0x01E9, 0x01E9, 0x01E9, 0x01E9, 0x01E9, 0x01E9, 0x01E9, 0x01E9, 0x01E1, 0x01E1, 0x01E1, 0x01E1,
+ 0x01E1, 0x01DA, 0x01DA, 0x01DA, 0x01DA, 0x01D4, 0x01D4, 0x01D4, 0x01CD, 0x01CD, 0x01CD, 0x01C7, 0x01C7, 0x01C7, 0x01C0, 0x01C0,
+ 0x01BA, 0x01BA, 0x01B4, 0x01B4, 0x01AF, 0x01AF, 0x01AF, 0x01A9, 0x01A9, 0x01A4, 0x019E, 0x019E, 0x0199, 0x0199, 0x0194, 0x0194,
+ 0x018F, 0x018F, 0x018A, 0x0186, 0x0186, 0x0181, 0x017D, 0x017D, 0x0178, 0x0178, 0x0174, 0x0170, 0x0170, 0x016C, 0x0168, 0x0168,
+ 0x0164, 0x0160, 0x015C, 0x015C, 0x0158, 0x0155, 0x0155, 0x0151, 0x014E, 0x014A, 0x014A, 0x0147, 0x0144, 0x0141, 0x0141, 0x013E,
+ 0x013B, 0x0138, 0x0138, 0x0135, 0x0132, 0x012F, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A,
+ 0x0118, 0x0118, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0102, 0x0100, 0x00FE,
+ 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E6, 0x00E5,
+ 0x01E9, 0x01E9, 0x01E9, 0x01E9, 0x01E1, 0x01E1, 0x01E1, 0x01E1, 0x01E1, 0x01E1, 0x01E1, 0x01E1, 0x01E1, 0x01DA, 0x01DA, 0x01DA,
+ 0x01DA, 0x01D4, 0x01D4, 0x01D4, 0x01D4, 0x01CD, 0x01CD, 0x01CD, 0x01C7, 0x01C7, 0x01C7, 0x01C0, 0x01C0, 0x01C0, 0x01BA, 0x01BA,
+ 0x01B4, 0x01B4, 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A9, 0x01A4, 0x01A4, 0x019E, 0x019E, 0x0199, 0x0199, 0x0194, 0x018F, 0x018F,
+ 0x018A, 0x018A, 0x0186, 0x0186, 0x0181, 0x017D, 0x017D, 0x0178, 0x0174, 0x0174, 0x0170, 0x0170, 0x016C, 0x0168, 0x0168, 0x0164,
+ 0x0160, 0x015C, 0x015C, 0x0158, 0x0155, 0x0155, 0x0151, 0x014E, 0x014E, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013B,
+ 0x013B, 0x0138, 0x0135, 0x0132, 0x012F, 0x012F, 0x012C, 0x0129, 0x0127, 0x0127, 0x0124, 0x0121, 0x011F, 0x011C, 0x011C, 0x011A,
+ 0x0118, 0x0115, 0x0113, 0x0111, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC,
+ 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E3,
+ 0x01E1, 0x01E1, 0x01E1, 0x01E1, 0x01DA, 0x01DA, 0x01DA, 0x01DA, 0x01DA, 0x01DA, 0x01DA, 0x01DA, 0x01DA, 0x01D4, 0x01D4, 0x01D4,
+ 0x01D4, 0x01D4, 0x01CD, 0x01CD, 0x01CD, 0x01C7, 0x01C7, 0x01C7, 0x01C7, 0x01C0, 0x01C0, 0x01C0, 0x01BA, 0x01BA, 0x01B4, 0x01B4,
+ 0x01B4, 0x01AF, 0x01AF, 0x01A9, 0x01A9, 0x01A4, 0x01A4, 0x019E, 0x019E, 0x0199, 0x0199, 0x0194, 0x0194, 0x018F, 0x018F, 0x018A,
+ 0x018A, 0x0186, 0x0181, 0x0181, 0x017D, 0x017D, 0x0178, 0x0174, 0x0174, 0x0170, 0x016C, 0x016C, 0x0168, 0x0164, 0x0164, 0x0160,
+ 0x015C, 0x015C, 0x0158, 0x0155, 0x0155, 0x0151, 0x014E, 0x014E, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013B, 0x013B,
+ 0x0138, 0x0135, 0x0132, 0x0132, 0x012F, 0x012C, 0x0129, 0x0129, 0x0127, 0x0124, 0x0121, 0x011F, 0x011F, 0x011C, 0x011A, 0x0118,
+ 0x0115, 0x0115, 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FC,
+ 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E3,
+ 0x01DA, 0x01DA, 0x01DA, 0x01DA, 0x01DA, 0x01D4, 0x01D4, 0x01D4, 0x01D4, 0x01D4, 0x01D4, 0x01D4, 0x01D4, 0x01CD, 0x01CD, 0x01CD,
+ 0x01CD, 0x01CD, 0x01C7, 0x01C7, 0x01C7, 0x01C7, 0x01C0, 0x01C0, 0x01C0, 0x01BA, 0x01BA, 0x01BA, 0x01B4, 0x01B4, 0x01AF, 0x01AF,
+ 0x01AF, 0x01A9, 0x01A9, 0x01A4, 0x01A4, 0x019E, 0x019E, 0x0199, 0x0199, 0x0194, 0x0194, 0x018F, 0x018F, 0x018A, 0x018A, 0x0186,
+ 0x0186, 0x0181, 0x0181, 0x017D, 0x0178, 0x0178, 0x0174, 0x0174, 0x0170, 0x016C, 0x016C, 0x0168, 0x0164, 0x0164, 0x0160, 0x015C,
+ 0x015C, 0x0158, 0x0155, 0x0155, 0x0151, 0x014E, 0x014E, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013E, 0x013B, 0x0138,
+ 0x0135, 0x0135, 0x0132, 0x012F, 0x012C, 0x0129, 0x0129, 0x0127, 0x0124, 0x0121, 0x0121, 0x011F, 0x011C, 0x011A, 0x0118, 0x0118,
+ 0x0115, 0x0113, 0x0111, 0x010E, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA,
+ 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E3, 0x00E3,
+ 0x01D4, 0x01D4, 0x01D4, 0x01D4, 0x01D4, 0x01CD, 0x01CD, 0x01CD, 0x01CD, 0x01CD, 0x01CD, 0x01CD, 0x01CD, 0x01C7, 0x01C7, 0x01C7,
+ 0x01C7, 0x01C7, 0x01C0, 0x01C0, 0x01C0, 0x01C0, 0x01BA, 0x01BA, 0x01BA, 0x01B4, 0x01B4, 0x01B4, 0x01AF, 0x01AF, 0x01AF, 0x01A9,
+ 0x01A9, 0x01A4, 0x01A4, 0x019E, 0x019E, 0x019E, 0x0199, 0x0199, 0x0194, 0x0194, 0x018F, 0x018F, 0x018A, 0x018A, 0x0186, 0x0181,
+ 0x0181, 0x017D, 0x017D, 0x0178, 0x0178, 0x0174, 0x0170, 0x0170, 0x016C, 0x016C, 0x0168, 0x0164, 0x0164, 0x0160, 0x015C, 0x015C,
+ 0x0158, 0x0155, 0x0155, 0x0151, 0x014E, 0x014E, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013E, 0x013B, 0x0138, 0x0135,
+ 0x0135, 0x0132, 0x012F, 0x012C, 0x012C, 0x0129, 0x0127, 0x0124, 0x0124, 0x0121, 0x011F, 0x011C, 0x011A, 0x011A, 0x0118, 0x0115,
+ 0x0113, 0x0111, 0x0111, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FA,
+ 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E5, 0x00E3, 0x00E1,
+ 0x01CD, 0x01CD, 0x01CD, 0x01CD, 0x01CD, 0x01CD, 0x01C7, 0x01C7, 0x01C7, 0x01C7, 0x01C7, 0x01C7, 0x01C7, 0x01C7, 0x01C0, 0x01C0,
+ 0x01C0, 0x01C0, 0x01BA, 0x01BA, 0x01BA, 0x01BA, 0x01B4, 0x01B4, 0x01B4, 0x01AF, 0x01AF, 0x01AF, 0x01A9, 0x01A9, 0x01A9, 0x01A4,
+ 0x01A4, 0x019E, 0x019E, 0x019E, 0x0199, 0x0199, 0x0194, 0x0194, 0x018F, 0x018F, 0x018A, 0x018A, 0x0186, 0x0186, 0x0181, 0x0181,
+ 0x017D, 0x017D, 0x0178, 0x0174, 0x0174, 0x0170, 0x0170, 0x016C, 0x0168, 0x0168, 0x0164, 0x0164, 0x0160, 0x015C, 0x015C, 0x0158,
+ 0x0155, 0x0155, 0x0151, 0x014E, 0x014E, 0x014A, 0x0147, 0x0147, 0x0144, 0x0141, 0x013E, 0x013E, 0x013B, 0x0138, 0x0135, 0x0135,
+ 0x0132, 0x012F, 0x012F, 0x012C, 0x0129, 0x0127, 0x0124, 0x0124, 0x0121, 0x011F, 0x011C, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113,
+ 0x0113, 0x0111, 0x010E, 0x010C, 0x010A, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8,
+ 0x00F6, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E3, 0x00E1, 0x00E1,
+ 0x01C7, 0x01C7, 0x01C7, 0x01C7, 0x01C7, 0x01C7, 0x01C0, 0x01C0, 0x01C0, 0x01C0, 0x01C0, 0x01C0, 0x01C0, 0x01C0, 0x01BA, 0x01BA,
+ 0x01BA, 0x01BA, 0x01BA, 0x01B4, 0x01B4, 0x01B4, 0x01AF, 0x01AF, 0x01AF, 0x01AF, 0x01A9, 0x01A9, 0x01A9, 0x01A4, 0x01A4, 0x019E,
+ 0x019E, 0x019E, 0x0199, 0x0199, 0x0194, 0x0194, 0x018F, 0x018F, 0x018A, 0x018A, 0x0186, 0x0186, 0x0181, 0x0181, 0x017D, 0x017D,
+ 0x0178, 0x0178, 0x0174, 0x0174, 0x0170, 0x016C, 0x016C, 0x0168, 0x0168, 0x0164, 0x0160, 0x0160, 0x015C, 0x015C, 0x0158, 0x0155,
+ 0x0155, 0x0151, 0x014E, 0x014E, 0x014A, 0x0147, 0x0147, 0x0144, 0x0141, 0x013E, 0x013E, 0x013B, 0x0138, 0x0138, 0x0135, 0x0132,
+ 0x012F, 0x012F, 0x012C, 0x0129, 0x0127, 0x0127, 0x0124, 0x0121, 0x011F, 0x011F, 0x011C, 0x011A, 0x0118, 0x0115, 0x0115, 0x0113,
+ 0x0111, 0x010E, 0x010C, 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00FA, 0x00F8,
+ 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E3, 0x00E1, 0x00E0,
+ 0x01C0, 0x01C0, 0x01C0, 0x01C0, 0x01C0, 0x01C0, 0x01BA, 0x01BA, 0x01BA, 0x01BA, 0x01BA, 0x01BA, 0x01BA, 0x01BA, 0x01B4, 0x01B4,
+ 0x01B4, 0x01B4, 0x01B4, 0x01AF, 0x01AF, 0x01AF, 0x01AF, 0x01A9, 0x01A9, 0x01A9, 0x01A4, 0x01A4, 0x01A4, 0x019E, 0x019E, 0x0199,
+ 0x0199, 0x0199, 0x0194, 0x0194, 0x018F, 0x018F, 0x018F, 0x018A, 0x018A, 0x0186, 0x0186, 0x0181, 0x0181, 0x017D, 0x017D, 0x0178,
+ 0x0174, 0x0174, 0x0170, 0x0170, 0x016C, 0x016C, 0x0168, 0x0164, 0x0164, 0x0160, 0x0160, 0x015C, 0x0158, 0x0158, 0x0155, 0x0151,
+ 0x0151, 0x014E, 0x014E, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013E, 0x013B, 0x0138, 0x0138, 0x0135, 0x0132, 0x012F,
+ 0x012F, 0x012C, 0x0129, 0x0127, 0x0127, 0x0124, 0x0121, 0x011F, 0x011F, 0x011C, 0x011A, 0x0118, 0x0118, 0x0115, 0x0113, 0x0111,
+ 0x010E, 0x010E, 0x010C, 0x010A, 0x0108, 0x0106, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6,
+ 0x00F4, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E3, 0x00E1, 0x00E0, 0x00E0,
+ 0x01BA, 0x01BA, 0x01BA, 0x01BA, 0x01BA, 0x01BA, 0x01BA, 0x01B4, 0x01B4, 0x01B4, 0x01B4, 0x01B4, 0x01B4, 0x01B4, 0x01AF, 0x01AF,
+ 0x01AF, 0x01AF, 0x01AF, 0x01A9, 0x01A9, 0x01A9, 0x01A9, 0x01A4, 0x01A4, 0x01A4, 0x019E, 0x019E, 0x019E, 0x0199, 0x0199, 0x0199,
+ 0x0194, 0x0194, 0x018F, 0x018F, 0x018F, 0x018A, 0x018A, 0x0186, 0x0186, 0x0181, 0x0181, 0x017D, 0x017D, 0x0178, 0x0178, 0x0174,
+ 0x0174, 0x0170, 0x0170, 0x016C, 0x0168, 0x0168, 0x0164, 0x0164, 0x0160, 0x0160, 0x015C, 0x0158, 0x0158, 0x0155, 0x0151, 0x0151,
+ 0x014E, 0x014A, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013E, 0x013B, 0x0138, 0x0138, 0x0135, 0x0132, 0x012F, 0x012F,
+ 0x012C, 0x0129, 0x0129, 0x0127, 0x0124, 0x0121, 0x0121, 0x011F, 0x011C, 0x011A, 0x011A, 0x0118, 0x0115, 0x0113, 0x0111, 0x0111,
+ 0x010E, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F8, 0x00F6,
+ 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E0, 0x00DE,
+ 0x01B4, 0x01B4, 0x01B4, 0x01B4, 0x01B4, 0x01B4, 0x01B4, 0x01AF, 0x01AF, 0x01AF, 0x01AF, 0x01AF, 0x01AF, 0x01AF, 0x01AF, 0x01A9,
+ 0x01A9, 0x01A9, 0x01A9, 0x01A4, 0x01A4, 0x01A4, 0x01A4, 0x019E, 0x019E, 0x019E, 0x0199, 0x0199, 0x0199, 0x0194, 0x0194, 0x0194,
+ 0x018F, 0x018F, 0x018F, 0x018A, 0x018A, 0x0186, 0x0186, 0x0181, 0x0181, 0x017D, 0x017D, 0x0178, 0x0178, 0x0174, 0x0174, 0x0170,
+ 0x0170, 0x016C, 0x016C, 0x0168, 0x0168, 0x0164, 0x0164, 0x0160, 0x015C, 0x015C, 0x0158, 0x0158, 0x0155, 0x0151, 0x0151, 0x014E,
+ 0x014A, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013E, 0x013B, 0x0138, 0x0138, 0x0135, 0x0132, 0x0132, 0x012F, 0x012C,
+ 0x0129, 0x0129, 0x0127, 0x0124, 0x0121, 0x0121, 0x011F, 0x011C, 0x011A, 0x011A, 0x0118, 0x0115, 0x0113, 0x0113, 0x0111, 0x010E,
+ 0x010C, 0x010A, 0x010A, 0x0108, 0x0106, 0x0104, 0x0102, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F6, 0x00F4,
+ 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E5, 0x00E3, 0x00E1, 0x00E0, 0x00E0, 0x00DE,
+ 0x01AF, 0x01AF, 0x01AF, 0x01AF, 0x01AF, 0x01AF, 0x01AF, 0x01A9, 0x01A9, 0x01A9, 0x01A9, 0x01A9, 0x01A9, 0x01A9, 0x01A9, 0x01A4,
+ 0x01A4, 0x01A4, 0x01A4, 0x019E, 0x019E, 0x019E, 0x019E, 0x0199, 0x0199, 0x0199, 0x0199, 0x0194, 0x0194, 0x0194, 0x018F, 0x018F,
+ 0x018A, 0x018A, 0x018A, 0x0186, 0x0186, 0x0181, 0x0181, 0x017D, 0x017D, 0x017D, 0x0178, 0x0178, 0x0174, 0x0174, 0x0170, 0x0170,
+ 0x016C, 0x0168, 0x0168, 0x0164, 0x0164, 0x0160, 0x0160, 0x015C, 0x015C, 0x0158, 0x0155, 0x0155, 0x0151, 0x0151, 0x014E, 0x014A,
+ 0x014A, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013E, 0x013B, 0x0138, 0x0138, 0x0135, 0x0132, 0x0132, 0x012F, 0x012C, 0x0129,
+ 0x0129, 0x0127, 0x0124, 0x0121, 0x0121, 0x011F, 0x011C, 0x011C, 0x011A, 0x0118, 0x0115, 0x0113, 0x0113, 0x0111, 0x010E, 0x010C,
+ 0x010C, 0x010A, 0x0108, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F4,
+ 0x00F2, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E0, 0x00DE, 0x00DD,
+ 0x01A9, 0x01A9, 0x01A9, 0x01A9, 0x01A9, 0x01A9, 0x01A9, 0x01A9, 0x01A4, 0x01A4, 0x01A4, 0x01A4, 0x01A4, 0x01A4, 0x01A4, 0x019E,
+ 0x019E, 0x019E, 0x019E, 0x019E, 0x0199, 0x0199, 0x0199, 0x0194, 0x0194, 0x0194, 0x0194, 0x018F, 0x018F, 0x018F, 0x018A, 0x018A,
+ 0x0186, 0x0186, 0x0186, 0x0181, 0x0181, 0x017D, 0x017D, 0x017D, 0x0178, 0x0178, 0x0174, 0x0174, 0x0170, 0x0170, 0x016C, 0x016C,
+ 0x0168, 0x0168, 0x0164, 0x0164, 0x0160, 0x015C, 0x015C, 0x0158, 0x0158, 0x0155, 0x0155, 0x0151, 0x014E, 0x014E, 0x014A, 0x0147,
+ 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013E, 0x013B, 0x0138, 0x0138, 0x0135, 0x0132, 0x0132, 0x012F, 0x012C, 0x0129, 0x0129,
+ 0x0127, 0x0124, 0x0124, 0x0121, 0x011F, 0x011C, 0x011C, 0x011A, 0x0118, 0x0115, 0x0115, 0x0113, 0x0111, 0x010E, 0x010E, 0x010C,
+ 0x010A, 0x0108, 0x0106, 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F2,
+ 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E3, 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DD,
+ 0x01A4, 0x01A4, 0x01A4, 0x01A4, 0x01A4, 0x01A4, 0x01A4, 0x01A4, 0x019E, 0x019E, 0x019E, 0x019E, 0x019E, 0x019E, 0x019E, 0x0199,
+ 0x0199, 0x0199, 0x0199, 0x0199, 0x0194, 0x0194, 0x0194, 0x0194, 0x018F, 0x018F, 0x018F, 0x018A, 0x018A, 0x018A, 0x0186, 0x0186,
+ 0x0186, 0x0181, 0x0181, 0x017D, 0x017D, 0x017D, 0x0178, 0x0178, 0x0174, 0x0174, 0x0170, 0x0170, 0x016C, 0x016C, 0x0168, 0x0168,
+ 0x0164, 0x0164, 0x0160, 0x0160, 0x015C, 0x015C, 0x0158, 0x0158, 0x0155, 0x0151, 0x0151, 0x014E, 0x014E, 0x014A, 0x0147, 0x0147,
+ 0x0144, 0x0141, 0x0141, 0x013E, 0x013E, 0x013B, 0x0138, 0x0138, 0x0135, 0x0132, 0x0132, 0x012F, 0x012C, 0x0129, 0x0129, 0x0127,
+ 0x0124, 0x0124, 0x0121, 0x011F, 0x011C, 0x011C, 0x011A, 0x0118, 0x0115, 0x0115, 0x0113, 0x0111, 0x010E, 0x010E, 0x010C, 0x010A,
+ 0x0108, 0x0108, 0x0106, 0x0104, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0,
+ 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DB,
+ 0x019E, 0x019E, 0x019E, 0x019E, 0x019E, 0x019E, 0x019E, 0x019E, 0x0199, 0x0199, 0x0199, 0x0199, 0x0199, 0x0199, 0x0199, 0x0194,
+ 0x0194, 0x0194, 0x0194, 0x0194, 0x018F, 0x018F, 0x018F, 0x018F, 0x018A, 0x018A, 0x018A, 0x0186, 0x0186, 0x0186, 0x0181, 0x0181,
+ 0x0181, 0x017D, 0x017D, 0x017D, 0x0178, 0x0178, 0x0174, 0x0174, 0x0170, 0x0170, 0x016C, 0x016C, 0x016C, 0x0168, 0x0168, 0x0164,
+ 0x0164, 0x0160, 0x015C, 0x015C, 0x0158, 0x0158, 0x0155, 0x0155, 0x0151, 0x0151, 0x014E, 0x014A, 0x014A, 0x0147, 0x0147, 0x0144,
+ 0x0141, 0x0141, 0x013E, 0x013B, 0x013B, 0x0138, 0x0135, 0x0135, 0x0132, 0x012F, 0x012F, 0x012C, 0x0129, 0x0129, 0x0127, 0x0124,
+ 0x0124, 0x0121, 0x011F, 0x011C, 0x011C, 0x011A, 0x0118, 0x0118, 0x0115, 0x0113, 0x0111, 0x0111, 0x010E, 0x010C, 0x010A, 0x010A,
+ 0x0108, 0x0106, 0x0104, 0x0102, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F0,
+ 0x00EF, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DB, 0x00DB,
+ 0x0199, 0x0199, 0x0199, 0x0199, 0x0199, 0x0199, 0x0199, 0x0199, 0x0194, 0x0194, 0x0194, 0x0194, 0x0194, 0x0194, 0x0194, 0x0194,
+ 0x018F, 0x018F, 0x018F, 0x018F, 0x018A, 0x018A, 0x018A, 0x018A, 0x0186, 0x0186, 0x0186, 0x0181, 0x0181, 0x0181, 0x017D, 0x017D,
+ 0x017D, 0x0178, 0x0178, 0x0178, 0x0174, 0x0174, 0x0170, 0x0170, 0x0170, 0x016C, 0x016C, 0x0168, 0x0168, 0x0164, 0x0164, 0x0160,
+ 0x0160, 0x015C, 0x015C, 0x0158, 0x0158, 0x0155, 0x0151, 0x0151, 0x014E, 0x014E, 0x014A, 0x014A, 0x0147, 0x0144, 0x0144, 0x0141,
+ 0x0141, 0x013E, 0x013B, 0x013B, 0x0138, 0x0135, 0x0135, 0x0132, 0x012F, 0x012F, 0x012C, 0x0129, 0x0129, 0x0127, 0x0124, 0x0124,
+ 0x0121, 0x011F, 0x011F, 0x011C, 0x011A, 0x0118, 0x0118, 0x0115, 0x0113, 0x0111, 0x0111, 0x010E, 0x010C, 0x010A, 0x010A, 0x0108,
+ 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00EF,
+ 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E3, 0x00E1, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DB, 0x00DA,
+ 0x0194, 0x0194, 0x0194, 0x0194, 0x0194, 0x0194, 0x0194, 0x0194, 0x0194, 0x018F, 0x018F, 0x018F, 0x018F, 0x018F, 0x018F, 0x018F,
+ 0x018A, 0x018A, 0x018A, 0x018A, 0x0186, 0x0186, 0x0186, 0x0186, 0x0181, 0x0181, 0x0181, 0x0181, 0x017D, 0x017D, 0x017D, 0x0178,
+ 0x0178, 0x0174, 0x0174, 0x0174, 0x0170, 0x0170, 0x016C, 0x016C, 0x016C, 0x0168, 0x0168, 0x0164, 0x0164, 0x0160, 0x0160, 0x015C,
+ 0x015C, 0x0158, 0x0158, 0x0155, 0x0155, 0x0151, 0x0151, 0x014E, 0x014E, 0x014A, 0x0147, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E,
+ 0x013E, 0x013B, 0x013B, 0x0138, 0x0135, 0x0135, 0x0132, 0x012F, 0x012F, 0x012C, 0x0129, 0x0129, 0x0127, 0x0124, 0x0124, 0x0121,
+ 0x011F, 0x011F, 0x011C, 0x011A, 0x0118, 0x0118, 0x0115, 0x0113, 0x0111, 0x0111, 0x010E, 0x010C, 0x010C, 0x010A, 0x0108, 0x0106,
+ 0x0106, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF,
+ 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DB, 0x00DA, 0x00DA,
+ 0x018F, 0x018F, 0x018F, 0x018F, 0x018F, 0x018F, 0x018F, 0x018F, 0x018F, 0x018A, 0x018A, 0x018A, 0x018A, 0x018A, 0x018A, 0x018A,
+ 0x0186, 0x0186, 0x0186, 0x0186, 0x0186, 0x0181, 0x0181, 0x0181, 0x0181, 0x017D, 0x017D, 0x017D, 0x0178, 0x0178, 0x0178, 0x0174,
+ 0x0174, 0x0174, 0x0170, 0x0170, 0x016C, 0x016C, 0x016C, 0x0168, 0x0168, 0x0164, 0x0164, 0x0160, 0x0160, 0x015C, 0x015C, 0x0158,
+ 0x0158, 0x0155, 0x0155, 0x0151, 0x0151, 0x014E, 0x014E, 0x014A, 0x014A, 0x0147, 0x0147, 0x0144, 0x0141, 0x0141, 0x013E, 0x013E,
+ 0x013B, 0x0138, 0x0138, 0x0135, 0x0135, 0x0132, 0x012F, 0x012F, 0x012C, 0x0129, 0x0129, 0x0127, 0x0124, 0x0124, 0x0121, 0x011F,
+ 0x011F, 0x011C, 0x011A, 0x0118, 0x0118, 0x0115, 0x0113, 0x0113, 0x0111, 0x010E, 0x010C, 0x010C, 0x010A, 0x0108, 0x0106, 0x0106,
+ 0x0104, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00EF, 0x00ED,
+ 0x00EB, 0x00EB, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DA, 0x00D9,
+ 0x018A, 0x018A, 0x018A, 0x018A, 0x018A, 0x018A, 0x018A, 0x018A, 0x018A, 0x0186, 0x0186, 0x0186, 0x0186, 0x0186, 0x0186, 0x0186,
+ 0x0181, 0x0181, 0x0181, 0x0181, 0x0181, 0x017D, 0x017D, 0x017D, 0x017D, 0x0178, 0x0178, 0x0178, 0x0174, 0x0174, 0x0174, 0x0170,
+ 0x0170, 0x0170, 0x016C, 0x016C, 0x016C, 0x0168, 0x0168, 0x0164, 0x0164, 0x0160, 0x0160, 0x0160, 0x015C, 0x015C, 0x0158, 0x0158,
+ 0x0155, 0x0155, 0x0151, 0x0151, 0x014E, 0x014E, 0x014A, 0x0147, 0x0147, 0x0144, 0x0144, 0x0141, 0x0141, 0x013E, 0x013B, 0x013B,
+ 0x0138, 0x0138, 0x0135, 0x0132, 0x0132, 0x012F, 0x012F, 0x012C, 0x0129, 0x0129, 0x0127, 0x0124, 0x0124, 0x0121, 0x011F, 0x011F,
+ 0x011C, 0x011A, 0x0118, 0x0118, 0x0115, 0x0113, 0x0113, 0x0111, 0x010E, 0x010C, 0x010C, 0x010A, 0x0108, 0x0106, 0x0106, 0x0104,
+ 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00ED, 0x00ED,
+ 0x00EB, 0x00EA, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E3, 0x00E1, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DB, 0x00DA, 0x00D9, 0x00D7,
+ 0x0186, 0x0186, 0x0186, 0x0186, 0x0186, 0x0186, 0x0186, 0x0186, 0x0186, 0x0181, 0x0181, 0x0181, 0x0181, 0x0181, 0x0181, 0x0181,
+ 0x017D, 0x017D, 0x017D, 0x017D, 0x017D, 0x0178, 0x0178, 0x0178, 0x0178, 0x0174, 0x0174, 0x0174, 0x0170, 0x0170, 0x0170, 0x016C,
+ 0x016C, 0x016C, 0x0168, 0x0168, 0x0168, 0x0164, 0x0164, 0x0160, 0x0160, 0x0160, 0x015C, 0x015C, 0x0158, 0x0158, 0x0155, 0x0155,
+ 0x0151, 0x0151, 0x014E, 0x014E, 0x014A, 0x014A, 0x0147, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013E, 0x013B, 0x013B, 0x0138,
+ 0x0135, 0x0135, 0x0132, 0x0132, 0x012F, 0x012C, 0x012C, 0x0129, 0x0127, 0x0127, 0x0124, 0x0121, 0x0121, 0x011F, 0x011C, 0x011C,
+ 0x011A, 0x0118, 0x0118, 0x0115, 0x0113, 0x0113, 0x0111, 0x010E, 0x010C, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0102,
+ 0x0102, 0x0100, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00EB,
+ 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7,
+ 0x0181, 0x0181, 0x0181, 0x0181, 0x0181, 0x0181, 0x0181, 0x0181, 0x0181, 0x0181, 0x017D, 0x017D, 0x017D, 0x017D, 0x017D, 0x017D,
+ 0x017D, 0x0178, 0x0178, 0x0178, 0x0178, 0x0174, 0x0174, 0x0174, 0x0174, 0x0170, 0x0170, 0x0170, 0x0170, 0x016C, 0x016C, 0x016C,
+ 0x0168, 0x0168, 0x0164, 0x0164, 0x0164, 0x0160, 0x0160, 0x0160, 0x015C, 0x015C, 0x0158, 0x0158, 0x0155, 0x0155, 0x0151, 0x0151,
+ 0x014E, 0x014E, 0x014A, 0x014A, 0x0147, 0x0147, 0x0144, 0x0144, 0x0141, 0x0141, 0x013E, 0x013E, 0x013B, 0x0138, 0x0138, 0x0135,
+ 0x0135, 0x0132, 0x012F, 0x012F, 0x012C, 0x012C, 0x0129, 0x0127, 0x0127, 0x0124, 0x0121, 0x0121, 0x011F, 0x011C, 0x011C, 0x011A,
+ 0x0118, 0x0118, 0x0115, 0x0113, 0x0113, 0x0111, 0x010E, 0x010E, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0102, 0x0102,
+ 0x0100, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EA,
+ 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DA, 0x00D9, 0x00D7, 0x00D6,
+ 0x017D, 0x017D, 0x017D, 0x017D, 0x017D, 0x017D, 0x017D, 0x017D, 0x017D, 0x017D, 0x0178, 0x0178, 0x0178, 0x0178, 0x0178, 0x0178,
+ 0x0178, 0x0174, 0x0174, 0x0174, 0x0174, 0x0170, 0x0170, 0x0170, 0x0170, 0x016C, 0x016C, 0x016C, 0x016C, 0x0168, 0x0168, 0x0168,
+ 0x0164, 0x0164, 0x0164, 0x0160, 0x0160, 0x015C, 0x015C, 0x015C, 0x0158, 0x0158, 0x0155, 0x0155, 0x0151, 0x0151, 0x0151, 0x014E,
+ 0x014E, 0x014A, 0x014A, 0x0147, 0x0147, 0x0144, 0x0144, 0x0141, 0x013E, 0x013E, 0x013B, 0x013B, 0x0138, 0x0138, 0x0135, 0x0132,
+ 0x0132, 0x012F, 0x012F, 0x012C, 0x0129, 0x0129, 0x0127, 0x0127, 0x0124, 0x0121, 0x0121, 0x011F, 0x011C, 0x011C, 0x011A, 0x0118,
+ 0x0118, 0x0115, 0x0113, 0x0113, 0x0111, 0x010E, 0x010E, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0102, 0x0102, 0x0100,
+ 0x00FE, 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EB, 0x00EA,
+ 0x00E8, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DB, 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6,
+ 0x0178, 0x0178, 0x0178, 0x0178, 0x0178, 0x0178, 0x0178, 0x0178, 0x0178, 0x0178, 0x0174, 0x0174, 0x0174, 0x0174, 0x0174, 0x0174,
+ 0x0174, 0x0170, 0x0170, 0x0170, 0x0170, 0x0170, 0x016C, 0x016C, 0x016C, 0x016C, 0x0168, 0x0168, 0x0168, 0x0164, 0x0164, 0x0164,
+ 0x0160, 0x0160, 0x0160, 0x015C, 0x015C, 0x015C, 0x0158, 0x0158, 0x0155, 0x0155, 0x0151, 0x0151, 0x0151, 0x014E, 0x014E, 0x014A,
+ 0x014A, 0x0147, 0x0147, 0x0144, 0x0144, 0x0141, 0x0141, 0x013E, 0x013E, 0x013B, 0x013B, 0x0138, 0x0135, 0x0135, 0x0132, 0x0132,
+ 0x012F, 0x012F, 0x012C, 0x0129, 0x0129, 0x0127, 0x0124, 0x0124, 0x0121, 0x0121, 0x011F, 0x011C, 0x011C, 0x011A, 0x0118, 0x0118,
+ 0x0115, 0x0113, 0x0113, 0x0111, 0x010E, 0x010E, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x00FE,
+ 0x00FE, 0x00FC, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EA, 0x00E8,
+ 0x00E6, 0x00E6, 0x00E5, 0x00E3, 0x00E1, 0x00E0, 0x00E0, 0x00DE, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D6, 0x00D4,
+ 0x0174, 0x0174, 0x0174, 0x0174, 0x0174, 0x0174, 0x0174, 0x0174, 0x0174, 0x0174, 0x0170, 0x0170, 0x0170, 0x0170, 0x0170, 0x0170,
+ 0x0170, 0x016C, 0x016C, 0x016C, 0x016C, 0x016C, 0x0168, 0x0168, 0x0168, 0x0168, 0x0164, 0x0164, 0x0164, 0x0160, 0x0160, 0x0160,
+ 0x015C, 0x015C, 0x015C, 0x0158, 0x0158, 0x0158, 0x0155, 0x0155, 0x0151, 0x0151, 0x0151, 0x014E, 0x014E, 0x014A, 0x014A, 0x0147,
+ 0x0147, 0x0144, 0x0144, 0x0141, 0x0141, 0x013E, 0x013E, 0x013B, 0x013B, 0x0138, 0x0138, 0x0135, 0x0135, 0x0132, 0x012F, 0x012F,
+ 0x012C, 0x012C, 0x0129, 0x0129, 0x0127, 0x0124, 0x0124, 0x0121, 0x011F, 0x011F, 0x011C, 0x011C, 0x011A, 0x0118, 0x0118, 0x0115,
+ 0x0113, 0x0113, 0x0111, 0x010E, 0x010E, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FE,
+ 0x00FC, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E8,
+ 0x00E6, 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DB, 0x00DB, 0x00DA, 0x00D9, 0x00D7, 0x00D6, 0x00D6, 0x00D4,
+ 0x0170, 0x0170, 0x0170, 0x0170, 0x0170, 0x0170, 0x0170, 0x0170, 0x0170, 0x0170, 0x016C, 0x016C, 0x016C, 0x016C, 0x016C, 0x016C,
+ 0x016C, 0x0168, 0x0168, 0x0168, 0x0168, 0x0168, 0x0164, 0x0164, 0x0164, 0x0164, 0x0160, 0x0160, 0x0160, 0x015C, 0x015C, 0x015C,
+ 0x015C, 0x0158, 0x0158, 0x0155, 0x0155, 0x0155, 0x0151, 0x0151, 0x0151, 0x014E, 0x014E, 0x014A, 0x014A, 0x0147, 0x0147, 0x0144,
+ 0x0144, 0x0144, 0x0141, 0x0141, 0x013E, 0x013E, 0x013B, 0x0138, 0x0138, 0x0135, 0x0135, 0x0132, 0x0132, 0x012F, 0x012F, 0x012C,
+ 0x012C, 0x0129, 0x0127, 0x0127, 0x0124, 0x0124, 0x0121, 0x011F, 0x011F, 0x011C, 0x011A, 0x011A, 0x0118, 0x0115, 0x0115, 0x0113,
+ 0x0111, 0x0111, 0x010E, 0x010C, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FE, 0x00FC,
+ 0x00FA, 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E8, 0x00E6,
+ 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D4, 0x00D3,
+ 0x016C, 0x016C, 0x016C, 0x016C, 0x016C, 0x016C, 0x016C, 0x016C, 0x016C, 0x016C, 0x016C, 0x0168, 0x0168, 0x0168, 0x0168, 0x0168,
+ 0x0168, 0x0164, 0x0164, 0x0164, 0x0164, 0x0164, 0x0160, 0x0160, 0x0160, 0x0160, 0x015C, 0x015C, 0x015C, 0x015C, 0x0158, 0x0158,
+ 0x0158, 0x0155, 0x0155, 0x0155, 0x0151, 0x0151, 0x014E, 0x014E, 0x014E, 0x014A, 0x014A, 0x0147, 0x0147, 0x0144, 0x0144, 0x0144,
+ 0x0141, 0x0141, 0x013E, 0x013E, 0x013B, 0x013B, 0x0138, 0x0138, 0x0135, 0x0135, 0x0132, 0x0132, 0x012F, 0x012C, 0x012C, 0x0129,
+ 0x0129, 0x0127, 0x0127, 0x0124, 0x0121, 0x0121, 0x011F, 0x011F, 0x011C, 0x011A, 0x011A, 0x0118, 0x0115, 0x0115, 0x0113, 0x0111,
+ 0x0111, 0x010E, 0x010C, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FA,
+ 0x00FA, 0x00F8, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E6, 0x00E5,
+ 0x00E5, 0x00E3, 0x00E1, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DB, 0x00DA, 0x00D9, 0x00D9, 0x00D7, 0x00D6, 0x00D4, 0x00D3, 0x00D3,
+ 0x0168, 0x0168, 0x0168, 0x0168, 0x0168, 0x0168, 0x0168, 0x0168, 0x0168, 0x0168, 0x0168, 0x0164, 0x0164, 0x0164, 0x0164, 0x0164,
+ 0x0164, 0x0164, 0x0160, 0x0160, 0x0160, 0x0160, 0x015C, 0x015C, 0x015C, 0x015C, 0x0158, 0x0158, 0x0158, 0x0158, 0x0155, 0x0155,
+ 0x0155, 0x0151, 0x0151, 0x0151, 0x014E, 0x014E, 0x014E, 0x014A, 0x014A, 0x0147, 0x0147, 0x0144, 0x0144, 0x0144, 0x0141, 0x0141,
+ 0x013E, 0x013E, 0x013B, 0x013B, 0x0138, 0x0138, 0x0135, 0x0135, 0x0132, 0x0132, 0x012F, 0x012F, 0x012C, 0x012C, 0x0129, 0x0127,
+ 0x0127, 0x0124, 0x0124, 0x0121, 0x0121, 0x011F, 0x011C, 0x011C, 0x011A, 0x011A, 0x0118, 0x0115, 0x0115, 0x0113, 0x0111, 0x0111,
+ 0x010E, 0x010C, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00FA,
+ 0x00F8, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E5,
+ 0x00E3, 0x00E1, 0x00E0, 0x00E0, 0x00DE, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D6, 0x00D4, 0x00D4, 0x00D3, 0x00D2,
+ 0x0164, 0x0164, 0x0164, 0x0164, 0x0164, 0x0164, 0x0164, 0x0164, 0x0164, 0x0164, 0x0164, 0x0160, 0x0160, 0x0160, 0x0160, 0x0160,
+ 0x0160, 0x0160, 0x015C, 0x015C, 0x015C, 0x015C, 0x015C, 0x0158, 0x0158, 0x0158, 0x0158, 0x0155, 0x0155, 0x0155, 0x0151, 0x0151,
+ 0x0151, 0x014E, 0x014E, 0x014E, 0x014A, 0x014A, 0x014A, 0x0147, 0x0147, 0x0144, 0x0144, 0x0144, 0x0141, 0x0141, 0x013E, 0x013E,
+ 0x013B, 0x013B, 0x0138, 0x0138, 0x0135, 0x0135, 0x0132, 0x0132, 0x012F, 0x012F, 0x012C, 0x012C, 0x0129, 0x0129, 0x0127, 0x0127,
+ 0x0124, 0x0124, 0x0121, 0x011F, 0x011F, 0x011C, 0x011C, 0x011A, 0x0118, 0x0118, 0x0115, 0x0113, 0x0113, 0x0111, 0x0111, 0x010E,
+ 0x010C, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FC, 0x00FA, 0x00FA, 0x00F8,
+ 0x00F6, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E3,
+ 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DB, 0x00DB, 0x00DA, 0x00D9, 0x00D7, 0x00D6, 0x00D6, 0x00D4, 0x00D3, 0x00D2, 0x00D0,
+ 0x0160, 0x0160, 0x0160, 0x0160, 0x0160, 0x0160, 0x0160, 0x0160, 0x0160, 0x0160, 0x0160, 0x015C, 0x015C, 0x015C, 0x015C, 0x015C,
+ 0x015C, 0x015C, 0x0158, 0x0158, 0x0158, 0x0158, 0x0158, 0x0155, 0x0155, 0x0155, 0x0155, 0x0151, 0x0151, 0x0151, 0x014E, 0x014E,
+ 0x014E, 0x014A, 0x014A, 0x014A, 0x0147, 0x0147, 0x0147, 0x0144, 0x0144, 0x0144, 0x0141, 0x0141, 0x013E, 0x013E, 0x013B, 0x013B,
+ 0x013B, 0x0138, 0x0138, 0x0135, 0x0135, 0x0132, 0x0132, 0x012F, 0x012F, 0x012C, 0x012C, 0x0129, 0x0129, 0x0127, 0x0124, 0x0124,
+ 0x0121, 0x0121, 0x011F, 0x011F, 0x011C, 0x011A, 0x011A, 0x0118, 0x0118, 0x0115, 0x0113, 0x0113, 0x0111, 0x010E, 0x010E, 0x010C,
+ 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F6,
+ 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E3, 0x00E1,
+ 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D4, 0x00D3, 0x00D2, 0x00D2, 0x00D0,
+ 0x015C, 0x015C, 0x015C, 0x015C, 0x015C, 0x015C, 0x015C, 0x015C, 0x015C, 0x015C, 0x015C, 0x0158, 0x0158, 0x0158, 0x0158, 0x0158,
+ 0x0158, 0x0158, 0x0155, 0x0155, 0x0155, 0x0155, 0x0155, 0x0151, 0x0151, 0x0151, 0x0151, 0x014E, 0x014E, 0x014E, 0x014A, 0x014A,
+ 0x014A, 0x014A, 0x0147, 0x0147, 0x0144, 0x0144, 0x0144, 0x0141, 0x0141, 0x0141, 0x013E, 0x013E, 0x013B, 0x013B, 0x013B, 0x0138,
+ 0x0138, 0x0135, 0x0135, 0x0132, 0x0132, 0x012F, 0x012F, 0x012C, 0x012C, 0x0129, 0x0129, 0x0127, 0x0127, 0x0124, 0x0124, 0x0121,
+ 0x011F, 0x011F, 0x011C, 0x011C, 0x011A, 0x011A, 0x0118, 0x0115, 0x0115, 0x0113, 0x0113, 0x0111, 0x010E, 0x010E, 0x010C, 0x010A,
+ 0x010A, 0x0108, 0x0106, 0x0106, 0x0104, 0x0102, 0x0102, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F6, 0x00F6,
+ 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E5, 0x00E3, 0x00E3, 0x00E1,
+ 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DB, 0x00DA, 0x00D9, 0x00D9, 0x00D7, 0x00D6, 0x00D4, 0x00D3, 0x00D3, 0x00D2, 0x00D0, 0x00CF,
+ 0x0158, 0x0158, 0x0158, 0x0158, 0x0158, 0x0158, 0x0158, 0x0158, 0x0158, 0x0158, 0x0158, 0x0158, 0x0155, 0x0155, 0x0155, 0x0155,
+ 0x0155, 0x0155, 0x0151, 0x0151, 0x0151, 0x0151, 0x0151, 0x014E, 0x014E, 0x014E, 0x014E, 0x014A, 0x014A, 0x014A, 0x014A, 0x0147,
+ 0x0147, 0x0147, 0x0144, 0x0144, 0x0144, 0x0141, 0x0141, 0x013E, 0x013E, 0x013E, 0x013B, 0x013B, 0x0138, 0x0138, 0x0138, 0x0135,
+ 0x0135, 0x0132, 0x0132, 0x012F, 0x012F, 0x012C, 0x012C, 0x0129, 0x0129, 0x0127, 0x0127, 0x0124, 0x0124, 0x0121, 0x0121, 0x011F,
+ 0x011F, 0x011C, 0x011C, 0x011A, 0x0118, 0x0118, 0x0115, 0x0115, 0x0113, 0x0111, 0x0111, 0x010E, 0x010E, 0x010C, 0x010A, 0x010A,
+ 0x0108, 0x0106, 0x0106, 0x0104, 0x0102, 0x0102, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F6, 0x00F6, 0x00F4,
+ 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E0,
+ 0x00DE, 0x00DE, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D6, 0x00D4, 0x00D4, 0x00D3, 0x00D2, 0x00D0, 0x00CF, 0x00CF,
+ 0x0155, 0x0155, 0x0155, 0x0155, 0x0155, 0x0155, 0x0155, 0x0155, 0x0155, 0x0155, 0x0155, 0x0155, 0x0151, 0x0151, 0x0151, 0x0151,
+ 0x0151, 0x0151, 0x014E, 0x014E, 0x014E, 0x014E, 0x014E, 0x014A, 0x014A, 0x014A, 0x014A, 0x0147, 0x0147, 0x0147, 0x0147, 0x0144,
+ 0x0144, 0x0144, 0x0141, 0x0141, 0x0141, 0x013E, 0x013E, 0x013E, 0x013B, 0x013B, 0x0138, 0x0138, 0x0138, 0x0135, 0x0135, 0x0132,
+ 0x0132, 0x012F, 0x012F, 0x012C, 0x012C, 0x012C, 0x0129, 0x0129, 0x0127, 0x0127, 0x0124, 0x0124, 0x0121, 0x011F, 0x011F, 0x011C,
+ 0x011C, 0x011A, 0x011A, 0x0118, 0x0118, 0x0115, 0x0113, 0x0113, 0x0111, 0x0111, 0x010E, 0x010C, 0x010C, 0x010A, 0x010A, 0x0108,
+ 0x0106, 0x0106, 0x0104, 0x0102, 0x0102, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F2,
+ 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E0, 0x00E0,
+ 0x00DE, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D6, 0x00D6, 0x00D4, 0x00D3, 0x00D2, 0x00D0, 0x00D0, 0x00CF, 0x00CE,
+ 0x0151, 0x0151, 0x0151, 0x0151, 0x0151, 0x0151, 0x0151, 0x0151, 0x0151, 0x0151, 0x0151, 0x0151, 0x014E, 0x014E, 0x014E, 0x014E,
+ 0x014E, 0x014E, 0x014E, 0x014A, 0x014A, 0x014A, 0x014A, 0x0147, 0x0147, 0x0147, 0x0147, 0x0144, 0x0144, 0x0144, 0x0144, 0x0141,
+ 0x0141, 0x0141, 0x013E, 0x013E, 0x013E, 0x013B, 0x013B, 0x013B, 0x0138, 0x0138, 0x0135, 0x0135, 0x0135, 0x0132, 0x0132, 0x012F,
+ 0x012F, 0x012F, 0x012C, 0x012C, 0x0129, 0x0129, 0x0127, 0x0127, 0x0124, 0x0124, 0x0121, 0x0121, 0x011F, 0x011F, 0x011C, 0x011C,
+ 0x011A, 0x0118, 0x0118, 0x0115, 0x0115, 0x0113, 0x0113, 0x0111, 0x010E, 0x010E, 0x010C, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106,
+ 0x0106, 0x0104, 0x0102, 0x0102, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F2, 0x00F0,
+ 0x00F0, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E0, 0x00E0, 0x00DE,
+ 0x00DD, 0x00DB, 0x00DB, 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D4, 0x00D3, 0x00D2, 0x00D2, 0x00D0, 0x00CF, 0x00CE, 0x00CC,
+ 0x014E, 0x014E, 0x014E, 0x014E, 0x014E, 0x014E, 0x014E, 0x014E, 0x014E, 0x014E, 0x014E, 0x014E, 0x014A, 0x014A, 0x014A, 0x014A,
+ 0x014A, 0x014A, 0x014A, 0x0147, 0x0147, 0x0147, 0x0147, 0x0147, 0x0144, 0x0144, 0x0144, 0x0144, 0x0141, 0x0141, 0x0141, 0x013E,
+ 0x013E, 0x013E, 0x013B, 0x013B, 0x013B, 0x0138, 0x0138, 0x0138, 0x0135, 0x0135, 0x0135, 0x0132, 0x0132, 0x012F, 0x012F, 0x012F,
+ 0x012C, 0x012C, 0x0129, 0x0129, 0x0127, 0x0127, 0x0124, 0x0124, 0x0121, 0x0121, 0x011F, 0x011F, 0x011C, 0x011C, 0x011A, 0x011A,
+ 0x0118, 0x0118, 0x0115, 0x0115, 0x0113, 0x0111, 0x0111, 0x010E, 0x010E, 0x010C, 0x010A, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104,
+ 0x0104, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F2, 0x00F0, 0x00F0,
+ 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E0, 0x00E0, 0x00DE, 0x00DD,
+ 0x00DB, 0x00DB, 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D4, 0x00D3, 0x00D3, 0x00D2, 0x00D0, 0x00CF, 0x00CE, 0x00CE, 0x00CC,
+ 0x014A, 0x014A, 0x014A, 0x014A, 0x014A, 0x014A, 0x014A, 0x014A, 0x014A, 0x014A, 0x014A, 0x014A, 0x0147, 0x0147, 0x0147, 0x0147,
+ 0x0147, 0x0147, 0x0147, 0x0144, 0x0144, 0x0144, 0x0144, 0x0144, 0x0141, 0x0141, 0x0141, 0x0141, 0x013E, 0x013E, 0x013E, 0x013B,
+ 0x013B, 0x013B, 0x013B, 0x0138, 0x0138, 0x0138, 0x0135, 0x0135, 0x0132, 0x0132, 0x0132, 0x012F, 0x012F, 0x012C, 0x012C, 0x012C,
+ 0x0129, 0x0129, 0x0127, 0x0127, 0x0124, 0x0124, 0x0121, 0x0121, 0x011F, 0x011F, 0x011F, 0x011C, 0x011A, 0x011A, 0x0118, 0x0118,
+ 0x0115, 0x0115, 0x0113, 0x0113, 0x0111, 0x0111, 0x010E, 0x010C, 0x010C, 0x010A, 0x010A, 0x0108, 0x0106, 0x0106, 0x0104, 0x0104,
+ 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF,
+ 0x00ED, 0x00ED, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DD,
+ 0x00DB, 0x00DA, 0x00D9, 0x00D9, 0x00D7, 0x00D6, 0x00D4, 0x00D4, 0x00D3, 0x00D2, 0x00D0, 0x00CF, 0x00CF, 0x00CE, 0x00CC, 0x00CB,
+ 0x0147, 0x0147, 0x0147, 0x0147, 0x0147, 0x0147, 0x0147, 0x0147, 0x0147, 0x0147, 0x0147, 0x0147, 0x0144, 0x0144, 0x0144, 0x0144,
+ 0x0144, 0x0144, 0x0144, 0x0141, 0x0141, 0x0141, 0x0141, 0x0141, 0x013E, 0x013E, 0x013E, 0x013E, 0x013B, 0x013B, 0x013B, 0x013B,
+ 0x0138, 0x0138, 0x0138, 0x0135, 0x0135, 0x0135, 0x0132, 0x0132, 0x0132, 0x012F, 0x012F, 0x012C, 0x012C, 0x012C, 0x0129, 0x0129,
+ 0x0127, 0x0127, 0x0124, 0x0124, 0x0124, 0x0121, 0x0121, 0x011F, 0x011F, 0x011C, 0x011C, 0x011A, 0x011A, 0x0118, 0x0118, 0x0115,
+ 0x0115, 0x0113, 0x0111, 0x0111, 0x010E, 0x010E, 0x010C, 0x010C, 0x010A, 0x0108, 0x0108, 0x0106, 0x0106, 0x0104, 0x0102, 0x0102,
+ 0x0100, 0x0100, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED,
+ 0x00ED, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DD, 0x00DB,
+ 0x00DA, 0x00D9, 0x00D9, 0x00D7, 0x00D6, 0x00D4, 0x00D4, 0x00D3, 0x00D2, 0x00D0, 0x00D0, 0x00CF, 0x00CE, 0x00CC, 0x00CB, 0x00CB,
+ 0x0144, 0x0144, 0x0144, 0x0144, 0x0144, 0x0144, 0x0144, 0x0144, 0x0144, 0x0144, 0x0144, 0x0144, 0x0144, 0x0141, 0x0141, 0x0141,
+ 0x0141, 0x0141, 0x0141, 0x013E, 0x013E, 0x013E, 0x013E, 0x013E, 0x013B, 0x013B, 0x013B, 0x013B, 0x0138, 0x0138, 0x0138, 0x0138,
+ 0x0135, 0x0135, 0x0135, 0x0132, 0x0132, 0x0132, 0x012F, 0x012F, 0x012F, 0x012C, 0x012C, 0x0129, 0x0129, 0x0129, 0x0127, 0x0127,
+ 0x0124, 0x0124, 0x0124, 0x0121, 0x0121, 0x011F, 0x011F, 0x011C, 0x011C, 0x011A, 0x011A, 0x0118, 0x0118, 0x0115, 0x0115, 0x0113,
+ 0x0113, 0x0111, 0x0111, 0x010E, 0x010E, 0x010C, 0x010A, 0x010A, 0x0108, 0x0108, 0x0106, 0x0104, 0x0104, 0x0102, 0x0102, 0x0100,
+ 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00ED,
+ 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DA,
+ 0x00DA, 0x00D9, 0x00D7, 0x00D6, 0x00D6, 0x00D4, 0x00D3, 0x00D2, 0x00D0, 0x00D0, 0x00CF, 0x00CE, 0x00CC, 0x00CC, 0x00CB, 0x00CA,
+ 0x0141, 0x0141, 0x0141, 0x0141, 0x0141, 0x0141, 0x0141, 0x0141, 0x0141, 0x0141, 0x0141, 0x0141, 0x0141, 0x013E, 0x013E, 0x013E,
+ 0x013E, 0x013E, 0x013E, 0x013B, 0x013B, 0x013B, 0x013B, 0x013B, 0x0138, 0x0138, 0x0138, 0x0138, 0x0135, 0x0135, 0x0135, 0x0135,
+ 0x0132, 0x0132, 0x0132, 0x012F, 0x012F, 0x012F, 0x012C, 0x012C, 0x012C, 0x0129, 0x0129, 0x0129, 0x0127, 0x0127, 0x0124, 0x0124,
+ 0x0124, 0x0121, 0x0121, 0x011F, 0x011F, 0x011C, 0x011C, 0x011A, 0x011A, 0x0118, 0x0118, 0x0115, 0x0115, 0x0113, 0x0113, 0x0111,
+ 0x0111, 0x010E, 0x010E, 0x010C, 0x010C, 0x010A, 0x010A, 0x0108, 0x0106, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x0100, 0x00FE,
+ 0x00FE, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00ED, 0x00EB,
+ 0x00EA, 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DA, 0x00DA,
+ 0x00D9, 0x00D7, 0x00D6, 0x00D6, 0x00D4, 0x00D3, 0x00D2, 0x00D2, 0x00D0, 0x00CF, 0x00CE, 0x00CE, 0x00CC, 0x00CB, 0x00CA, 0x00C9,
+ 0x013E, 0x013E, 0x013E, 0x013E, 0x013E, 0x013E, 0x013E, 0x013E, 0x013E, 0x013E, 0x013E, 0x013E, 0x013E, 0x013B, 0x013B, 0x013B,
+ 0x013B, 0x013B, 0x013B, 0x013B, 0x0138, 0x0138, 0x0138, 0x0138, 0x0135, 0x0135, 0x0135, 0x0135, 0x0135, 0x0132, 0x0132, 0x0132,
+ 0x012F, 0x012F, 0x012F, 0x012C, 0x012C, 0x012C, 0x0129, 0x0129, 0x0129, 0x0127, 0x0127, 0x0127, 0x0124, 0x0124, 0x0121, 0x0121,
+ 0x0121, 0x011F, 0x011F, 0x011C, 0x011C, 0x011A, 0x011A, 0x011A, 0x0118, 0x0118, 0x0115, 0x0115, 0x0113, 0x0113, 0x0111, 0x0111,
+ 0x010E, 0x010C, 0x010C, 0x010A, 0x010A, 0x0108, 0x0108, 0x0106, 0x0106, 0x0104, 0x0102, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FC,
+ 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EA,
+ 0x00EA, 0x00E8, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9,
+ 0x00D7, 0x00D6, 0x00D6, 0x00D4, 0x00D3, 0x00D2, 0x00D2, 0x00D0, 0x00CF, 0x00CE, 0x00CE, 0x00CC, 0x00CB, 0x00CA, 0x00CA, 0x00C9,
+ 0x013B, 0x013B, 0x013B, 0x013B, 0x013B, 0x013B, 0x013B, 0x013B, 0x013B, 0x013B, 0x013B, 0x013B, 0x013B, 0x0138, 0x0138, 0x0138,
+ 0x0138, 0x0138, 0x0138, 0x0138, 0x0135, 0x0135, 0x0135, 0x0135, 0x0135, 0x0132, 0x0132, 0x0132, 0x0132, 0x012F, 0x012F, 0x012F,
+ 0x012C, 0x012C, 0x012C, 0x012C, 0x0129, 0x0129, 0x0129, 0x0127, 0x0127, 0x0124, 0x0124, 0x0124, 0x0121, 0x0121, 0x0121, 0x011F,
+ 0x011F, 0x011C, 0x011C, 0x011A, 0x011A, 0x011A, 0x0118, 0x0118, 0x0115, 0x0115, 0x0113, 0x0113, 0x0111, 0x0111, 0x010E, 0x010E,
+ 0x010C, 0x010C, 0x010A, 0x010A, 0x0108, 0x0106, 0x0106, 0x0104, 0x0104, 0x0102, 0x0102, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FC,
+ 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00E8,
+ 0x00E8, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7,
+ 0x00D6, 0x00D6, 0x00D4, 0x00D3, 0x00D3, 0x00D2, 0x00D0, 0x00CF, 0x00CF, 0x00CE, 0x00CC, 0x00CB, 0x00CB, 0x00CA, 0x00C9, 0x00C7,
+ 0x0138, 0x0138, 0x0138, 0x0138, 0x0138, 0x0138, 0x0138, 0x0138, 0x0138, 0x0138, 0x0138, 0x0138, 0x0138, 0x0135, 0x0135, 0x0135,
+ 0x0135, 0x0135, 0x0135, 0x0135, 0x0132, 0x0132, 0x0132, 0x0132, 0x0132, 0x012F, 0x012F, 0x012F, 0x012F, 0x012C, 0x012C, 0x012C,
+ 0x012C, 0x0129, 0x0129, 0x0129, 0x0127, 0x0127, 0x0127, 0x0124, 0x0124, 0x0124, 0x0121, 0x0121, 0x011F, 0x011F, 0x011F, 0x011C,
+ 0x011C, 0x011A, 0x011A, 0x011A, 0x0118, 0x0118, 0x0115, 0x0115, 0x0113, 0x0113, 0x0111, 0x0111, 0x010E, 0x010E, 0x010C, 0x010C,
+ 0x010A, 0x010A, 0x0108, 0x0108, 0x0106, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FA,
+ 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00E8, 0x00E8,
+ 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D7,
+ 0x00D6, 0x00D4, 0x00D3, 0x00D3, 0x00D2, 0x00D0, 0x00CF, 0x00CF, 0x00CE, 0x00CC, 0x00CB, 0x00CB, 0x00CA, 0x00C9, 0x00C7, 0x00C7,
+ 0x0135, 0x0135, 0x0135, 0x0135, 0x0135, 0x0135, 0x0135, 0x0135, 0x0135, 0x0135, 0x0135, 0x0135, 0x0135, 0x0132, 0x0132, 0x0132,
+ 0x0132, 0x0132, 0x0132, 0x0132, 0x012F, 0x012F, 0x012F, 0x012F, 0x012F, 0x012C, 0x012C, 0x012C, 0x012C, 0x0129, 0x0129, 0x0129,
+ 0x0129, 0x0127, 0x0127, 0x0127, 0x0124, 0x0124, 0x0124, 0x0121, 0x0121, 0x0121, 0x011F, 0x011F, 0x011F, 0x011C, 0x011C, 0x011A,
+ 0x011A, 0x011A, 0x0118, 0x0118, 0x0115, 0x0115, 0x0113, 0x0113, 0x0111, 0x0111, 0x010E, 0x010E, 0x010E, 0x010C, 0x010C, 0x010A,
+ 0x0108, 0x0108, 0x0106, 0x0106, 0x0104, 0x0104, 0x0102, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00F8,
+ 0x00F8, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00E8, 0x00E8, 0x00E6,
+ 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6,
+ 0x00D4, 0x00D3, 0x00D3, 0x00D2, 0x00D0, 0x00CF, 0x00CF, 0x00CE, 0x00CC, 0x00CB, 0x00CB, 0x00CA, 0x00C9, 0x00C7, 0x00C7, 0x00C6,
+ 0x0132, 0x0132, 0x0132, 0x0132, 0x0132, 0x0132, 0x0132, 0x0132, 0x0132, 0x0132, 0x0132, 0x0132, 0x0132, 0x0132, 0x012F, 0x012F,
+ 0x012F, 0x012F, 0x012F, 0x012F, 0x012C, 0x012C, 0x012C, 0x012C, 0x012C, 0x0129, 0x0129, 0x0129, 0x0129, 0x0127, 0x0127, 0x0127,
+ 0x0127, 0x0124, 0x0124, 0x0124, 0x0121, 0x0121, 0x0121, 0x011F, 0x011F, 0x011F, 0x011C, 0x011C, 0x011C, 0x011A, 0x011A, 0x0118,
+ 0x0118, 0x0118, 0x0115, 0x0115, 0x0113, 0x0113, 0x0111, 0x0111, 0x0111, 0x010E, 0x010E, 0x010C, 0x010C, 0x010A, 0x010A, 0x0108,
+ 0x0108, 0x0106, 0x0106, 0x0104, 0x0104, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F6,
+ 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E5,
+ 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D4,
+ 0x00D3, 0x00D3, 0x00D2, 0x00D0, 0x00D0, 0x00CF, 0x00CE, 0x00CC, 0x00CC, 0x00CB, 0x00CA, 0x00C9, 0x00C9, 0x00C7, 0x00C6, 0x00C5,
+ 0x012F, 0x012F, 0x012F, 0x012F, 0x012F, 0x012F, 0x012F, 0x012F, 0x012F, 0x012F, 0x012F, 0x012F, 0x012F, 0x012F, 0x012C, 0x012C,
+ 0x012C, 0x012C, 0x012C, 0x012C, 0x0129, 0x0129, 0x0129, 0x0129, 0x0129, 0x0127, 0x0127, 0x0127, 0x0127, 0x0124, 0x0124, 0x0124,
+ 0x0124, 0x0121, 0x0121, 0x0121, 0x011F, 0x011F, 0x011F, 0x011C, 0x011C, 0x011C, 0x011A, 0x011A, 0x011A, 0x0118, 0x0118, 0x0118,
+ 0x0115, 0x0115, 0x0113, 0x0113, 0x0111, 0x0111, 0x0111, 0x010E, 0x010E, 0x010C, 0x010C, 0x010A, 0x010A, 0x0108, 0x0108, 0x0106,
+ 0x0106, 0x0104, 0x0104, 0x0102, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F6,
+ 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E5,
+ 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D4, 0x00D3,
+ 0x00D3, 0x00D2, 0x00D0, 0x00D0, 0x00CF, 0x00CE, 0x00CC, 0x00CC, 0x00CB, 0x00CA, 0x00C9, 0x00C9, 0x00C7, 0x00C6, 0x00C5, 0x00C5,
+ 0x012C, 0x012C, 0x012C, 0x012C, 0x012C, 0x012C, 0x012C, 0x012C, 0x012C, 0x012C, 0x012C, 0x012C, 0x012C, 0x012C, 0x0129, 0x0129,
+ 0x0129, 0x0129, 0x0129, 0x0129, 0x0129, 0x0127, 0x0127, 0x0127, 0x0127, 0x0124, 0x0124, 0x0124, 0x0124, 0x0124, 0x0121, 0x0121,
+ 0x0121, 0x011F, 0x011F, 0x011F, 0x011F, 0x011C, 0x011C, 0x011C, 0x011A, 0x011A, 0x011A, 0x0118, 0x0118, 0x0115, 0x0115, 0x0115,
+ 0x0113, 0x0113, 0x0111, 0x0111, 0x0111, 0x010E, 0x010E, 0x010C, 0x010C, 0x010A, 0x010A, 0x0108, 0x0108, 0x0106, 0x0106, 0x0104,
+ 0x0104, 0x0102, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F4,
+ 0x00F2, 0x00F2, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E3, 0x00E3,
+ 0x00E1, 0x00E0, 0x00E0, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D4, 0x00D4, 0x00D3,
+ 0x00D2, 0x00D0, 0x00D0, 0x00CF, 0x00CE, 0x00CC, 0x00CC, 0x00CB, 0x00CA, 0x00CA, 0x00C9, 0x00C7, 0x00C6, 0x00C6, 0x00C5, 0x00C4,
+ 0x0129, 0x0129, 0x0129, 0x0129, 0x0129, 0x0129, 0x0129, 0x0129, 0x0129, 0x0129, 0x0129, 0x0129, 0x0129, 0x0129, 0x0127, 0x0127,
+ 0x0127, 0x0127, 0x0127, 0x0127, 0x0127, 0x0124, 0x0124, 0x0124, 0x0124, 0x0124, 0x0121, 0x0121, 0x0121, 0x0121, 0x011F, 0x011F,
+ 0x011F, 0x011C, 0x011C, 0x011C, 0x011C, 0x011A, 0x011A, 0x011A, 0x0118, 0x0118, 0x0118, 0x0115, 0x0115, 0x0113, 0x0113, 0x0113,
+ 0x0111, 0x0111, 0x0111, 0x010E, 0x010E, 0x010C, 0x010C, 0x010A, 0x010A, 0x0108, 0x0108, 0x0108, 0x0106, 0x0106, 0x0104, 0x0104,
+ 0x0102, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F4, 0x00F2,
+ 0x00F0, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E3, 0x00E3, 0x00E1,
+ 0x00E0, 0x00E0, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D4, 0x00D4, 0x00D3, 0x00D2,
+ 0x00D0, 0x00D0, 0x00CF, 0x00CE, 0x00CE, 0x00CC, 0x00CB, 0x00CA, 0x00CA, 0x00C9, 0x00C7, 0x00C6, 0x00C6, 0x00C5, 0x00C4, 0x00C3,
+ 0x0127, 0x0127, 0x0127, 0x0127, 0x0127, 0x0127, 0x0127, 0x0127, 0x0127, 0x0127, 0x0127, 0x0127, 0x0127, 0x0127, 0x0124, 0x0124,
+ 0x0124, 0x0124, 0x0124, 0x0124, 0x0124, 0x0121, 0x0121, 0x0121, 0x0121, 0x0121, 0x011F, 0x011F, 0x011F, 0x011F, 0x011C, 0x011C,
+ 0x011C, 0x011C, 0x011A, 0x011A, 0x011A, 0x0118, 0x0118, 0x0118, 0x0115, 0x0115, 0x0115, 0x0113, 0x0113, 0x0113, 0x0111, 0x0111,
+ 0x010E, 0x010E, 0x010E, 0x010C, 0x010C, 0x010A, 0x010A, 0x0108, 0x0108, 0x0108, 0x0106, 0x0106, 0x0104, 0x0104, 0x0102, 0x0102,
+ 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F2, 0x00F0,
+ 0x00F0, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E0,
+ 0x00E0, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D4, 0x00D4, 0x00D3, 0x00D2, 0x00D0,
+ 0x00D0, 0x00CF, 0x00CE, 0x00CE, 0x00CC, 0x00CB, 0x00CA, 0x00CA, 0x00C9, 0x00C7, 0x00C6, 0x00C6, 0x00C5, 0x00C4, 0x00C3, 0x00C3,
+ 0x0124, 0x0124, 0x0124, 0x0124, 0x0124, 0x0124, 0x0124, 0x0124, 0x0124, 0x0124, 0x0124, 0x0124, 0x0124, 0x0124, 0x0121, 0x0121,
+ 0x0121, 0x0121, 0x0121, 0x0121, 0x0121, 0x011F, 0x011F, 0x011F, 0x011F, 0x011F, 0x011C, 0x011C, 0x011C, 0x011C, 0x011A, 0x011A,
+ 0x011A, 0x011A, 0x0118, 0x0118, 0x0118, 0x0115, 0x0115, 0x0115, 0x0113, 0x0113, 0x0113, 0x0111, 0x0111, 0x0111, 0x010E, 0x010E,
+ 0x010E, 0x010C, 0x010C, 0x010A, 0x010A, 0x0108, 0x0108, 0x0108, 0x0106, 0x0106, 0x0104, 0x0104, 0x0102, 0x0102, 0x0100, 0x0100,
+ 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF,
+ 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00E0,
+ 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D4, 0x00D3, 0x00D3, 0x00D2, 0x00D0, 0x00D0,
+ 0x00CF, 0x00CE, 0x00CE, 0x00CC, 0x00CB, 0x00CA, 0x00CA, 0x00C9, 0x00C7, 0x00C7, 0x00C6, 0x00C5, 0x00C4, 0x00C4, 0x00C3, 0x00C1,
+ 0x0121, 0x0121, 0x0121, 0x0121, 0x0121, 0x0121, 0x0121, 0x0121, 0x0121, 0x0121, 0x0121, 0x0121, 0x0121, 0x0121, 0x011F, 0x011F,
+ 0x011F, 0x011F, 0x011F, 0x011F, 0x011F, 0x011C, 0x011C, 0x011C, 0x011C, 0x011C, 0x011A, 0x011A, 0x011A, 0x011A, 0x0118, 0x0118,
+ 0x0118, 0x0118, 0x0115, 0x0115, 0x0115, 0x0113, 0x0113, 0x0113, 0x0111, 0x0111, 0x0111, 0x010E, 0x010E, 0x010E, 0x010C, 0x010C,
+ 0x010C, 0x010A, 0x010A, 0x0108, 0x0108, 0x0108, 0x0106, 0x0106, 0x0104, 0x0104, 0x0102, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FE,
+ 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00EF,
+ 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DE,
+ 0x00DD, 0x00DB, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D6, 0x00D6, 0x00D4, 0x00D3, 0x00D3, 0x00D2, 0x00D0, 0x00D0, 0x00CF,
+ 0x00CE, 0x00CE, 0x00CC, 0x00CB, 0x00CA, 0x00CA, 0x00C9, 0x00C7, 0x00C7, 0x00C6, 0x00C5, 0x00C4, 0x00C4, 0x00C3, 0x00C1, 0x00C0,
+ 0x011F, 0x011F, 0x011F, 0x011F, 0x011F, 0x011F, 0x011F, 0x011F, 0x011F, 0x011F, 0x011F, 0x011F, 0x011F, 0x011F, 0x011F, 0x011C,
+ 0x011C, 0x011C, 0x011C, 0x011C, 0x011C, 0x011A, 0x011A, 0x011A, 0x011A, 0x011A, 0x0118, 0x0118, 0x0118, 0x0118, 0x0115, 0x0115,
+ 0x0115, 0x0115, 0x0113, 0x0113, 0x0113, 0x0113, 0x0111, 0x0111, 0x0111, 0x010E, 0x010E, 0x010E, 0x010C, 0x010C, 0x010A, 0x010A,
+ 0x010A, 0x0108, 0x0108, 0x0106, 0x0106, 0x0106, 0x0104, 0x0104, 0x0102, 0x0102, 0x0100, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FC,
+ 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00ED,
+ 0x00EB, 0x00EB, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DE, 0x00DD,
+ 0x00DB, 0x00DB, 0x00DA, 0x00D9, 0x00D9, 0x00D7, 0x00D6, 0x00D6, 0x00D4, 0x00D3, 0x00D3, 0x00D2, 0x00D0, 0x00D0, 0x00CF, 0x00CE,
+ 0x00CE, 0x00CC, 0x00CB, 0x00CA, 0x00CA, 0x00C9, 0x00C7, 0x00C7, 0x00C6, 0x00C5, 0x00C4, 0x00C4, 0x00C3, 0x00C1, 0x00C0, 0x00C0,
+ 0x011C, 0x011C, 0x011C, 0x011C, 0x011C, 0x011C, 0x011C, 0x011C, 0x011C, 0x011C, 0x011C, 0x011C, 0x011C, 0x011C, 0x011C, 0x011A,
+ 0x011A, 0x011A, 0x011A, 0x011A, 0x011A, 0x0118, 0x0118, 0x0118, 0x0118, 0x0118, 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0113,
+ 0x0113, 0x0113, 0x0111, 0x0111, 0x0111, 0x0111, 0x010E, 0x010E, 0x010E, 0x010C, 0x010C, 0x010C, 0x010A, 0x010A, 0x010A, 0x0108,
+ 0x0108, 0x0106, 0x0106, 0x0106, 0x0104, 0x0104, 0x0102, 0x0102, 0x0100, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FA,
+ 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00EB, 0x00EB,
+ 0x00EA, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E0, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DB,
+ 0x00DB, 0x00DA, 0x00D9, 0x00D9, 0x00D7, 0x00D6, 0x00D6, 0x00D4, 0x00D3, 0x00D3, 0x00D2, 0x00D0, 0x00D0, 0x00CF, 0x00CE, 0x00CE,
+ 0x00CC, 0x00CB, 0x00CA, 0x00CA, 0x00C9, 0x00C7, 0x00C7, 0x00C6, 0x00C5, 0x00C4, 0x00C4, 0x00C3, 0x00C1, 0x00C1, 0x00C0, 0x00BF,
+ 0x011A, 0x011A, 0x011A, 0x011A, 0x011A, 0x011A, 0x011A, 0x011A, 0x011A, 0x011A, 0x011A, 0x011A, 0x011A, 0x011A, 0x011A, 0x0118,
+ 0x0118, 0x0118, 0x0118, 0x0118, 0x0118, 0x0118, 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0113, 0x0113, 0x0113, 0x0113, 0x0111,
+ 0x0111, 0x0111, 0x0111, 0x010E, 0x010E, 0x010E, 0x010C, 0x010C, 0x010C, 0x010A, 0x010A, 0x010A, 0x0108, 0x0108, 0x0108, 0x0106,
+ 0x0106, 0x0104, 0x0104, 0x0104, 0x0102, 0x0102, 0x0100, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00F8,
+ 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EB, 0x00EA,
+ 0x00E8, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00E0, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DA,
+ 0x00DA, 0x00D9, 0x00D9, 0x00D7, 0x00D6, 0x00D6, 0x00D4, 0x00D3, 0x00D3, 0x00D2, 0x00D0, 0x00D0, 0x00CF, 0x00CE, 0x00CE, 0x00CC,
+ 0x00CB, 0x00CA, 0x00CA, 0x00C9, 0x00C7, 0x00C7, 0x00C6, 0x00C5, 0x00C5, 0x00C4, 0x00C3, 0x00C1, 0x00C1, 0x00C0, 0x00BF, 0x00BE,
+ 0x0118, 0x0118, 0x0118, 0x0118, 0x0118, 0x0118, 0x0118, 0x0118, 0x0118, 0x0118, 0x0118, 0x0118, 0x0118, 0x0118, 0x0118, 0x0115,
+ 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0113, 0x0113, 0x0113, 0x0113, 0x0113, 0x0111, 0x0111, 0x0111, 0x0111, 0x010E,
+ 0x010E, 0x010E, 0x010E, 0x010C, 0x010C, 0x010C, 0x010A, 0x010A, 0x010A, 0x0108, 0x0108, 0x0108, 0x0106, 0x0106, 0x0106, 0x0104,
+ 0x0104, 0x0104, 0x0102, 0x0102, 0x0100, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F8, 0x00F8,
+ 0x00F6, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00EA, 0x00E8,
+ 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DA, 0x00DA,
+ 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D6, 0x00D4, 0x00D3, 0x00D3, 0x00D2, 0x00D0, 0x00D0, 0x00CF, 0x00CE, 0x00CE, 0x00CC, 0x00CB,
+ 0x00CA, 0x00CA, 0x00C9, 0x00C7, 0x00C7, 0x00C6, 0x00C5, 0x00C5, 0x00C4, 0x00C3, 0x00C1, 0x00C1, 0x00C0, 0x00BF, 0x00BE, 0x00BE,
+ 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0115, 0x0113,
+ 0x0113, 0x0113, 0x0113, 0x0113, 0x0113, 0x0113, 0x0111, 0x0111, 0x0111, 0x0111, 0x0111, 0x010E, 0x010E, 0x010E, 0x010E, 0x010C,
+ 0x010C, 0x010C, 0x010C, 0x010A, 0x010A, 0x010A, 0x0108, 0x0108, 0x0108, 0x0106, 0x0106, 0x0106, 0x0104, 0x0104, 0x0104, 0x0102,
+ 0x0102, 0x0102, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F8, 0x00F8, 0x00F6, 0x00F6,
+ 0x00F4, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E6,
+ 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E0, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DB, 0x00DB, 0x00DA, 0x00DA, 0x00D9,
+ 0x00D7, 0x00D7, 0x00D6, 0x00D4, 0x00D4, 0x00D3, 0x00D2, 0x00D2, 0x00D0, 0x00CF, 0x00CF, 0x00CE, 0x00CC, 0x00CC, 0x00CB, 0x00CA,
+ 0x00CA, 0x00C9, 0x00C7, 0x00C7, 0x00C6, 0x00C5, 0x00C5, 0x00C4, 0x00C3, 0x00C1, 0x00C1, 0x00C0, 0x00BF, 0x00BF, 0x00BE, 0x00BD,
+ 0x0113, 0x0113, 0x0113, 0x0113, 0x0113, 0x0113, 0x0113, 0x0113, 0x0113, 0x0113, 0x0113, 0x0113, 0x0113, 0x0113, 0x0113, 0x0111,
+ 0x0111, 0x0111, 0x0111, 0x0111, 0x0111, 0x0111, 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010C, 0x010C, 0x010C, 0x010C, 0x010A,
+ 0x010A, 0x010A, 0x010A, 0x0108, 0x0108, 0x0108, 0x0108, 0x0106, 0x0106, 0x0106, 0x0104, 0x0104, 0x0104, 0x0102, 0x0102, 0x0100,
+ 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F8, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F4,
+ 0x00F2, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E6,
+ 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00E0, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DB, 0x00DA, 0x00D9, 0x00D9, 0x00D7,
+ 0x00D7, 0x00D6, 0x00D4, 0x00D4, 0x00D3, 0x00D2, 0x00D2, 0x00D0, 0x00CF, 0x00CF, 0x00CE, 0x00CC, 0x00CC, 0x00CB, 0x00CA, 0x00CA,
+ 0x00C9, 0x00C7, 0x00C7, 0x00C6, 0x00C5, 0x00C5, 0x00C4, 0x00C3, 0x00C1, 0x00C1, 0x00C0, 0x00BF, 0x00BF, 0x00BE, 0x00BD, 0x00BC,
+ 0x0111, 0x0111, 0x0111, 0x0111, 0x0111, 0x0111, 0x0111, 0x0111, 0x0111, 0x0111, 0x0111, 0x0111, 0x0111, 0x0111, 0x0111, 0x010E,
+ 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010C, 0x010C, 0x010C, 0x010C, 0x010C, 0x010A, 0x010A, 0x010A, 0x010A, 0x0108,
+ 0x0108, 0x0108, 0x0108, 0x0106, 0x0106, 0x0106, 0x0106, 0x0104, 0x0104, 0x0104, 0x0102, 0x0102, 0x0102, 0x0100, 0x0100, 0x0100,
+ 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F8, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F2,
+ 0x00F0, 0x00F0, 0x00EF, 0x00EF, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E5,
+ 0x00E3, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D9, 0x00D7, 0x00D6,
+ 0x00D6, 0x00D4, 0x00D4, 0x00D3, 0x00D2, 0x00D2, 0x00D0, 0x00CF, 0x00CF, 0x00CE, 0x00CC, 0x00CC, 0x00CB, 0x00CA, 0x00CA, 0x00C9,
+ 0x00C7, 0x00C7, 0x00C6, 0x00C5, 0x00C5, 0x00C4, 0x00C3, 0x00C1, 0x00C1, 0x00C0, 0x00BF, 0x00BF, 0x00BE, 0x00BD, 0x00BC, 0x00BC,
+ 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010E,
+ 0x010C, 0x010C, 0x010C, 0x010C, 0x010C, 0x010C, 0x010A, 0x010A, 0x010A, 0x010A, 0x010A, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108,
+ 0x0106, 0x0106, 0x0106, 0x0104, 0x0104, 0x0104, 0x0104, 0x0102, 0x0102, 0x0102, 0x0100, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FE,
+ 0x00FC, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00F8, 0x00F8, 0x00F8, 0x00F6, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00F0,
+ 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E3, 0x00E3,
+ 0x00E1, 0x00E1, 0x00E0, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DB, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D6,
+ 0x00D4, 0x00D3, 0x00D3, 0x00D2, 0x00D0, 0x00D0, 0x00CF, 0x00CF, 0x00CE, 0x00CC, 0x00CC, 0x00CB, 0x00CA, 0x00CA, 0x00C9, 0x00C7,
+ 0x00C7, 0x00C6, 0x00C5, 0x00C5, 0x00C4, 0x00C3, 0x00C1, 0x00C1, 0x00C0, 0x00BF, 0x00BF, 0x00BE, 0x00BD, 0x00BC, 0x00BC, 0x00BB,
+ 0x010C, 0x010C, 0x010C, 0x010C, 0x010C, 0x010C, 0x010C, 0x010C, 0x010C, 0x010C, 0x010C, 0x010C, 0x010C, 0x010C, 0x010C, 0x010C,
+ 0x010A, 0x010A, 0x010A, 0x010A, 0x010A, 0x010A, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0106, 0x0106, 0x0106, 0x0106, 0x0106,
+ 0x0104, 0x0104, 0x0104, 0x0104, 0x0102, 0x0102, 0x0102, 0x0100, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FC,
+ 0x00FA, 0x00FA, 0x00FA, 0x00F8, 0x00F8, 0x00F6, 0x00F6, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00F0, 0x00F0, 0x00EF,
+ 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E3, 0x00E1,
+ 0x00E1, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DB, 0x00DA, 0x00D9, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D4, 0x00D4,
+ 0x00D3, 0x00D3, 0x00D2, 0x00D0, 0x00D0, 0x00CF, 0x00CE, 0x00CE, 0x00CC, 0x00CB, 0x00CB, 0x00CA, 0x00CA, 0x00C9, 0x00C7, 0x00C7,
+ 0x00C6, 0x00C5, 0x00C4, 0x00C4, 0x00C3, 0x00C1, 0x00C1, 0x00C0, 0x00BF, 0x00BF, 0x00BE, 0x00BD, 0x00BD, 0x00BC, 0x00BB, 0x00BA,
+ 0x010A, 0x010A, 0x010A, 0x010A, 0x010A, 0x010A, 0x010A, 0x010A, 0x010A, 0x010A, 0x010A, 0x010A, 0x010A, 0x010A, 0x010A, 0x010A,
+ 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0106, 0x0106, 0x0106, 0x0106, 0x0106, 0x0104, 0x0104, 0x0104, 0x0104,
+ 0x0102, 0x0102, 0x0102, 0x0102, 0x0100, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00FA,
+ 0x00F8, 0x00F8, 0x00F8, 0x00F6, 0x00F6, 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00F0, 0x00F0, 0x00EF, 0x00EF, 0x00ED,
+ 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E1, 0x00E0,
+ 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D9, 0x00D7, 0x00D6, 0x00D6, 0x00D4, 0x00D4, 0x00D3,
+ 0x00D2, 0x00D2, 0x00D0, 0x00D0, 0x00CF, 0x00CE, 0x00CE, 0x00CC, 0x00CB, 0x00CB, 0x00CA, 0x00C9, 0x00C9, 0x00C7, 0x00C6, 0x00C6,
+ 0x00C5, 0x00C4, 0x00C4, 0x00C3, 0x00C1, 0x00C1, 0x00C0, 0x00BF, 0x00BF, 0x00BE, 0x00BD, 0x00BD, 0x00BC, 0x00BB, 0x00BA, 0x00BA,
+ 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108, 0x0108,
+ 0x0106, 0x0106, 0x0106, 0x0106, 0x0106, 0x0106, 0x0106, 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0102, 0x0102, 0x0102, 0x0102,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00FA, 0x00F8, 0x00F8,
+ 0x00F6, 0x00F6, 0x00F6, 0x00F4, 0x00F4, 0x00F4, 0x00F2, 0x00F2, 0x00F0, 0x00F0, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00ED, 0x00EB,
+ 0x00EB, 0x00EA, 0x00EA, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00DE,
+ 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D6, 0x00D4, 0x00D3, 0x00D3, 0x00D2,
+ 0x00D2, 0x00D0, 0x00CF, 0x00CF, 0x00CE, 0x00CE, 0x00CC, 0x00CB, 0x00CB, 0x00CA, 0x00C9, 0x00C9, 0x00C7, 0x00C6, 0x00C6, 0x00C5,
+ 0x00C4, 0x00C4, 0x00C3, 0x00C1, 0x00C1, 0x00C0, 0x00BF, 0x00BF, 0x00BE, 0x00BD, 0x00BD, 0x00BC, 0x00BB, 0x00BA, 0x00BA, 0x00B9,
+ 0x0106, 0x0106, 0x0106, 0x0106, 0x0106, 0x0106, 0x0106, 0x0106, 0x0106, 0x0106, 0x0106, 0x0106, 0x0106, 0x0106, 0x0106, 0x0106,
+ 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x00FE, 0x00FE, 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00FA, 0x00F8, 0x00F8, 0x00F8, 0x00F6, 0x00F6,
+ 0x00F6, 0x00F4, 0x00F4, 0x00F2, 0x00F2, 0x00F2, 0x00F0, 0x00F0, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EB, 0x00EA,
+ 0x00EA, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00E0, 0x00DE, 0x00DE,
+ 0x00DD, 0x00DD, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D4, 0x00D4, 0x00D3, 0x00D3, 0x00D2, 0x00D0,
+ 0x00D0, 0x00CF, 0x00CF, 0x00CE, 0x00CC, 0x00CC, 0x00CB, 0x00CB, 0x00CA, 0x00C9, 0x00C9, 0x00C7, 0x00C6, 0x00C6, 0x00C5, 0x00C4,
+ 0x00C4, 0x00C3, 0x00C1, 0x00C1, 0x00C0, 0x00BF, 0x00BF, 0x00BE, 0x00BD, 0x00BD, 0x00BC, 0x00BB, 0x00BA, 0x00BA, 0x00B9, 0x00B8,
+ 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0104, 0x0104,
+ 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FE, 0x00FE,
+ 0x00FC, 0x00FC, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00FA, 0x00FA, 0x00F8, 0x00F8, 0x00F8, 0x00F6, 0x00F6, 0x00F6, 0x00F4, 0x00F4,
+ 0x00F4, 0x00F2, 0x00F2, 0x00F2, 0x00F0, 0x00F0, 0x00EF, 0x00EF, 0x00EF, 0x00ED, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00EA, 0x00EA,
+ 0x00E8, 0x00E8, 0x00E6, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DD,
+ 0x00DB, 0x00DB, 0x00DA, 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D6, 0x00D4, 0x00D4, 0x00D3, 0x00D2, 0x00D2, 0x00D0, 0x00D0,
+ 0x00CF, 0x00CE, 0x00CE, 0x00CC, 0x00CC, 0x00CB, 0x00CA, 0x00CA, 0x00C9, 0x00C7, 0x00C7, 0x00C6, 0x00C6, 0x00C5, 0x00C4, 0x00C4,
+ 0x00C3, 0x00C1, 0x00C1, 0x00C0, 0x00BF, 0x00BF, 0x00BE, 0x00BD, 0x00BC, 0x00BC, 0x00BB, 0x00BA, 0x00BA, 0x00B9, 0x00B8, 0x00B8,
+ 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0102, 0x0102,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x00FE, 0x00FE, 0x00FE, 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FC, 0x00FC,
+ 0x00FC, 0x00FA, 0x00FA, 0x00FA, 0x00F8, 0x00F8, 0x00F8, 0x00F8, 0x00F6, 0x00F6, 0x00F6, 0x00F4, 0x00F4, 0x00F4, 0x00F2, 0x00F2,
+ 0x00F2, 0x00F0, 0x00F0, 0x00F0, 0x00EF, 0x00EF, 0x00ED, 0x00ED, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00EA, 0x00EA, 0x00E8, 0x00E8,
+ 0x00E6, 0x00E6, 0x00E5, 0x00E5, 0x00E3, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00E0, 0x00E0, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DB,
+ 0x00DA, 0x00DA, 0x00D9, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D6, 0x00D4, 0x00D3, 0x00D3, 0x00D2, 0x00D2, 0x00D0, 0x00CF, 0x00CF,
+ 0x00CE, 0x00CE, 0x00CC, 0x00CB, 0x00CB, 0x00CA, 0x00CA, 0x00C9, 0x00C7, 0x00C7, 0x00C6, 0x00C5, 0x00C5, 0x00C4, 0x00C3, 0x00C3,
+ 0x00C1, 0x00C0, 0x00C0, 0x00BF, 0x00BE, 0x00BE, 0x00BD, 0x00BC, 0x00BC, 0x00BB, 0x00BA, 0x00BA, 0x00B9, 0x00B8, 0x00B8, 0x00B7,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x00FE, 0x00FE, 0x00FE, 0x00FE, 0x00FE, 0x00FE, 0x00FE, 0x00FC, 0x00FC, 0x00FC, 0x00FC, 0x00FC, 0x00FA, 0x00FA, 0x00FA, 0x00FA,
+ 0x00FA, 0x00F8, 0x00F8, 0x00F8, 0x00F8, 0x00F6, 0x00F6, 0x00F6, 0x00F4, 0x00F4, 0x00F4, 0x00F2, 0x00F2, 0x00F2, 0x00F0, 0x00F0,
+ 0x00F0, 0x00EF, 0x00EF, 0x00EF, 0x00ED, 0x00ED, 0x00ED, 0x00EB, 0x00EB, 0x00EA, 0x00EA, 0x00EA, 0x00E8, 0x00E8, 0x00E6, 0x00E6,
+ 0x00E5, 0x00E5, 0x00E3, 0x00E3, 0x00E3, 0x00E1, 0x00E1, 0x00E0, 0x00E0, 0x00DE, 0x00DE, 0x00DD, 0x00DD, 0x00DB, 0x00DB, 0x00DA,
+ 0x00DA, 0x00D9, 0x00D7, 0x00D7, 0x00D6, 0x00D6, 0x00D4, 0x00D4, 0x00D3, 0x00D3, 0x00D2, 0x00D0, 0x00D0, 0x00CF, 0x00CF, 0x00CE,
+ 0x00CC, 0x00CC, 0x00CB, 0x00CB, 0x00CA, 0x00C9, 0x00C9, 0x00C7, 0x00C7, 0x00C6, 0x00C5, 0x00C5, 0x00C4, 0x00C3, 0x00C3, 0x00C1,
+ 0x00C0, 0x00C0, 0x00BF, 0x00BE, 0x00BE, 0x00BD, 0x00BC, 0x00BC, 0x00BB, 0x00BA, 0x00BA, 0x00B9, 0x00B8, 0x00B8, 0x00B7, 0x00B6
+};
+#define kDeltaUsedToBuildTable 32
diff --git a/effects/SkGpuBlurUtils.cpp b/effects/SkGpuBlurUtils.cpp
new file mode 100644
index 00000000..5fc51811
--- /dev/null
+++ b/effects/SkGpuBlurUtils.cpp
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkGpuBlurUtils.h"
+
+#include "SkRect.h"
+
+#if SK_SUPPORT_GPU
+#include "effects/GrConvolutionEffect.h"
+#include "effects/GrTextureDomainEffect.h"
+#include "GrContext.h"
+#endif
+
+namespace SkGpuBlurUtils {
+
+#if SK_SUPPORT_GPU
+
+#define MAX_BLUR_SIGMA 4.0f
+
+static void scale_rect(SkRect* rect, float xScale, float yScale) {
+ rect->fLeft = SkScalarMul(rect->fLeft, SkFloatToScalar(xScale));
+ rect->fTop = SkScalarMul(rect->fTop, SkFloatToScalar(yScale));
+ rect->fRight = SkScalarMul(rect->fRight, SkFloatToScalar(xScale));
+ rect->fBottom = SkScalarMul(rect->fBottom, SkFloatToScalar(yScale));
+}
+
+static float adjust_sigma(float sigma, int *scaleFactor, int *radius) {
+ *scaleFactor = 1;
+ while (sigma > MAX_BLUR_SIGMA) {
+ *scaleFactor *= 2;
+ sigma *= 0.5f;
+ }
+ *radius = static_cast<int>(ceilf(sigma * 3.0f));
+ GrAssert(*radius <= GrConvolutionEffect::kMaxKernelRadius);
+ return sigma;
+}
+
+static void convolve_gaussian_pass(GrContext* context,
+ const SkRect& srcRect,
+ const SkRect& dstRect,
+ GrTexture* texture,
+ Gr1DKernelEffect::Direction direction,
+ int radius,
+ float sigma,
+ bool useBounds,
+ float bounds[2]) {
+ GrPaint paint;
+ paint.reset();
+ SkAutoTUnref<GrEffectRef> conv(GrConvolutionEffect::CreateGaussian(
+ texture, direction, radius, sigma, useBounds, bounds));
+ paint.reset();
+ paint.addColorEffect(conv);
+ context->drawRectToRect(paint, dstRect, srcRect);
+}
+
+static void convolve_gaussian(GrContext* context,
+ const SkRect& srcRect,
+ const SkRect& dstRect,
+ GrTexture* texture,
+ Gr1DKernelEffect::Direction direction,
+ int radius,
+ float sigma,
+ bool cropToSrcRect) {
+ float bounds[2] = { 0.0f, 1.0f };
+ if (!cropToSrcRect) {
+ convolve_gaussian_pass(context, srcRect, dstRect, texture,
+ direction, radius, sigma, false, bounds);
+ return;
+ }
+ SkRect lowerSrcRect = srcRect, lowerDstRect = dstRect;
+ SkRect middleSrcRect = srcRect, middleDstRect = dstRect;
+ SkRect upperSrcRect = srcRect, upperDstRect = dstRect;
+ SkScalar size;
+ SkScalar rad = SkIntToScalar(radius);
+ if (direction == Gr1DKernelEffect::kX_Direction) {
+ bounds[0] = SkScalarToFloat(srcRect.left()) / texture->width();
+ bounds[1] = SkScalarToFloat(srcRect.right()) / texture->width();
+ size = srcRect.width();
+ lowerSrcRect.fRight = srcRect.left() + rad;
+ lowerDstRect.fRight = dstRect.left() + rad;
+ upperSrcRect.fLeft = srcRect.right() - rad;
+ upperDstRect.fLeft = dstRect.right() - rad;
+ middleSrcRect.inset(rad, 0);
+ middleDstRect.inset(rad, 0);
+ } else {
+ bounds[0] = SkScalarToFloat(srcRect.top()) / texture->height();
+ bounds[1] = SkScalarToFloat(srcRect.bottom()) / texture->height();
+ size = srcRect.height();
+ lowerSrcRect.fBottom = srcRect.top() + rad;
+ lowerDstRect.fBottom = dstRect.top() + rad;
+ upperSrcRect.fTop = srcRect.bottom() - rad;
+ upperDstRect.fTop = dstRect.bottom() - rad;
+ middleSrcRect.inset(0, rad);
+ middleDstRect.inset(0, rad);
+ }
+ if (radius >= size * SK_ScalarHalf) {
+ // Blur radius covers srcRect; use bounds over entire draw
+ convolve_gaussian_pass(context, srcRect, dstRect, texture,
+ direction, radius, sigma, true, bounds);
+ } else {
+ // Draw upper and lower margins with bounds; middle without.
+ convolve_gaussian_pass(context, lowerSrcRect, lowerDstRect, texture,
+ direction, radius, sigma, true, bounds);
+ convolve_gaussian_pass(context, upperSrcRect, upperDstRect, texture,
+ direction, radius, sigma, true, bounds);
+ convolve_gaussian_pass(context, middleSrcRect, middleDstRect, texture,
+ direction, radius, sigma, false, bounds);
+ }
+}
+
+GrTexture* GaussianBlur(GrContext* context,
+ GrTexture* srcTexture,
+ bool canClobberSrc,
+ const SkRect& rect,
+ bool cropToRect,
+ float sigmaX,
+ float sigmaY) {
+ GrAssert(NULL != context);
+
+ GrContext::AutoRenderTarget art(context);
+
+ GrContext::AutoMatrix am;
+ am.setIdentity(context);
+
+ SkIRect clearRect;
+ int scaleFactorX, radiusX;
+ int scaleFactorY, radiusY;
+ sigmaX = adjust_sigma(sigmaX, &scaleFactorX, &radiusX);
+ sigmaY = adjust_sigma(sigmaY, &scaleFactorY, &radiusY);
+
+ SkRect srcRect(rect);
+ scale_rect(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY);
+ srcRect.roundOut();
+ scale_rect(&srcRect, static_cast<float>(scaleFactorX),
+ static_cast<float>(scaleFactorY));
+
+ GrContext::AutoClip acs(context, SkRect::MakeWH(srcRect.width(), srcRect.height()));
+
+ GrAssert(kBGRA_8888_GrPixelConfig == srcTexture->config() ||
+ kRGBA_8888_GrPixelConfig == srcTexture->config() ||
+ kAlpha_8_GrPixelConfig == srcTexture->config());
+
+ GrTextureDesc desc;
+ desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
+ desc.fWidth = SkScalarFloorToInt(srcRect.width());
+ desc.fHeight = SkScalarFloorToInt(srcRect.height());
+ desc.fConfig = srcTexture->config();
+
+ GrAutoScratchTexture temp1, temp2;
+ GrTexture* dstTexture = temp1.set(context, desc);
+ GrTexture* tempTexture = canClobberSrc ? srcTexture : temp2.set(context, desc);
+ if (NULL == dstTexture || NULL == tempTexture) {
+ return NULL;
+ }
+
+ for (int i = 1; i < scaleFactorX || i < scaleFactorY; i *= 2) {
+ GrPaint paint;
+ SkMatrix matrix;
+ matrix.setIDiv(srcTexture->width(), srcTexture->height());
+ context->setRenderTarget(dstTexture->asRenderTarget());
+ SkRect dstRect(srcRect);
+ if (cropToRect && i == 1) {
+ dstRect.offset(-dstRect.fLeft, -dstRect.fTop);
+ SkRect domain;
+ matrix.mapRect(&domain, rect);
+ domain.inset(i < scaleFactorX ? SK_ScalarHalf / srcTexture->width() : 0.0f,
+ i < scaleFactorY ? SK_ScalarHalf / srcTexture->height() : 0.0f);
+ SkAutoTUnref<GrEffectRef> effect(GrTextureDomainEffect::Create(
+ srcTexture,
+ matrix,
+ domain,
+ GrTextureDomainEffect::kDecal_WrapMode,
+ GrTextureParams::kBilerp_FilterMode));
+ paint.addColorEffect(effect);
+ } else {
+ GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode);
+ paint.addColorTextureEffect(srcTexture, matrix, params);
+ }
+ scale_rect(&dstRect, i < scaleFactorX ? 0.5f : 1.0f,
+ i < scaleFactorY ? 0.5f : 1.0f);
+ context->drawRectToRect(paint, dstRect, srcRect);
+ srcRect = dstRect;
+ srcTexture = dstTexture;
+ SkTSwap(dstTexture, tempTexture);
+ }
+
+ SkIRect srcIRect;
+ srcRect.roundOut(&srcIRect);
+
+ if (sigmaX > 0.0f) {
+ if (scaleFactorX > 1) {
+ // Clear out a radius to the right of the srcRect to prevent the
+ // X convolution from reading garbage.
+ clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop,
+ radiusX, srcIRect.height());
+ context->clear(&clearRect, 0x0);
+ }
+ context->setRenderTarget(dstTexture->asRenderTarget());
+ SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
+ convolve_gaussian(context, srcRect, dstRect, srcTexture,
+ Gr1DKernelEffect::kX_Direction, radiusX, sigmaX, cropToRect);
+ srcTexture = dstTexture;
+ srcRect = dstRect;
+ SkTSwap(dstTexture, tempTexture);
+ }
+
+ if (sigmaY > 0.0f) {
+ if (scaleFactorY > 1 || sigmaX > 0.0f) {
+ // Clear out a radius below the srcRect to prevent the Y
+ // convolution from reading garbage.
+ clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom,
+ srcIRect.width(), radiusY);
+ context->clear(&clearRect, 0x0);
+ }
+
+ context->setRenderTarget(dstTexture->asRenderTarget());
+ SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
+ convolve_gaussian(context, srcRect, dstRect, srcTexture,
+ Gr1DKernelEffect::kY_Direction, radiusY, sigmaY, cropToRect);
+ srcTexture = dstTexture;
+ srcRect = dstRect;
+ SkTSwap(dstTexture, tempTexture);
+ }
+
+ if (scaleFactorX > 1 || scaleFactorY > 1) {
+ // Clear one pixel to the right and below, to accommodate bilinear
+ // upsampling.
+ clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom,
+ srcIRect.width() + 1, 1);
+ context->clear(&clearRect, 0x0);
+ clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop,
+ 1, srcIRect.height());
+ context->clear(&clearRect, 0x0);
+ SkMatrix matrix;
+ matrix.setIDiv(srcTexture->width(), srcTexture->height());
+ context->setRenderTarget(dstTexture->asRenderTarget());
+
+ GrPaint paint;
+ // FIXME: this should be mitchell, not bilinear.
+ GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode);
+ paint.addColorTextureEffect(srcTexture, matrix, params);
+
+ SkRect dstRect(srcRect);
+ scale_rect(&dstRect, (float) scaleFactorX, (float) scaleFactorY);
+ context->drawRectToRect(paint, dstRect, srcRect);
+ srcRect = dstRect;
+ srcTexture = dstTexture;
+ SkTSwap(dstTexture, tempTexture);
+ }
+ if (srcTexture == temp1.texture()) {
+ return temp1.detach();
+ } else if (srcTexture == temp2.texture()) {
+ return temp2.detach();
+ } else {
+ srcTexture->ref();
+ return srcTexture;
+ }
+}
+#endif
+
+}
diff --git a/effects/SkGpuBlurUtils.h b/effects/SkGpuBlurUtils.h
new file mode 100644
index 00000000..98be8130
--- /dev/null
+++ b/effects/SkGpuBlurUtils.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkGpuBlurUtils_DEFINED
+#define SkGpuBlurUtils_DEFINED
+
+#if SK_SUPPORT_GPU
+class GrTexture;
+class GrContext;
+#endif
+
+struct SkRect;
+
+namespace SkGpuBlurUtils {
+
+#if SK_SUPPORT_GPU
+ /**
+ * Applies a 2D Gaussian blur to a given texture.
+ * @param context The GPU context
+ * @param srcTexture The source texture to be blurred.
+ * @param canClobberSrc If true, srcTexture may be overwritten, and
+ * may be returned as the result.
+ * @param rect The destination rectangle.
+ * @param cropToRect If true, do not sample any pixels outside the
+ * source rect.
+ * @param sigmaX The blur's standard deviation in X.
+ * @param sigmaY The blur's standard deviation in Y.
+ * @return the blurred texture, which may be srcTexture reffed, or a
+ * new texture. It is the caller's responsibility to unref this texture.
+ */
+ GrTexture* GaussianBlur(GrContext* context,
+ GrTexture* srcTexture,
+ bool canClobberSrc,
+ const SkRect& rect,
+ bool cropToRect,
+ float sigmaX,
+ float sigmaY);
+#endif
+
+};
+
+#endif
diff --git a/effects/SkKernel33MaskFilter.cpp b/effects/SkKernel33MaskFilter.cpp
new file mode 100644
index 00000000..485001bb
--- /dev/null
+++ b/effects/SkKernel33MaskFilter.cpp
@@ -0,0 +1,142 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "SkKernel33MaskFilter.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkString.h"
+
+SkMask::Format SkKernel33ProcMaskFilter::getFormat() const {
+ return SkMask::kA8_Format;
+}
+
+bool SkKernel33ProcMaskFilter::filterMask(SkMask* dst, const SkMask& src,
+ const SkMatrix&, SkIPoint* margin) const {
+ // margin???
+ dst->fImage = NULL;
+ dst->fBounds = src.fBounds;
+ dst->fBounds.inset(-1, -1);
+ dst->fFormat = SkMask::kA8_Format;
+
+ if (NULL == src.fImage) {
+ return true;
+ }
+
+ dst->fRowBytes = dst->fBounds.width();
+ size_t size = dst->computeImageSize();
+ if (0 == size) {
+ return false; // too big to allocate, abort
+ }
+ dst->fImage = SkMask::AllocImage(size);
+
+ const int h = src.fBounds.height();
+ const int w = src.fBounds.width();
+ const int srcRB = src.fRowBytes;
+ const uint8_t* srcImage = src.fImage;
+ uint8_t* dstImage = dst->fImage;
+
+ uint8_t* srcRows[3];
+ uint8_t storage[3][3];
+
+ srcRows[0] = storage[0];
+ srcRows[1] = storage[1];
+ srcRows[2] = storage[2];
+
+ unsigned scale = fPercent256;
+
+ for (int y = -1; y <= h; y++) {
+ uint8_t* dstRow = dstImage;
+ for (int x = -1; x <= w; x++) {
+ memset(storage, 0, sizeof(storage));
+ uint8_t* storagePtr = &storage[0][0];
+
+ for (int ky = y - 1; ky <= y + 1; ky++) {
+ const uint8_t* srcRow = srcImage + ky * srcRB; // may be out-of-range
+ for (int kx = x - 1; kx <= x + 1; kx++) {
+ if ((unsigned)ky < (unsigned)h && (unsigned)kx < (unsigned)w) {
+ *storagePtr = srcRow[kx];
+ }
+ storagePtr++;
+ }
+ }
+ int value = this->computeValue(srcRows);
+
+ if (scale < 256) {
+ value = SkAlphaBlend(value, srcRows[1][1], scale);
+ }
+ *dstRow++ = SkToU8(value);
+ }
+ dstImage += dst->fRowBytes;
+ }
+ return true;
+}
+
+void SkKernel33ProcMaskFilter::flatten(SkFlattenableWriteBuffer& wb) const {
+ this->INHERITED::flatten(wb);
+ wb.writeInt(fPercent256);
+}
+
+SkKernel33ProcMaskFilter::SkKernel33ProcMaskFilter(SkFlattenableReadBuffer& rb)
+ : SkMaskFilter(rb) {
+ fPercent256 = rb.readInt();
+}
+
+#ifdef SK_DEVELOPER
+void SkKernel33ProcMaskFilter::toString(SkString* str) const {
+ str->appendf("percent256: %d, ", fPercent256);
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint8_t SkKernel33MaskFilter::computeValue(uint8_t* const* srcRows) const {
+ int value = 0;
+
+ for (int i = 0; i < 3; i++) {
+ for (int j = 0; j < 3; j++) {
+ value += fKernel[i][j] * srcRows[i][j];
+ }
+ }
+
+ value >>= fShift;
+
+ if (value < 0) {
+ value = 0;
+ } else if (value > 255) {
+ value = 255;
+ }
+ return (uint8_t)value;
+}
+
+void SkKernel33MaskFilter::flatten(SkFlattenableWriteBuffer& wb) const {
+ this->INHERITED::flatten(wb);
+ wb.writeIntArray(&fKernel[0][0], 9);
+ wb.writeInt(fShift);
+}
+
+SkKernel33MaskFilter::SkKernel33MaskFilter(SkFlattenableReadBuffer& rb)
+ : SkKernel33ProcMaskFilter(rb) {
+ SkDEBUGCODE(const uint32_t count = )rb.readIntArray(&fKernel[0][0]);
+ SkASSERT(9 == count);
+ fShift = rb.readInt();
+}
+
+#ifdef SK_DEVELOPER
+void SkKernel33MaskFilter::toString(SkString* str) const {
+ str->append("SkKernel33MaskFilter: (");
+
+ str->appendf("kernel: (%d, %d, %d, %d, %d, %d, %d, %d, %d), ",
+ fKernel[0][0], fKernel[0][1], fKernel[0][2],
+ fKernel[1][0], fKernel[1][1], fKernel[1][2],
+ fKernel[2][0], fKernel[2][1], fKernel[2][2]);
+ str->appendf("shift: %d, ", fShift);
+
+ this->INHERITED::toString(str);
+
+ str->append(")");
+}
+#endif
diff --git a/effects/SkLayerDrawLooper.cpp b/effects/SkLayerDrawLooper.cpp
new file mode 100644
index 00000000..998c4bcd
--- /dev/null
+++ b/effects/SkLayerDrawLooper.cpp
@@ -0,0 +1,350 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "SkCanvas.h"
+#include "SkColor.h"
+#include "SkFlattenableBuffers.h"
+#include "SkLayerDrawLooper.h"
+#include "SkString.h"
+#include "SkStringUtils.h"
+#include "SkUnPreMultiply.h"
+
+SK_DEFINE_INST_COUNT(SkLayerDrawLooper)
+
+SkLayerDrawLooper::LayerInfo::LayerInfo() {
+ fFlagsMask = 0; // ignore our paint flags
+ fPaintBits = 0; // ignore our paint fields
+ fColorMode = SkXfermode::kDst_Mode; // ignore our color
+ fOffset.set(0, 0);
+ fPostTranslate = false;
+}
+
+SkLayerDrawLooper::SkLayerDrawLooper()
+ : fRecs(NULL),
+ fTopRec(NULL),
+ fCount(0),
+ fCurrRec(NULL) {
+}
+
+SkLayerDrawLooper::~SkLayerDrawLooper() {
+ Rec* rec = fRecs;
+ while (rec) {
+ Rec* next = rec->fNext;
+ SkDELETE(rec);
+ rec = next;
+ }
+}
+
+SkPaint* SkLayerDrawLooper::addLayer(const LayerInfo& info) {
+ fCount += 1;
+
+ Rec* rec = SkNEW(Rec);
+ rec->fNext = fRecs;
+ rec->fInfo = info;
+ fRecs = rec;
+ if (NULL == fTopRec) {
+ fTopRec = rec;
+ }
+
+ return &rec->fPaint;
+}
+
+void SkLayerDrawLooper::addLayer(SkScalar dx, SkScalar dy) {
+ LayerInfo info;
+
+ info.fOffset.set(dx, dy);
+ (void)this->addLayer(info);
+}
+
+SkPaint* SkLayerDrawLooper::addLayerOnTop(const LayerInfo& info) {
+ fCount += 1;
+
+ Rec* rec = SkNEW(Rec);
+ rec->fNext = NULL;
+ rec->fInfo = info;
+ if (NULL == fRecs) {
+ fRecs = rec;
+ } else {
+ SkASSERT(NULL != fTopRec);
+ fTopRec->fNext = rec;
+ }
+ fTopRec = rec;
+
+ return &rec->fPaint;
+}
+
+void SkLayerDrawLooper::init(SkCanvas* canvas) {
+ fCurrRec = fRecs;
+ canvas->save(SkCanvas::kMatrix_SaveFlag);
+}
+
+static SkColor xferColor(SkColor src, SkColor dst, SkXfermode::Mode mode) {
+ switch (mode) {
+ case SkXfermode::kSrc_Mode:
+ return src;
+ case SkXfermode::kDst_Mode:
+ return dst;
+ default: {
+ SkPMColor pmS = SkPreMultiplyColor(src);
+ SkPMColor pmD = SkPreMultiplyColor(dst);
+ SkPMColor result = SkXfermode::GetProc(mode)(pmS, pmD);
+ return SkUnPreMultiply::PMColorToColor(result);
+ }
+ }
+}
+
+// Even with kEntirePaint_Bits, we always ensure that the master paint's
+// text-encoding is respected, since that controls how we interpret the
+// text/length parameters of a draw[Pos]Text call.
+void SkLayerDrawLooper::ApplyInfo(SkPaint* dst, const SkPaint& src,
+ const LayerInfo& info) {
+
+ uint32_t mask = info.fFlagsMask;
+ dst->setFlags((dst->getFlags() & ~mask) | (src.getFlags() & mask));
+ dst->setColor(xferColor(src.getColor(), dst->getColor(), info.fColorMode));
+
+ BitFlags bits = info.fPaintBits;
+ SkPaint::TextEncoding encoding = dst->getTextEncoding();
+
+ if (0 == bits) {
+ return;
+ }
+ if (kEntirePaint_Bits == bits) {
+ // we've already computed these, so save it from the assignment
+ uint32_t f = dst->getFlags();
+ SkColor c = dst->getColor();
+ *dst = src;
+ dst->setFlags(f);
+ dst->setColor(c);
+ dst->setTextEncoding(encoding);
+ return;
+ }
+
+ if (bits & kStyle_Bit) {
+ dst->setStyle(src.getStyle());
+ dst->setStrokeWidth(src.getStrokeWidth());
+ dst->setStrokeMiter(src.getStrokeMiter());
+ dst->setStrokeCap(src.getStrokeCap());
+ dst->setStrokeJoin(src.getStrokeJoin());
+ }
+
+ if (bits & kTextSkewX_Bit) {
+ dst->setTextSkewX(src.getTextSkewX());
+ }
+
+ if (bits & kPathEffect_Bit) {
+ dst->setPathEffect(src.getPathEffect());
+ }
+ if (bits & kMaskFilter_Bit) {
+ dst->setMaskFilter(src.getMaskFilter());
+ }
+ if (bits & kShader_Bit) {
+ dst->setShader(src.getShader());
+ }
+ if (bits & kColorFilter_Bit) {
+ dst->setColorFilter(src.getColorFilter());
+ }
+ if (bits & kXfermode_Bit) {
+ dst->setXfermode(src.getXfermode());
+ }
+
+ // we don't override these
+#if 0
+ dst->setTypeface(src.getTypeface());
+ dst->setTextSize(src.getTextSize());
+ dst->setTextScaleX(src.getTextScaleX());
+ dst->setRasterizer(src.getRasterizer());
+ dst->setLooper(src.getLooper());
+ dst->setTextEncoding(src.getTextEncoding());
+ dst->setHinting(src.getHinting());
+#endif
+}
+
+// Should we add this to canvas?
+static void postTranslate(SkCanvas* canvas, SkScalar dx, SkScalar dy) {
+ SkMatrix m = canvas->getTotalMatrix();
+ m.postTranslate(dx, dy);
+ canvas->setMatrix(m);
+}
+
+bool SkLayerDrawLooper::next(SkCanvas* canvas, SkPaint* paint) {
+ canvas->restore();
+ if (NULL == fCurrRec) {
+ return false;
+ }
+
+ ApplyInfo(paint, fCurrRec->fPaint, fCurrRec->fInfo);
+
+ canvas->save(SkCanvas::kMatrix_SaveFlag);
+ if (fCurrRec->fInfo.fPostTranslate) {
+ postTranslate(canvas, fCurrRec->fInfo.fOffset.fX,
+ fCurrRec->fInfo.fOffset.fY);
+ } else {
+ canvas->translate(fCurrRec->fInfo.fOffset.fX, fCurrRec->fInfo.fOffset.fY);
+ }
+ fCurrRec = fCurrRec->fNext;
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkLayerDrawLooper::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+
+#ifdef SK_DEBUG
+ {
+ Rec* rec = fRecs;
+ int count = 0;
+ while (rec) {
+ rec = rec->fNext;
+ count += 1;
+ }
+ SkASSERT(count == fCount);
+ }
+#endif
+
+ buffer.writeInt(fCount);
+
+ Rec* rec = fRecs;
+ for (int i = 0; i < fCount; i++) {
+ buffer.writeInt(rec->fInfo.fFlagsMask);
+ buffer.writeInt(rec->fInfo.fPaintBits);
+ buffer.writeInt(rec->fInfo.fColorMode);
+ buffer.writePoint(rec->fInfo.fOffset);
+ buffer.writeBool(rec->fInfo.fPostTranslate);
+ buffer.writePaint(rec->fPaint);
+ rec = rec->fNext;
+ }
+}
+
+SkLayerDrawLooper::SkLayerDrawLooper(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer),
+ fRecs(NULL),
+ fTopRec(NULL),
+ fCount(0),
+ fCurrRec(NULL) {
+ int count = buffer.readInt();
+
+ for (int i = 0; i < count; i++) {
+ LayerInfo info;
+ info.fFlagsMask = buffer.readInt();
+ info.fPaintBits = buffer.readInt();
+ info.fColorMode = (SkXfermode::Mode)buffer.readInt();
+ buffer.readPoint(&info.fOffset);
+ info.fPostTranslate = buffer.readBool();
+ buffer.readPaint(this->addLayerOnTop(info));
+ }
+ SkASSERT(count == fCount);
+
+#ifdef SK_DEBUG
+ {
+ Rec* rec = fRecs;
+ int n = 0;
+ while (rec) {
+ rec = rec->fNext;
+ n += 1;
+ }
+ SkASSERT(count == n);
+ }
+#endif
+}
+
+#ifdef SK_DEVELOPER
+void SkLayerDrawLooper::toString(SkString* str) const {
+ str->appendf("SkLayerDrawLooper (%d): ", fCount);
+
+ Rec* rec = fRecs;
+ for (int i = 0; i < fCount; i++) {
+ str->appendf("%d: ", i);
+
+ str->append("flagsMask: (");
+ if (0 == rec->fInfo.fFlagsMask) {
+ str->append("None");
+ } else {
+ bool needSeparator = false;
+ SkAddFlagToString(str, SkToBool(SkPaint::kAntiAlias_Flag & rec->fInfo.fFlagsMask),
+ "AntiAlias", &needSeparator);
+// SkAddFlagToString(str, SkToBool(SkPaint::kFilterBitmap_Flag & rec->fInfo.fFlagsMask), "FilterBitmap", &needSeparator);
+ SkAddFlagToString(str, SkToBool(SkPaint::kDither_Flag & rec->fInfo.fFlagsMask),
+ "Dither", &needSeparator);
+ SkAddFlagToString(str, SkToBool(SkPaint::kUnderlineText_Flag & rec->fInfo.fFlagsMask),
+ "UnderlineText", &needSeparator);
+ SkAddFlagToString(str, SkToBool(SkPaint::kStrikeThruText_Flag & rec->fInfo.fFlagsMask),
+ "StrikeThruText", &needSeparator);
+ SkAddFlagToString(str, SkToBool(SkPaint::kFakeBoldText_Flag & rec->fInfo.fFlagsMask),
+ "FakeBoldText", &needSeparator);
+ SkAddFlagToString(str, SkToBool(SkPaint::kLinearText_Flag & rec->fInfo.fFlagsMask),
+ "LinearText", &needSeparator);
+ SkAddFlagToString(str, SkToBool(SkPaint::kSubpixelText_Flag & rec->fInfo.fFlagsMask),
+ "SubpixelText", &needSeparator);
+ SkAddFlagToString(str, SkToBool(SkPaint::kDevKernText_Flag & rec->fInfo.fFlagsMask),
+ "DevKernText", &needSeparator);
+ SkAddFlagToString(str, SkToBool(SkPaint::kLCDRenderText_Flag & rec->fInfo.fFlagsMask),
+ "LCDRenderText", &needSeparator);
+ SkAddFlagToString(str, SkToBool(SkPaint::kEmbeddedBitmapText_Flag & rec->fInfo.fFlagsMask),
+ "EmbeddedBitmapText", &needSeparator);
+ SkAddFlagToString(str, SkToBool(SkPaint::kAutoHinting_Flag & rec->fInfo.fFlagsMask),
+ "Autohinted", &needSeparator);
+ SkAddFlagToString(str, SkToBool(SkPaint::kVerticalText_Flag & rec->fInfo.fFlagsMask),
+ "VerticalText", &needSeparator);
+ SkAddFlagToString(str, SkToBool(SkPaint::kGenA8FromLCD_Flag & rec->fInfo.fFlagsMask),
+ "GenA8FromLCD", &needSeparator);
+ }
+ str->append(") ");
+
+ str->append("paintBits: (");
+ if (0 == rec->fInfo.fPaintBits) {
+ str->append("None");
+ } else if (kEntirePaint_Bits == rec->fInfo.fPaintBits) {
+ str->append("EntirePaint");
+ } else {
+ bool needSeparator = false;
+ SkAddFlagToString(str, SkToBool(kStyle_Bit & rec->fInfo.fPaintBits), "Style",
+ &needSeparator);
+ SkAddFlagToString(str, SkToBool(kTextSkewX_Bit & rec->fInfo.fPaintBits), "TextSkewX",
+ &needSeparator);
+ SkAddFlagToString(str, SkToBool(kPathEffect_Bit & rec->fInfo.fPaintBits), "PathEffect",
+ &needSeparator);
+ SkAddFlagToString(str, SkToBool(kMaskFilter_Bit & rec->fInfo.fPaintBits), "MaskFilter",
+ &needSeparator);
+ SkAddFlagToString(str, SkToBool(kShader_Bit & rec->fInfo.fPaintBits), "Shader",
+ &needSeparator);
+ SkAddFlagToString(str, SkToBool(kColorFilter_Bit & rec->fInfo.fPaintBits), "ColorFilter",
+ &needSeparator);
+ SkAddFlagToString(str, SkToBool(kXfermode_Bit & rec->fInfo.fPaintBits), "Xfermode",
+ &needSeparator);
+ }
+ str->append(") ");
+
+ static const char* gModeStrings[SkXfermode::kLastMode+1] = {
+ "kClear", "kSrc", "kDst", "kSrcOver", "kDstOver", "kSrcIn", "kDstIn",
+ "kSrcOut", "kDstOut", "kSrcATop", "kDstATop", "kXor", "kPlus",
+ "kMultiply", "kScreen", "kOverlay", "kDarken", "kLighten", "kColorDodge",
+ "kColorBurn", "kHardLight", "kSoftLight", "kDifference", "kExclusion"
+ };
+
+ str->appendf("mode: %s ", gModeStrings[rec->fInfo.fColorMode]);
+
+ str->append("offset: (");
+ str->appendScalar(rec->fInfo.fOffset.fX);
+ str->append(", ");
+ str->appendScalar(rec->fInfo.fOffset.fY);
+ str->append(") ");
+
+ str->append("postTranslate: ");
+ if (rec->fInfo.fPostTranslate) {
+ str->append("true ");
+ } else {
+ str->append("false ");
+ }
+
+ rec->fPaint.toString(str);
+ rec = rec->fNext;
+ }
+}
+#endif
diff --git a/effects/SkLayerRasterizer.cpp b/effects/SkLayerRasterizer.cpp
new file mode 100644
index 00000000..ea5808c2
--- /dev/null
+++ b/effects/SkLayerRasterizer.cpp
@@ -0,0 +1,171 @@
+
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#include "SkLayerRasterizer.h"
+#include "SkDraw.h"
+#include "SkFlattenableBuffers.h"
+#include "SkMask.h"
+#include "SkMaskFilter.h"
+#include "SkPaint.h"
+#include "SkPath.h"
+#include "SkPathEffect.h"
+#include "../core/SkRasterClip.h"
+#include "SkXfermode.h"
+#include <new>
+
+struct SkLayerRasterizer_Rec {
+ SkPaint fPaint;
+ SkVector fOffset;
+};
+
+SkLayerRasterizer::SkLayerRasterizer() : fLayers(sizeof(SkLayerRasterizer_Rec))
+{
+}
+
+SkLayerRasterizer::~SkLayerRasterizer() {
+ SkDeque::F2BIter iter(fLayers);
+ SkLayerRasterizer_Rec* rec;
+
+ while ((rec = (SkLayerRasterizer_Rec*)iter.next()) != NULL)
+ rec->fPaint.~SkPaint();
+}
+
+void SkLayerRasterizer::addLayer(const SkPaint& paint, SkScalar dx,
+ SkScalar dy) {
+ SkLayerRasterizer_Rec* rec = (SkLayerRasterizer_Rec*)fLayers.push_back();
+
+ SkNEW_PLACEMENT_ARGS(&rec->fPaint, SkPaint, (paint));
+ rec->fOffset.set(dx, dy);
+}
+
+static bool compute_bounds(const SkDeque& layers, const SkPath& path,
+ const SkMatrix& matrix,
+ const SkIRect* clipBounds, SkIRect* bounds) {
+ SkDeque::F2BIter iter(layers);
+ SkLayerRasterizer_Rec* rec;
+
+ bounds->set(SK_MaxS32, SK_MaxS32, SK_MinS32, SK_MinS32);
+
+ while ((rec = (SkLayerRasterizer_Rec*)iter.next()) != NULL) {
+ const SkPaint& paint = rec->fPaint;
+ SkPath fillPath, devPath;
+ const SkPath* p = &path;
+
+ if (paint.getPathEffect() || paint.getStyle() != SkPaint::kFill_Style) {
+ paint.getFillPath(path, &fillPath);
+ p = &fillPath;
+ }
+ if (p->isEmpty()) {
+ continue;
+ }
+
+ // apply the matrix and offset
+ {
+ SkMatrix m = matrix;
+ m.preTranslate(rec->fOffset.fX, rec->fOffset.fY);
+ p->transform(m, &devPath);
+ }
+
+ SkMask mask;
+ if (!SkDraw::DrawToMask(devPath, clipBounds, paint.getMaskFilter(),
+ &matrix, &mask,
+ SkMask::kJustComputeBounds_CreateMode,
+ SkPaint::kFill_Style)) {
+ return false;
+ }
+
+ bounds->join(mask.fBounds);
+ }
+ return true;
+}
+
+bool SkLayerRasterizer::onRasterize(const SkPath& path, const SkMatrix& matrix,
+ const SkIRect* clipBounds,
+ SkMask* mask, SkMask::CreateMode mode) const {
+ if (fLayers.empty()) {
+ return false;
+ }
+
+ if (SkMask::kJustRenderImage_CreateMode != mode) {
+ if (!compute_bounds(fLayers, path, matrix, clipBounds, &mask->fBounds))
+ return false;
+ }
+
+ if (SkMask::kComputeBoundsAndRenderImage_CreateMode == mode) {
+ mask->fFormat = SkMask::kA8_Format;
+ mask->fRowBytes = mask->fBounds.width();
+ size_t size = mask->computeImageSize();
+ if (0 == size) {
+ return false; // too big to allocate, abort
+ }
+ mask->fImage = SkMask::AllocImage(size);
+ memset(mask->fImage, 0, size);
+ }
+
+ if (SkMask::kJustComputeBounds_CreateMode != mode) {
+ SkBitmap device;
+ SkRasterClip rectClip;
+ SkDraw draw;
+ SkMatrix translatedMatrix; // this translates us to our local pixels
+ SkMatrix drawMatrix; // this translates the path by each layer's offset
+
+ rectClip.setRect(SkIRect::MakeWH(mask->fBounds.width(), mask->fBounds.height()));
+
+ translatedMatrix = matrix;
+ translatedMatrix.postTranslate(-SkIntToScalar(mask->fBounds.fLeft),
+ -SkIntToScalar(mask->fBounds.fTop));
+
+ device.setConfig(SkBitmap::kA8_Config, mask->fBounds.width(), mask->fBounds.height(), mask->fRowBytes);
+ device.setPixels(mask->fImage);
+
+ draw.fBitmap = &device;
+ draw.fMatrix = &drawMatrix;
+ draw.fRC = &rectClip;
+ draw.fClip = &rectClip.bwRgn();
+ // we set the matrixproc in the loop, as the matrix changes each time (potentially)
+ draw.fBounder = NULL;
+
+ SkDeque::F2BIter iter(fLayers);
+ SkLayerRasterizer_Rec* rec;
+
+ while ((rec = (SkLayerRasterizer_Rec*)iter.next()) != NULL) {
+ drawMatrix = translatedMatrix;
+ drawMatrix.preTranslate(rec->fOffset.fX, rec->fOffset.fY);
+ draw.drawPath(path, rec->fPaint);
+ }
+ }
+ return true;
+}
+
+SkLayerRasterizer::SkLayerRasterizer(SkFlattenableReadBuffer& buffer)
+ : SkRasterizer(buffer), fLayers(sizeof(SkLayerRasterizer_Rec)) {
+ int count = buffer.readInt();
+
+ for (int i = 0; i < count; i++) {
+ SkLayerRasterizer_Rec* rec = (SkLayerRasterizer_Rec*)fLayers.push_back();
+
+ SkNEW_PLACEMENT(&rec->fPaint, SkPaint);
+ buffer.readPaint(&rec->fPaint);
+ buffer.readPoint(&rec->fOffset);
+ }
+}
+
+void SkLayerRasterizer::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+
+ buffer.writeInt(fLayers.count());
+
+ SkDeque::F2BIter iter(fLayers);
+ const SkLayerRasterizer_Rec* rec;
+
+ while ((rec = (const SkLayerRasterizer_Rec*)iter.next()) != NULL) {
+ buffer.writePaint(rec->fPaint);
+ buffer.writePoint(rec->fOffset);
+ }
+}
diff --git a/effects/SkLerpXfermode.cpp b/effects/SkLerpXfermode.cpp
new file mode 100644
index 00000000..d73ecf4c
--- /dev/null
+++ b/effects/SkLerpXfermode.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkLerpXfermode.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkString.h"
+
+SkXfermode* SkLerpXfermode::Create(SkScalar scale) {
+ int scale256 = SkScalarRoundToInt(scale * 256);
+ if (scale256 >= 256) {
+ return SkXfermode::Create(SkXfermode::kSrc_Mode);
+ } else if (scale256 <= 0) {
+ return SkXfermode::Create(SkXfermode::kDst_Mode);
+ }
+ return SkNEW_ARGS(SkLerpXfermode, (scale256));
+}
+
+SkLerpXfermode::SkLerpXfermode(unsigned scale256) : fScale256(scale256) {}
+
+SkLerpXfermode::SkLerpXfermode(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {
+ fScale256 = buffer.readUInt();
+}
+
+void SkLerpXfermode::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeUInt(fScale256);
+}
+
+void SkLerpXfermode::xfer32(SkPMColor dst[], const SkPMColor src[], int count,
+ const SkAlpha aa[]) const {
+ const int scale = fScale256;
+
+ if (aa) {
+ for (int i = 0; i < count; ++i) {
+ unsigned a = aa[i];
+ if (a) {
+ SkPMColor dstC = dst[i];
+ SkPMColor resC = SkFastFourByteInterp256(src[i], dstC, scale);
+ if (a < 255) {
+ resC = SkFastFourByteInterp256(resC, dstC, a + (a >> 7));
+ }
+ dst[i] = resC;
+ }
+ }
+ } else {
+ for (int i = 0; i < count; ++i) {
+ dst[i] = SkFastFourByteInterp256(src[i], dst[i], scale);
+ }
+ }
+}
+
+void SkLerpXfermode::xfer16(uint16_t dst[], const SkPMColor src[], int count,
+ const SkAlpha aa[]) const {
+ const int scale = fScale256;
+
+ if (aa) {
+ for (int i = 0; i < count; ++i) {
+ unsigned a = aa[i];
+ if (a) {
+ SkPMColor dstC = SkPixel16ToPixel32(dst[i]);
+ SkPMColor resC = SkFastFourByteInterp256(src[i], dstC, scale);
+ if (a < 255) {
+ resC = SkFastFourByteInterp256(resC, dstC, a + (a >> 7));
+ }
+ dst[i] = SkPixel32ToPixel16(resC);
+ }
+ }
+ } else {
+ for (int i = 0; i < count; ++i) {
+ SkPMColor dstC = SkPixel16ToPixel32(dst[i]);
+ SkPMColor resC = SkFastFourByteInterp256(src[i], dstC, scale);
+ dst[i] = SkPixel32ToPixel16(resC);
+ }
+ }
+}
+
+void SkLerpXfermode::xferA8(SkAlpha dst[], const SkPMColor src[], int count,
+ const SkAlpha aa[]) const {
+ const int scale = fScale256;
+
+ if (aa) {
+ for (int i = 0; i < count; ++i) {
+ unsigned a = aa[i];
+ if (a) {
+ unsigned dstA = dst[i];
+ unsigned resA = SkAlphaBlend(SkGetPackedA32(src[i]), dstA, scale);
+ if (a < 255) {
+ resA = SkAlphaBlend(resA, dstA, a + (a >> 7));
+ }
+ dst[i] = resA;
+ }
+ }
+ } else {
+ for (int i = 0; i < count; ++i) {
+ dst[i] = SkAlphaBlend(SkGetPackedA32(src[i]), dst[i], scale);
+ }
+ }
+}
+
+#ifdef SK_DEVELOPER
+void SkLerpXfermode::toString(SkString* str) const {
+ str->printf("SkLerpXfermode: scale: %g", fScale256 / 256.0);
+}
+#endif
diff --git a/effects/SkLightingImageFilter.cpp b/effects/SkLightingImageFilter.cpp
new file mode 100644
index 00000000..7a74f736
--- /dev/null
+++ b/effects/SkLightingImageFilter.cpp
@@ -0,0 +1,1553 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkLightingImageFilter.h"
+#include "SkBitmap.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkOrderedReadBuffer.h"
+#include "SkOrderedWriteBuffer.h"
+#include "SkTypes.h"
+
+#if SK_SUPPORT_GPU
+#include "effects/GrSingleTextureEffect.h"
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
+#include "GrEffect.h"
+#include "GrTBackendEffectFactory.h"
+
+class GrGLDiffuseLightingEffect;
+class GrGLSpecularLightingEffect;
+
+// For brevity
+typedef GrGLUniformManager::UniformHandle UniformHandle;
+static const UniformHandle kInvalidUniformHandle = GrGLUniformManager::kInvalidUniformHandle;
+#endif
+
+namespace {
+
+const SkScalar gOneThird = SkScalarInvert(SkIntToScalar(3));
+const SkScalar gTwoThirds = SkScalarDiv(SkIntToScalar(2), SkIntToScalar(3));
+const SkScalar gOneHalf = SkFloatToScalar(0.5f);
+const SkScalar gOneQuarter = SkFloatToScalar(0.25f);
+
+#if SK_SUPPORT_GPU
+void setUniformPoint3(const GrGLUniformManager& uman, UniformHandle uni, const SkPoint3& point) {
+ GR_STATIC_ASSERT(sizeof(SkPoint3) == 3 * sizeof(GrGLfloat));
+ uman.set3fv(uni, 0, 1, &point.fX);
+}
+
+void setUniformNormal3(const GrGLUniformManager& uman, UniformHandle uni, const SkPoint3& point) {
+ setUniformPoint3(uman, uni, SkPoint3(point.fX, point.fY, point.fZ));
+}
+#endif
+
+// Shift matrix components to the left, as we advance pixels to the right.
+inline void shiftMatrixLeft(int m[9]) {
+ m[0] = m[1];
+ m[3] = m[4];
+ m[6] = m[7];
+ m[1] = m[2];
+ m[4] = m[5];
+ m[7] = m[8];
+}
+
+class DiffuseLightingType {
+public:
+ DiffuseLightingType(SkScalar kd)
+ : fKD(kd) {}
+ SkPMColor light(const SkPoint3& normal, const SkPoint3& surfaceTolight, const SkPoint3& lightColor) const {
+ SkScalar colorScale = SkScalarMul(fKD, normal.dot(surfaceTolight));
+ colorScale = SkScalarClampMax(colorScale, SK_Scalar1);
+ SkPoint3 color(lightColor * colorScale);
+ return SkPackARGB32(255,
+ SkScalarFloorToInt(color.fX),
+ SkScalarFloorToInt(color.fY),
+ SkScalarFloorToInt(color.fZ));
+ }
+private:
+ SkScalar fKD;
+};
+
+class SpecularLightingType {
+public:
+ SpecularLightingType(SkScalar ks, SkScalar shininess)
+ : fKS(ks), fShininess(shininess) {}
+ SkPMColor light(const SkPoint3& normal, const SkPoint3& surfaceTolight, const SkPoint3& lightColor) const {
+ SkPoint3 halfDir(surfaceTolight);
+ halfDir.fZ += SK_Scalar1; // eye position is always (0, 0, 1)
+ halfDir.normalize();
+ SkScalar colorScale = SkScalarMul(fKS,
+ SkScalarPow(normal.dot(halfDir), fShininess));
+ colorScale = SkScalarClampMax(colorScale, SK_Scalar1);
+ SkPoint3 color(lightColor * colorScale);
+ return SkPackARGB32(SkScalarFloorToInt(color.maxComponent()),
+ SkScalarFloorToInt(color.fX),
+ SkScalarFloorToInt(color.fY),
+ SkScalarFloorToInt(color.fZ));
+ }
+private:
+ SkScalar fKS;
+ SkScalar fShininess;
+};
+
+inline SkScalar sobel(int a, int b, int c, int d, int e, int f, SkScalar scale) {
+ return SkScalarMul(SkIntToScalar(-a + b - 2 * c + 2 * d -e + f), scale);
+}
+
+inline SkPoint3 pointToNormal(SkScalar x, SkScalar y, SkScalar surfaceScale) {
+ SkPoint3 vector(SkScalarMul(-x, surfaceScale),
+ SkScalarMul(-y, surfaceScale),
+ SK_Scalar1);
+ vector.normalize();
+ return vector;
+}
+
+inline SkPoint3 topLeftNormal(int m[9], SkScalar surfaceScale) {
+ return pointToNormal(sobel(0, 0, m[4], m[5], m[7], m[8], gTwoThirds),
+ sobel(0, 0, m[4], m[7], m[5], m[8], gTwoThirds),
+ surfaceScale);
+}
+
+inline SkPoint3 topNormal(int m[9], SkScalar surfaceScale) {
+ return pointToNormal(sobel( 0, 0, m[3], m[5], m[6], m[8], gOneThird),
+ sobel(m[3], m[6], m[4], m[7], m[5], m[8], gOneHalf),
+ surfaceScale);
+}
+
+inline SkPoint3 topRightNormal(int m[9], SkScalar surfaceScale) {
+ return pointToNormal(sobel( 0, 0, m[3], m[4], m[6], m[7], gTwoThirds),
+ sobel(m[3], m[6], m[4], m[7], 0, 0, gTwoThirds),
+ surfaceScale);
+}
+
+inline SkPoint3 leftNormal(int m[9], SkScalar surfaceScale) {
+ return pointToNormal(sobel(m[1], m[2], m[4], m[5], m[7], m[8], gOneHalf),
+ sobel( 0, 0, m[1], m[7], m[2], m[8], gOneThird),
+ surfaceScale);
+}
+
+
+inline SkPoint3 interiorNormal(int m[9], SkScalar surfaceScale) {
+ return pointToNormal(sobel(m[0], m[2], m[3], m[5], m[6], m[8], gOneQuarter),
+ sobel(m[0], m[6], m[1], m[7], m[2], m[8], gOneQuarter),
+ surfaceScale);
+}
+
+inline SkPoint3 rightNormal(int m[9], SkScalar surfaceScale) {
+ return pointToNormal(sobel(m[0], m[1], m[3], m[4], m[6], m[7], gOneHalf),
+ sobel(m[0], m[6], m[1], m[7], 0, 0, gOneThird),
+ surfaceScale);
+}
+
+inline SkPoint3 bottomLeftNormal(int m[9], SkScalar surfaceScale) {
+ return pointToNormal(sobel(m[1], m[2], m[4], m[5], 0, 0, gTwoThirds),
+ sobel( 0, 0, m[1], m[4], m[2], m[5], gTwoThirds),
+ surfaceScale);
+}
+
+inline SkPoint3 bottomNormal(int m[9], SkScalar surfaceScale) {
+ return pointToNormal(sobel(m[0], m[2], m[3], m[5], 0, 0, gOneThird),
+ sobel(m[0], m[3], m[1], m[4], m[2], m[5], gOneHalf),
+ surfaceScale);
+}
+
+inline SkPoint3 bottomRightNormal(int m[9], SkScalar surfaceScale) {
+ return pointToNormal(sobel(m[0], m[1], m[3], m[4], 0, 0, gTwoThirds),
+ sobel(m[0], m[3], m[1], m[4], 0, 0, gTwoThirds),
+ surfaceScale);
+}
+
+template <class LightingType, class LightType> void lightBitmap(const LightingType& lightingType, const SkLight* light, const SkBitmap& src, SkBitmap* dst, SkScalar surfaceScale, const SkIRect& bounds) {
+ SkASSERT(dst->width() == bounds.width() && dst->height() == bounds.height());
+ const LightType* l = static_cast<const LightType*>(light);
+ int left = bounds.left(), right = bounds.right();
+ int bottom = bounds.bottom();
+ int y = bounds.top();
+ SkPMColor* dptr = dst->getAddr32(0, 0);
+ {
+ int x = left;
+ const SkPMColor* row1 = src.getAddr32(x, y);
+ const SkPMColor* row2 = src.getAddr32(x, y + 1);
+ int m[9];
+ m[4] = SkGetPackedA32(*row1++);
+ m[5] = SkGetPackedA32(*row1++);
+ m[7] = SkGetPackedA32(*row2++);
+ m[8] = SkGetPackedA32(*row2++);
+ SkPoint3 surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale);
+ *dptr++ = lightingType.light(topLeftNormal(m, surfaceScale), surfaceToLight, l->lightColor(surfaceToLight));
+ for (++x; x < right - 1; ++x)
+ {
+ shiftMatrixLeft(m);
+ m[5] = SkGetPackedA32(*row1++);
+ m[8] = SkGetPackedA32(*row2++);
+ surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale);
+ *dptr++ = lightingType.light(topNormal(m, surfaceScale), surfaceToLight, l->lightColor(surfaceToLight));
+ }
+ shiftMatrixLeft(m);
+ surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale);
+ *dptr++ = lightingType.light(topRightNormal(m, surfaceScale), surfaceToLight, l->lightColor(surfaceToLight));
+ }
+
+ for (++y; y < bottom - 1; ++y) {
+ int x = left;
+ const SkPMColor* row0 = src.getAddr32(x, y - 1);
+ const SkPMColor* row1 = src.getAddr32(x, y);
+ const SkPMColor* row2 = src.getAddr32(x, y + 1);
+ int m[9];
+ m[1] = SkGetPackedA32(*row0++);
+ m[2] = SkGetPackedA32(*row0++);
+ m[4] = SkGetPackedA32(*row1++);
+ m[5] = SkGetPackedA32(*row1++);
+ m[7] = SkGetPackedA32(*row2++);
+ m[8] = SkGetPackedA32(*row2++);
+ SkPoint3 surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale);
+ *dptr++ = lightingType.light(leftNormal(m, surfaceScale), surfaceToLight, l->lightColor(surfaceToLight));
+ for (++x; x < right - 1; ++x) {
+ shiftMatrixLeft(m);
+ m[2] = SkGetPackedA32(*row0++);
+ m[5] = SkGetPackedA32(*row1++);
+ m[8] = SkGetPackedA32(*row2++);
+ surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale);
+ *dptr++ = lightingType.light(interiorNormal(m, surfaceScale), surfaceToLight, l->lightColor(surfaceToLight));
+ }
+ shiftMatrixLeft(m);
+ surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale);
+ *dptr++ = lightingType.light(rightNormal(m, surfaceScale), surfaceToLight, l->lightColor(surfaceToLight));
+ }
+
+ {
+ int x = left;
+ const SkPMColor* row0 = src.getAddr32(x, bottom - 2);
+ const SkPMColor* row1 = src.getAddr32(x, bottom - 1);
+ int m[9];
+ m[1] = SkGetPackedA32(*row0++);
+ m[2] = SkGetPackedA32(*row0++);
+ m[4] = SkGetPackedA32(*row1++);
+ m[5] = SkGetPackedA32(*row1++);
+ SkPoint3 surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale);
+ *dptr++ = lightingType.light(bottomLeftNormal(m, surfaceScale), surfaceToLight, l->lightColor(surfaceToLight));
+ for (++x; x < right - 1; ++x)
+ {
+ shiftMatrixLeft(m);
+ m[2] = SkGetPackedA32(*row0++);
+ m[5] = SkGetPackedA32(*row1++);
+ surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale);
+ *dptr++ = lightingType.light(bottomNormal(m, surfaceScale), surfaceToLight, l->lightColor(surfaceToLight));
+ }
+ shiftMatrixLeft(m);
+ surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale);
+ *dptr++ = lightingType.light(bottomRightNormal(m, surfaceScale), surfaceToLight, l->lightColor(surfaceToLight));
+ }
+}
+
+SkPoint3 readPoint3(SkFlattenableReadBuffer& buffer) {
+ SkPoint3 point;
+ point.fX = buffer.readScalar();
+ point.fY = buffer.readScalar();
+ point.fZ = buffer.readScalar();
+ return point;
+};
+
+void writePoint3(const SkPoint3& point, SkFlattenableWriteBuffer& buffer) {
+ buffer.writeScalar(point.fX);
+ buffer.writeScalar(point.fY);
+ buffer.writeScalar(point.fZ);
+};
+
+class SkDiffuseLightingImageFilter : public SkLightingImageFilter {
+public:
+ SkDiffuseLightingImageFilter(SkLight* light, SkScalar surfaceScale,
+ SkScalar kd, SkImageFilter* input, const SkIRect* cropRect);
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkDiffuseLightingImageFilter)
+
+#if SK_SUPPORT_GPU
+ virtual bool asNewEffect(GrEffectRef** effect, GrTexture*, const SkIPoint& offset) const SK_OVERRIDE;
+#endif
+ SkScalar kd() const { return fKD; }
+
+protected:
+ explicit SkDiffuseLightingImageFilter(SkFlattenableReadBuffer& buffer);
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE;
+ virtual bool onFilterImage(Proxy*, const SkBitmap& src, const SkMatrix&,
+ SkBitmap* result, SkIPoint* offset) SK_OVERRIDE;
+
+
+private:
+ typedef SkLightingImageFilter INHERITED;
+ SkScalar fKD;
+};
+
+class SkSpecularLightingImageFilter : public SkLightingImageFilter {
+public:
+ SkSpecularLightingImageFilter(SkLight* light, SkScalar surfaceScale, SkScalar ks, SkScalar shininess, SkImageFilter* input, const SkIRect* cropRect);
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkSpecularLightingImageFilter)
+
+#if SK_SUPPORT_GPU
+ virtual bool asNewEffect(GrEffectRef** effect, GrTexture*, const SkIPoint& offset) const SK_OVERRIDE;
+#endif
+
+ SkScalar ks() const { return fKS; }
+ SkScalar shininess() const { return fShininess; }
+
+protected:
+ explicit SkSpecularLightingImageFilter(SkFlattenableReadBuffer& buffer);
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE;
+ virtual bool onFilterImage(Proxy*, const SkBitmap& src, const SkMatrix&,
+ SkBitmap* result, SkIPoint* offset) SK_OVERRIDE;
+
+private:
+ typedef SkLightingImageFilter INHERITED;
+ SkScalar fKS;
+ SkScalar fShininess;
+};
+
+#if SK_SUPPORT_GPU
+
+class GrLightingEffect : public GrSingleTextureEffect {
+public:
+ GrLightingEffect(GrTexture* texture, const SkLight* light, SkScalar surfaceScale, const SkIPoint& offset);
+ virtual ~GrLightingEffect();
+
+ const SkLight* light() const { return fLight; }
+ SkScalar surfaceScale() const { return fSurfaceScale; }
+ const SkIPoint& offset() const { return fOffset; }
+
+ virtual void getConstantColorComponents(GrColor* color,
+ uint32_t* validFlags) const SK_OVERRIDE {
+ // lighting shaders are complicated. We just throw up our hands.
+ *validFlags = 0;
+ }
+
+protected:
+ virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE;
+
+private:
+ typedef GrSingleTextureEffect INHERITED;
+ const SkLight* fLight;
+ SkScalar fSurfaceScale;
+ SkIPoint fOffset;
+};
+
+class GrDiffuseLightingEffect : public GrLightingEffect {
+public:
+ static GrEffectRef* Create(GrTexture* texture,
+ const SkLight* light,
+ SkScalar surfaceScale,
+ const SkIPoint& offset,
+ SkScalar kd) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrDiffuseLightingEffect, (texture,
+ light,
+ surfaceScale,
+ offset,
+ kd)));
+ return CreateEffectRef(effect);
+ }
+
+ static const char* Name() { return "DiffuseLighting"; }
+
+ typedef GrGLDiffuseLightingEffect GLEffect;
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+ SkScalar kd() const { return fKD; }
+
+private:
+ virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE;
+
+ GrDiffuseLightingEffect(GrTexture* texture,
+ const SkLight* light,
+ SkScalar surfaceScale,
+ const SkIPoint& offset,
+ SkScalar kd);
+
+ GR_DECLARE_EFFECT_TEST;
+ typedef GrLightingEffect INHERITED;
+ SkScalar fKD;
+};
+
+class GrSpecularLightingEffect : public GrLightingEffect {
+public:
+ static GrEffectRef* Create(GrTexture* texture,
+ const SkLight* light,
+ SkScalar surfaceScale,
+ const SkIPoint& offset,
+ SkScalar ks,
+ SkScalar shininess) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrSpecularLightingEffect, (texture,
+ light,
+ surfaceScale,
+ offset,
+ ks,
+ shininess)));
+ return CreateEffectRef(effect);
+ }
+ static const char* Name() { return "SpecularLighting"; }
+
+ typedef GrGLSpecularLightingEffect GLEffect;
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+ SkScalar ks() const { return fKS; }
+ SkScalar shininess() const { return fShininess; }
+
+private:
+ virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE;
+
+ GrSpecularLightingEffect(GrTexture* texture,
+ const SkLight* light,
+ SkScalar surfaceScale,
+ const SkIPoint& offset,
+ SkScalar ks,
+ SkScalar shininess);
+
+ GR_DECLARE_EFFECT_TEST;
+ typedef GrLightingEffect INHERITED;
+ SkScalar fKS;
+ SkScalar fShininess;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class GrGLLight {
+public:
+ virtual ~GrGLLight() {}
+
+ /**
+ * This is called by GrGLLightingEffect::emitCode() before either of the two virtual functions
+ * below. It adds a vec3f uniform visible in the FS that represents the constant light color.
+ */
+ void emitLightColorUniform(GrGLShaderBuilder*);
+
+ /**
+ * These two functions are called from GrGLLightingEffect's emitCode() function.
+ * emitSurfaceToLight places an expression in param out that is the vector from the surface to
+ * the light. The expression will be used in the FS. emitLightColor writes an expression into
+ * the FS that is the color of the light. Either function may add functions and/or uniforms to
+ * the FS. The default of emitLightColor appends the name of the constant light color uniform
+ * and so this function only needs to be overridden if the light color varies spatially.
+ */
+ virtual void emitSurfaceToLight(GrGLShaderBuilder*, const char* z) = 0;
+ virtual void emitLightColor(GrGLShaderBuilder*, const char *surfaceToLight);
+
+ // This is called from GrGLLightingEffect's setData(). Subclasses of GrGLLight must call
+ // INHERITED::setData().
+ virtual void setData(const GrGLUniformManager&,
+ const SkLight* light,
+ const SkIPoint& offset) const;
+
+protected:
+ /**
+ * Gets the constant light color uniform. Subclasses can use this in their emitLightColor
+ * function.
+ */
+ UniformHandle lightColorUni() const { return fColorUni; }
+
+private:
+ UniformHandle fColorUni;
+
+ typedef SkRefCnt INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class GrGLDistantLight : public GrGLLight {
+public:
+ virtual ~GrGLDistantLight() {}
+ virtual void setData(const GrGLUniformManager&,
+ const SkLight* light,
+ const SkIPoint& offset) const SK_OVERRIDE;
+ virtual void emitSurfaceToLight(GrGLShaderBuilder*, const char* z) SK_OVERRIDE;
+
+private:
+ typedef GrGLLight INHERITED;
+ UniformHandle fDirectionUni;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class GrGLPointLight : public GrGLLight {
+public:
+ virtual ~GrGLPointLight() {}
+ virtual void setData(const GrGLUniformManager&,
+ const SkLight* light,
+ const SkIPoint& offset) const SK_OVERRIDE;
+ virtual void emitSurfaceToLight(GrGLShaderBuilder*, const char* z) SK_OVERRIDE;
+
+private:
+ typedef GrGLLight INHERITED;
+ UniformHandle fLocationUni;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class GrGLSpotLight : public GrGLLight {
+public:
+ virtual ~GrGLSpotLight() {}
+ virtual void setData(const GrGLUniformManager&,
+ const SkLight* light,
+ const SkIPoint& offset) const SK_OVERRIDE;
+ virtual void emitSurfaceToLight(GrGLShaderBuilder*, const char* z) SK_OVERRIDE;
+ virtual void emitLightColor(GrGLShaderBuilder*, const char *surfaceToLight) SK_OVERRIDE;
+
+private:
+ typedef GrGLLight INHERITED;
+
+ SkString fLightColorFunc;
+ UniformHandle fLocationUni;
+ UniformHandle fExponentUni;
+ UniformHandle fCosOuterConeAngleUni;
+ UniformHandle fCosInnerConeAngleUni;
+ UniformHandle fConeScaleUni;
+ UniformHandle fSUni;
+};
+#else
+
+class GrGLLight;
+
+#endif
+
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkLight : public SkFlattenable {
+public:
+ SK_DECLARE_INST_COUNT(SkLight)
+
+ enum LightType {
+ kDistant_LightType,
+ kPoint_LightType,
+ kSpot_LightType,
+ };
+ virtual LightType type() const = 0;
+ const SkPoint3& color() const { return fColor; }
+ virtual GrGLLight* createGLLight() const = 0;
+ virtual bool isEqual(const SkLight& other) const {
+ return fColor == other.fColor;
+ }
+ // Called to know whether the generated GrGLLight will require access to the fragment position.
+ virtual bool requiresFragmentPosition() const = 0;
+
+protected:
+ SkLight(SkColor color)
+ : fColor(SkIntToScalar(SkColorGetR(color)),
+ SkIntToScalar(SkColorGetG(color)),
+ SkIntToScalar(SkColorGetB(color))) {}
+ SkLight(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {
+ fColor = readPoint3(buffer);
+ }
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
+ INHERITED::flatten(buffer);
+ writePoint3(fColor, buffer);
+ }
+
+private:
+ typedef SkFlattenable INHERITED;
+ SkPoint3 fColor;
+};
+
+SK_DEFINE_INST_COUNT(SkLight)
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkDistantLight : public SkLight {
+public:
+ SkDistantLight(const SkPoint3& direction, SkColor color)
+ : INHERITED(color), fDirection(direction) {
+ }
+
+ SkPoint3 surfaceToLight(int x, int y, int z, SkScalar surfaceScale) const {
+ return fDirection;
+ };
+ SkPoint3 lightColor(const SkPoint3&) const { return color(); }
+ virtual LightType type() const { return kDistant_LightType; }
+ const SkPoint3& direction() const { return fDirection; }
+ virtual GrGLLight* createGLLight() const SK_OVERRIDE {
+#if SK_SUPPORT_GPU
+ return SkNEW(GrGLDistantLight);
+#else
+ SkDEBUGFAIL("Should not call in GPU-less build");
+ return NULL;
+#endif
+ }
+ virtual bool requiresFragmentPosition() const SK_OVERRIDE { return false; }
+
+ virtual bool isEqual(const SkLight& other) const SK_OVERRIDE {
+ if (other.type() != kDistant_LightType) {
+ return false;
+ }
+
+ const SkDistantLight& o = static_cast<const SkDistantLight&>(other);
+ return INHERITED::isEqual(other) &&
+ fDirection == o.fDirection;
+ }
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkDistantLight)
+
+protected:
+ SkDistantLight(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ fDirection = readPoint3(buffer);
+ }
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const {
+ INHERITED::flatten(buffer);
+ writePoint3(fDirection, buffer);
+ }
+
+private:
+ typedef SkLight INHERITED;
+ SkPoint3 fDirection;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkPointLight : public SkLight {
+public:
+ SkPointLight(const SkPoint3& location, SkColor color)
+ : INHERITED(color), fLocation(location) {}
+
+ SkPoint3 surfaceToLight(int x, int y, int z, SkScalar surfaceScale) const {
+ SkPoint3 direction(fLocation.fX - SkIntToScalar(x),
+ fLocation.fY - SkIntToScalar(y),
+ fLocation.fZ - SkScalarMul(SkIntToScalar(z), surfaceScale));
+ direction.normalize();
+ return direction;
+ };
+ SkPoint3 lightColor(const SkPoint3&) const { return color(); }
+ virtual LightType type() const { return kPoint_LightType; }
+ const SkPoint3& location() const { return fLocation; }
+ virtual GrGLLight* createGLLight() const SK_OVERRIDE {
+#if SK_SUPPORT_GPU
+ return SkNEW(GrGLPointLight);
+#else
+ SkDEBUGFAIL("Should not call in GPU-less build");
+ return NULL;
+#endif
+ }
+ virtual bool requiresFragmentPosition() const SK_OVERRIDE { return true; }
+ virtual bool isEqual(const SkLight& other) const SK_OVERRIDE {
+ if (other.type() != kPoint_LightType) {
+ return false;
+ }
+ const SkPointLight& o = static_cast<const SkPointLight&>(other);
+ return INHERITED::isEqual(other) &&
+ fLocation == o.fLocation;
+ }
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkPointLight)
+
+protected:
+ SkPointLight(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ fLocation = readPoint3(buffer);
+ }
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const {
+ INHERITED::flatten(buffer);
+ writePoint3(fLocation, buffer);
+ }
+
+private:
+ typedef SkLight INHERITED;
+ SkPoint3 fLocation;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkSpotLight : public SkLight {
+public:
+ SkSpotLight(const SkPoint3& location, const SkPoint3& target, SkScalar specularExponent, SkScalar cutoffAngle, SkColor color)
+ : INHERITED(color),
+ fLocation(location),
+ fTarget(target),
+ fSpecularExponent(specularExponent)
+ {
+ fS = target - location;
+ fS.normalize();
+ fCosOuterConeAngle = SkScalarCos(SkDegreesToRadians(cutoffAngle));
+ const SkScalar antiAliasThreshold = SkFloatToScalar(0.016f);
+ fCosInnerConeAngle = fCosOuterConeAngle + antiAliasThreshold;
+ fConeScale = SkScalarInvert(antiAliasThreshold);
+ }
+
+ SkPoint3 surfaceToLight(int x, int y, int z, SkScalar surfaceScale) const {
+ SkPoint3 direction(fLocation.fX - SkIntToScalar(x),
+ fLocation.fY - SkIntToScalar(y),
+ fLocation.fZ - SkScalarMul(SkIntToScalar(z), surfaceScale));
+ direction.normalize();
+ return direction;
+ };
+ SkPoint3 lightColor(const SkPoint3& surfaceToLight) const {
+ SkScalar cosAngle = -surfaceToLight.dot(fS);
+ if (cosAngle < fCosOuterConeAngle) {
+ return SkPoint3(0, 0, 0);
+ }
+ SkScalar scale = SkScalarPow(cosAngle, fSpecularExponent);
+ if (cosAngle < fCosInnerConeAngle) {
+ scale = SkScalarMul(scale, cosAngle - fCosOuterConeAngle);
+ return color() * SkScalarMul(scale, fConeScale);
+ }
+ return color() * scale;
+ }
+ virtual GrGLLight* createGLLight() const SK_OVERRIDE {
+#if SK_SUPPORT_GPU
+ return SkNEW(GrGLSpotLight);
+#else
+ SkDEBUGFAIL("Should not call in GPU-less build");
+ return NULL;
+#endif
+ }
+ virtual bool requiresFragmentPosition() const SK_OVERRIDE { return true; }
+ virtual LightType type() const { return kSpot_LightType; }
+ const SkPoint3& location() const { return fLocation; }
+ const SkPoint3& target() const { return fTarget; }
+ SkScalar specularExponent() const { return fSpecularExponent; }
+ SkScalar cosInnerConeAngle() const { return fCosInnerConeAngle; }
+ SkScalar cosOuterConeAngle() const { return fCosOuterConeAngle; }
+ SkScalar coneScale() const { return fConeScale; }
+ const SkPoint3& s() const { return fS; }
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkSpotLight)
+
+protected:
+ SkSpotLight(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ fLocation = readPoint3(buffer);
+ fTarget = readPoint3(buffer);
+ fSpecularExponent = buffer.readScalar();
+ fCosOuterConeAngle = buffer.readScalar();
+ fCosInnerConeAngle = buffer.readScalar();
+ fConeScale = buffer.readScalar();
+ fS = readPoint3(buffer);
+ }
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const {
+ INHERITED::flatten(buffer);
+ writePoint3(fLocation, buffer);
+ writePoint3(fTarget, buffer);
+ buffer.writeScalar(fSpecularExponent);
+ buffer.writeScalar(fCosOuterConeAngle);
+ buffer.writeScalar(fCosInnerConeAngle);
+ buffer.writeScalar(fConeScale);
+ writePoint3(fS, buffer);
+ }
+
+ virtual bool isEqual(const SkLight& other) const SK_OVERRIDE {
+ if (other.type() != kSpot_LightType) {
+ return false;
+ }
+
+ const SkSpotLight& o = static_cast<const SkSpotLight&>(other);
+ return INHERITED::isEqual(other) &&
+ fLocation == o.fLocation &&
+ fTarget == o.fTarget &&
+ fSpecularExponent == o.fSpecularExponent &&
+ fCosOuterConeAngle == o.fCosOuterConeAngle;
+ }
+
+private:
+ typedef SkLight INHERITED;
+ SkPoint3 fLocation;
+ SkPoint3 fTarget;
+ SkScalar fSpecularExponent;
+ SkScalar fCosOuterConeAngle;
+ SkScalar fCosInnerConeAngle;
+ SkScalar fConeScale;
+ SkPoint3 fS;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkLightingImageFilter::SkLightingImageFilter(SkLight* light, SkScalar surfaceScale, SkImageFilter* input, const SkIRect* cropRect)
+ : INHERITED(input, cropRect),
+ fLight(light),
+ fSurfaceScale(SkScalarDiv(surfaceScale, SkIntToScalar(255)))
+{
+ SkASSERT(fLight);
+ // our caller knows that we take ownership of the light, so we don't
+ // need to call ref() here.
+}
+
+SkImageFilter* SkLightingImageFilter::CreateDistantLitDiffuse(
+ const SkPoint3& direction, SkColor lightColor, SkScalar surfaceScale,
+ SkScalar kd, SkImageFilter* input, const SkIRect* cropRect) {
+ return SkNEW_ARGS(SkDiffuseLightingImageFilter,
+ (SkNEW_ARGS(SkDistantLight, (direction, lightColor)), surfaceScale, kd,
+ input, cropRect));
+}
+
+SkImageFilter* SkLightingImageFilter::CreatePointLitDiffuse(
+ const SkPoint3& location, SkColor lightColor, SkScalar surfaceScale,
+ SkScalar kd, SkImageFilter* input, const SkIRect* cropRect) {
+ return SkNEW_ARGS(SkDiffuseLightingImageFilter,
+ (SkNEW_ARGS(SkPointLight, (location, lightColor)), surfaceScale, kd,
+ input, cropRect));
+}
+
+SkImageFilter* SkLightingImageFilter::CreateSpotLitDiffuse(
+ const SkPoint3& location, const SkPoint3& target,
+ SkScalar specularExponent, SkScalar cutoffAngle,
+ SkColor lightColor, SkScalar surfaceScale, SkScalar kd,
+ SkImageFilter* input, const SkIRect* cropRect) {
+ return SkNEW_ARGS(SkDiffuseLightingImageFilter,
+ (SkNEW_ARGS(SkSpotLight, (location, target, specularExponent,
+ cutoffAngle, lightColor)),
+ surfaceScale, kd, input, cropRect));
+}
+
+SkImageFilter* SkLightingImageFilter::CreateDistantLitSpecular(
+ const SkPoint3& direction, SkColor lightColor, SkScalar surfaceScale,
+ SkScalar ks, SkScalar shininess, SkImageFilter* input, const SkIRect* cropRect) {
+ return SkNEW_ARGS(SkSpecularLightingImageFilter,
+ (SkNEW_ARGS(SkDistantLight, (direction, lightColor)),
+ surfaceScale, ks, shininess, input, cropRect));
+}
+
+SkImageFilter* SkLightingImageFilter::CreatePointLitSpecular(
+ const SkPoint3& location, SkColor lightColor, SkScalar surfaceScale,
+ SkScalar ks, SkScalar shininess, SkImageFilter* input, const SkIRect* cropRect) {
+ return SkNEW_ARGS(SkSpecularLightingImageFilter,
+ (SkNEW_ARGS(SkPointLight, (location, lightColor)),
+ surfaceScale, ks, shininess, input, cropRect));
+}
+
+SkImageFilter* SkLightingImageFilter::CreateSpotLitSpecular(
+ const SkPoint3& location, const SkPoint3& target,
+ SkScalar specularExponent, SkScalar cutoffAngle,
+ SkColor lightColor, SkScalar surfaceScale,
+ SkScalar ks, SkScalar shininess, SkImageFilter* input, const SkIRect* cropRect) {
+ return SkNEW_ARGS(SkSpecularLightingImageFilter,
+ (SkNEW_ARGS(SkSpotLight, (location, target, specularExponent, cutoffAngle, lightColor)),
+ surfaceScale, ks, shininess, input, cropRect));
+}
+
+SkLightingImageFilter::~SkLightingImageFilter() {
+ fLight->unref();
+}
+
+SkLightingImageFilter::SkLightingImageFilter(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer)
+{
+ fLight = buffer.readFlattenableT<SkLight>();
+ fSurfaceScale = buffer.readScalar();
+}
+
+void SkLightingImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeFlattenable(fLight);
+ buffer.writeScalar(fSurfaceScale);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkDiffuseLightingImageFilter::SkDiffuseLightingImageFilter(SkLight* light, SkScalar surfaceScale, SkScalar kd, SkImageFilter* input, const SkIRect* cropRect = NULL)
+ : SkLightingImageFilter(light, surfaceScale, input, cropRect),
+ fKD(kd)
+{
+}
+
+SkDiffuseLightingImageFilter::SkDiffuseLightingImageFilter(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer)
+{
+ fKD = buffer.readScalar();
+}
+
+void SkDiffuseLightingImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeScalar(fKD);
+}
+
+bool SkDiffuseLightingImageFilter::onFilterImage(Proxy*,
+ const SkBitmap& src,
+ const SkMatrix&,
+ SkBitmap* dst,
+ SkIPoint* offset) {
+ if (src.config() != SkBitmap::kARGB_8888_Config) {
+ return false;
+ }
+ SkAutoLockPixels alp(src);
+ if (!src.getPixels()) {
+ return false;
+ }
+
+ SkIRect bounds;
+ src.getBounds(&bounds);
+ if (!this->applyCropRect(&bounds)) {
+ return false;
+ }
+
+ if (bounds.width() < 2 || bounds.height() < 2) {
+ return false;
+ }
+
+ dst->setConfig(src.config(), bounds.width(), bounds.height());
+ dst->allocPixels();
+
+ DiffuseLightingType lightingType(fKD);
+ switch (light()->type()) {
+ case SkLight::kDistant_LightType:
+ lightBitmap<DiffuseLightingType, SkDistantLight>(lightingType, light(), src, dst, surfaceScale(), bounds);
+ break;
+ case SkLight::kPoint_LightType:
+ lightBitmap<DiffuseLightingType, SkPointLight>(lightingType, light(), src, dst, surfaceScale(), bounds);
+ break;
+ case SkLight::kSpot_LightType:
+ lightBitmap<DiffuseLightingType, SkSpotLight>(lightingType, light(), src, dst, surfaceScale(), bounds);
+ break;
+ }
+
+ offset->fX += bounds.left();
+ offset->fY += bounds.top();
+ return true;
+}
+
+#if SK_SUPPORT_GPU
+bool SkDiffuseLightingImageFilter::asNewEffect(GrEffectRef** effect, GrTexture* texture, const SkIPoint& offset) const {
+ if (effect) {
+ SkScalar scale = SkScalarMul(surfaceScale(), SkIntToScalar(255));
+ *effect = GrDiffuseLightingEffect::Create(texture, light(), scale, offset, kd());
+ }
+ return true;
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkSpecularLightingImageFilter::SkSpecularLightingImageFilter(SkLight* light, SkScalar surfaceScale, SkScalar ks, SkScalar shininess, SkImageFilter* input, const SkIRect* cropRect)
+ : SkLightingImageFilter(light, surfaceScale, input, cropRect),
+ fKS(ks),
+ fShininess(shininess)
+{
+}
+
+SkSpecularLightingImageFilter::SkSpecularLightingImageFilter(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer)
+{
+ fKS = buffer.readScalar();
+ fShininess = buffer.readScalar();
+}
+
+void SkSpecularLightingImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeScalar(fKS);
+ buffer.writeScalar(fShininess);
+}
+
+bool SkSpecularLightingImageFilter::onFilterImage(Proxy*,
+ const SkBitmap& src,
+ const SkMatrix&,
+ SkBitmap* dst,
+ SkIPoint* offset) {
+ if (src.config() != SkBitmap::kARGB_8888_Config) {
+ return false;
+ }
+ SkAutoLockPixels alp(src);
+ if (!src.getPixels()) {
+ return false;
+ }
+
+ SkIRect bounds;
+ src.getBounds(&bounds);
+ if (!this->applyCropRect(&bounds)) {
+ return false;
+ }
+
+ if (bounds.width() < 2 || bounds.height() < 2) {
+ return false;
+ }
+
+ dst->setConfig(src.config(), bounds.width(), bounds.height());
+ dst->allocPixels();
+
+ SpecularLightingType lightingType(fKS, fShininess);
+ switch (light()->type()) {
+ case SkLight::kDistant_LightType:
+ lightBitmap<SpecularLightingType, SkDistantLight>(lightingType, light(), src, dst, surfaceScale(), bounds);
+ break;
+ case SkLight::kPoint_LightType:
+ lightBitmap<SpecularLightingType, SkPointLight>(lightingType, light(), src, dst, surfaceScale(), bounds);
+ break;
+ case SkLight::kSpot_LightType:
+ lightBitmap<SpecularLightingType, SkSpotLight>(lightingType, light(), src, dst, surfaceScale(), bounds);
+ break;
+ }
+ offset->fX += bounds.left();
+ offset->fY += bounds.top();
+ return true;
+}
+
+#if SK_SUPPORT_GPU
+bool SkSpecularLightingImageFilter::asNewEffect(GrEffectRef** effect, GrTexture* texture, const SkIPoint& offset) const {
+ if (effect) {
+ SkScalar scale = SkScalarMul(surfaceScale(), SkIntToScalar(255));
+ *effect = GrSpecularLightingEffect::Create(texture, light(), scale, offset, ks(), shininess());
+ }
+ return true;
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+
+namespace {
+SkPoint3 random_point3(SkMWCRandom* random) {
+ return SkPoint3(SkScalarToFloat(random->nextSScalar1()),
+ SkScalarToFloat(random->nextSScalar1()),
+ SkScalarToFloat(random->nextSScalar1()));
+}
+
+SkLight* create_random_light(SkMWCRandom* random) {
+ int type = random->nextULessThan(3);
+ switch (type) {
+ case 0: {
+ return SkNEW_ARGS(SkDistantLight, (random_point3(random), random->nextU()));
+ }
+ case 1: {
+ return SkNEW_ARGS(SkPointLight, (random_point3(random), random->nextU()));
+ }
+ case 2: {
+ return SkNEW_ARGS(SkSpotLight, (random_point3(random),
+ random_point3(random),
+ random->nextUScalar1(),
+ random->nextUScalar1(),
+ random->nextU()));
+ }
+ default:
+ GrCrash();
+ return NULL;
+ }
+}
+
+}
+
+class GrGLLightingEffect : public GrGLEffect {
+public:
+ GrGLLightingEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& effect);
+ virtual ~GrGLLightingEffect();
+
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ static inline EffectKey GenKey(const GrDrawEffect&, const GrGLCaps&);
+
+ /**
+ * Subclasses of GrGLLightingEffect must call INHERITED::setData();
+ */
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+protected:
+ virtual void emitLightFunc(GrGLShaderBuilder*, SkString* funcName) = 0;
+
+private:
+ typedef GrGLEffect INHERITED;
+
+ UniformHandle fImageIncrementUni;
+ UniformHandle fSurfaceScaleUni;
+ GrGLLight* fLight;
+ GrGLEffectMatrix fEffectMatrix;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class GrGLDiffuseLightingEffect : public GrGLLightingEffect {
+public:
+ GrGLDiffuseLightingEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect);
+ virtual void emitLightFunc(GrGLShaderBuilder*, SkString* funcName) SK_OVERRIDE;
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+private:
+ typedef GrGLLightingEffect INHERITED;
+
+ UniformHandle fKDUni;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class GrGLSpecularLightingEffect : public GrGLLightingEffect {
+public:
+ GrGLSpecularLightingEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& effect);
+ virtual void emitLightFunc(GrGLShaderBuilder*, SkString* funcName) SK_OVERRIDE;
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+private:
+ typedef GrGLLightingEffect INHERITED;
+
+ UniformHandle fKSUni;
+ UniformHandle fShininessUni;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrLightingEffect::GrLightingEffect(GrTexture* texture,
+ const SkLight* light,
+ SkScalar surfaceScale,
+ const SkIPoint& offset)
+ : INHERITED(texture, MakeDivByTextureWHMatrix(texture))
+ , fLight(light)
+ , fSurfaceScale(surfaceScale)
+ , fOffset(offset) {
+ fLight->ref();
+ if (light->requiresFragmentPosition()) {
+ this->setWillReadFragmentPosition();
+ }
+}
+
+GrLightingEffect::~GrLightingEffect() {
+ fLight->unref();
+}
+
+bool GrLightingEffect::onIsEqual(const GrEffect& sBase) const {
+ const GrLightingEffect& s = CastEffect<GrLightingEffect>(sBase);
+ return this->texture(0) == s.texture(0) &&
+ fLight->isEqual(*s.fLight) &&
+ fSurfaceScale == s.fSurfaceScale;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrDiffuseLightingEffect::GrDiffuseLightingEffect(GrTexture* texture,
+ const SkLight* light,
+ SkScalar surfaceScale,
+ const SkIPoint& offset,
+ SkScalar kd)
+ : INHERITED(texture, light, surfaceScale, offset), fKD(kd) {
+}
+
+const GrBackendEffectFactory& GrDiffuseLightingEffect::getFactory() const {
+ return GrTBackendEffectFactory<GrDiffuseLightingEffect>::getInstance();
+}
+
+bool GrDiffuseLightingEffect::onIsEqual(const GrEffect& sBase) const {
+ const GrDiffuseLightingEffect& s = CastEffect<GrDiffuseLightingEffect>(sBase);
+ return INHERITED::onIsEqual(sBase) &&
+ this->kd() == s.kd();
+}
+
+GR_DEFINE_EFFECT_TEST(GrDiffuseLightingEffect);
+
+GrEffectRef* GrDiffuseLightingEffect::TestCreate(SkMWCRandom* random,
+ GrContext* context,
+ const GrDrawTargetCaps&,
+ GrTexture* textures[]) {
+ SkScalar surfaceScale = random->nextSScalar1();
+ SkScalar kd = random->nextUScalar1();
+ SkAutoTUnref<SkLight> light(create_random_light(random));
+ SkIPoint offset = SkIPoint::Make(random->nextS(), random->nextS());
+ return GrDiffuseLightingEffect::Create(textures[GrEffectUnitTest::kAlphaTextureIdx],
+ light, surfaceScale, offset, kd);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrGLLightingEffect::GrGLLightingEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect)
+ : INHERITED(factory)
+ , fImageIncrementUni(kInvalidUniformHandle)
+ , fSurfaceScaleUni(kInvalidUniformHandle)
+ , fEffectMatrix(drawEffect.castEffect<GrLightingEffect>().coordsType()) {
+ const GrLightingEffect& m = drawEffect.castEffect<GrLightingEffect>();
+ fLight = m.light()->createGLLight();
+}
+
+GrGLLightingEffect::~GrGLLightingEffect() {
+ delete fLight;
+}
+
+void GrGLLightingEffect::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect&,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ const char* coords;
+ fEffectMatrix.emitCodeMakeFSCoords2D(builder, key, &coords);
+
+ fImageIncrementUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec2f_GrSLType,
+ "ImageIncrement");
+ fSurfaceScaleUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType,
+ "SurfaceScale");
+ fLight->emitLightColorUniform(builder);
+ SkString lightFunc;
+ this->emitLightFunc(builder, &lightFunc);
+ static const GrGLShaderVar gSobelArgs[] = {
+ GrGLShaderVar("a", kFloat_GrSLType),
+ GrGLShaderVar("b", kFloat_GrSLType),
+ GrGLShaderVar("c", kFloat_GrSLType),
+ GrGLShaderVar("d", kFloat_GrSLType),
+ GrGLShaderVar("e", kFloat_GrSLType),
+ GrGLShaderVar("f", kFloat_GrSLType),
+ GrGLShaderVar("scale", kFloat_GrSLType),
+ };
+ SkString sobelFuncName;
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType,
+ "sobel",
+ SK_ARRAY_COUNT(gSobelArgs),
+ gSobelArgs,
+ "\treturn (-a + b - 2.0 * c + 2.0 * d -e + f) * scale;\n",
+ &sobelFuncName);
+ static const GrGLShaderVar gPointToNormalArgs[] = {
+ GrGLShaderVar("x", kFloat_GrSLType),
+ GrGLShaderVar("y", kFloat_GrSLType),
+ GrGLShaderVar("scale", kFloat_GrSLType),
+ };
+ SkString pointToNormalName;
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec3f_GrSLType,
+ "pointToNormal",
+ SK_ARRAY_COUNT(gPointToNormalArgs),
+ gPointToNormalArgs,
+ "\treturn normalize(vec3(-x * scale, y * scale, 1));\n",
+ &pointToNormalName);
+
+ static const GrGLShaderVar gInteriorNormalArgs[] = {
+ GrGLShaderVar("m", kFloat_GrSLType, 9),
+ GrGLShaderVar("surfaceScale", kFloat_GrSLType),
+ };
+ SkString interiorNormalBody;
+ interiorNormalBody.appendf("\treturn %s(%s(m[0], m[2], m[3], m[5], m[6], m[8], 0.25),\n"
+ "\t %s(m[0], m[6], m[1], m[7], m[2], m[8], 0.25),\n"
+ "\t surfaceScale);\n",
+ pointToNormalName.c_str(),
+ sobelFuncName.c_str(),
+ sobelFuncName.c_str());
+ SkString interiorNormalName;
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec3f_GrSLType,
+ "interiorNormal",
+ SK_ARRAY_COUNT(gInteriorNormalArgs),
+ gInteriorNormalArgs,
+ interiorNormalBody.c_str(),
+ &interiorNormalName);
+
+ builder->fsCodeAppendf("\t\tvec2 coord = %s;\n", coords);
+ builder->fsCodeAppend("\t\tfloat m[9];\n");
+
+ const char* imgInc = builder->getUniformCStr(fImageIncrementUni);
+ const char* surfScale = builder->getUniformCStr(fSurfaceScaleUni);
+
+ int index = 0;
+ for (int dy = -1; dy <= 1; dy++) {
+ for (int dx = -1; dx <= 1; dx++) {
+ SkString texCoords;
+ texCoords.appendf("coord + vec2(%d, %d) * %s", dx, dy, imgInc);
+ builder->fsCodeAppendf("\t\tm[%d] = ", index++);
+ builder->appendTextureLookup(GrGLShaderBuilder::kFragment_ShaderType,
+ samplers[0],
+ texCoords.c_str());
+ builder->fsCodeAppend(".a;\n");
+ }
+ }
+ builder->fsCodeAppend("\t\tvec3 surfaceToLight = ");
+ SkString arg;
+ arg.appendf("%s * m[4]", surfScale);
+ fLight->emitSurfaceToLight(builder, arg.c_str());
+ builder->fsCodeAppend(";\n");
+ builder->fsCodeAppendf("\t\t%s = %s(%s(m, %s), surfaceToLight, ",
+ outputColor, lightFunc.c_str(), interiorNormalName.c_str(), surfScale);
+ fLight->emitLightColor(builder, "surfaceToLight");
+ builder->fsCodeAppend(");\n");
+ SkString modulate;
+ GrGLSLMulVarBy4f(&modulate, 2, outputColor, inputColor);
+ builder->fsCodeAppend(modulate.c_str());
+}
+
+GrGLEffect::EffectKey GrGLLightingEffect::GenKey(const GrDrawEffect& drawEffect,
+ const GrGLCaps& caps) {
+ const GrLightingEffect& lighting = drawEffect.castEffect<GrLightingEffect>();
+ EffectKey key = lighting.light()->type();
+ key <<= GrGLEffectMatrix::kKeyBits;
+ EffectKey matrixKey = GrGLEffectMatrix::GenKey(lighting.getMatrix(),
+ drawEffect,
+ lighting.coordsType(),
+ lighting.texture(0));
+ return key | matrixKey;
+}
+
+void GrGLLightingEffect::setData(const GrGLUniformManager& uman,
+ const GrDrawEffect& drawEffect) {
+ const GrLightingEffect& lighting = drawEffect.castEffect<GrLightingEffect>();
+ GrTexture* texture = lighting.texture(0);
+ float ySign = texture->origin() == kTopLeft_GrSurfaceOrigin ? -1.0f : 1.0f;
+ uman.set2f(fImageIncrementUni, 1.0f / texture->width(), ySign / texture->height());
+ uman.set1f(fSurfaceScaleUni, lighting.surfaceScale());
+ fLight->setData(uman, lighting.light(), lighting.offset());
+ fEffectMatrix.setData(uman,
+ lighting.getMatrix(),
+ drawEffect,
+ lighting.texture(0));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrGLDiffuseLightingEffect::GrGLDiffuseLightingEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect)
+ : INHERITED(factory, drawEffect)
+ , fKDUni(kInvalidUniformHandle) {
+}
+
+void GrGLDiffuseLightingEffect::emitLightFunc(GrGLShaderBuilder* builder, SkString* funcName) {
+ const char* kd;
+ fKDUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType,
+ "KD",
+ &kd);
+
+ static const GrGLShaderVar gLightArgs[] = {
+ GrGLShaderVar("normal", kVec3f_GrSLType),
+ GrGLShaderVar("surfaceToLight", kVec3f_GrSLType),
+ GrGLShaderVar("lightColor", kVec3f_GrSLType)
+ };
+ SkString lightBody;
+ lightBody.appendf("\tfloat colorScale = %s * dot(normal, surfaceToLight);\n", kd);
+ lightBody.appendf("\treturn vec4(lightColor * clamp(colorScale, 0.0, 1.0), 1.0);\n");
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec4f_GrSLType,
+ "light",
+ SK_ARRAY_COUNT(gLightArgs),
+ gLightArgs,
+ lightBody.c_str(),
+ funcName);
+}
+
+void GrGLDiffuseLightingEffect::setData(const GrGLUniformManager& uman,
+ const GrDrawEffect& drawEffect) {
+ INHERITED::setData(uman, drawEffect);
+ const GrDiffuseLightingEffect& diffuse = drawEffect.castEffect<GrDiffuseLightingEffect>();
+ uman.set1f(fKDUni, diffuse.kd());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrSpecularLightingEffect::GrSpecularLightingEffect(GrTexture* texture,
+ const SkLight* light,
+ SkScalar surfaceScale,
+ const SkIPoint& offset,
+ SkScalar ks,
+ SkScalar shininess)
+ : INHERITED(texture, light, surfaceScale, offset),
+ fKS(ks),
+ fShininess(shininess) {
+}
+
+const GrBackendEffectFactory& GrSpecularLightingEffect::getFactory() const {
+ return GrTBackendEffectFactory<GrSpecularLightingEffect>::getInstance();
+}
+
+bool GrSpecularLightingEffect::onIsEqual(const GrEffect& sBase) const {
+ const GrSpecularLightingEffect& s = CastEffect<GrSpecularLightingEffect>(sBase);
+ return INHERITED::onIsEqual(sBase) &&
+ this->ks() == s.ks() &&
+ this->shininess() == s.shininess();
+}
+
+GR_DEFINE_EFFECT_TEST(GrSpecularLightingEffect);
+
+GrEffectRef* GrSpecularLightingEffect::TestCreate(SkMWCRandom* random,
+ GrContext* context,
+ const GrDrawTargetCaps&,
+ GrTexture* textures[]) {
+ SkScalar surfaceScale = random->nextSScalar1();
+ SkScalar ks = random->nextUScalar1();
+ SkScalar shininess = random->nextUScalar1();
+ SkAutoTUnref<SkLight> light(create_random_light(random));
+ SkIPoint offset = SkIPoint::Make(random->nextS(), random->nextS());
+ return GrSpecularLightingEffect::Create(textures[GrEffectUnitTest::kAlphaTextureIdx],
+ light, surfaceScale, offset, ks, shininess);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrGLSpecularLightingEffect::GrGLSpecularLightingEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect)
+ : GrGLLightingEffect(factory, drawEffect)
+ , fKSUni(kInvalidUniformHandle)
+ , fShininessUni(kInvalidUniformHandle) {
+}
+
+void GrGLSpecularLightingEffect::emitLightFunc(GrGLShaderBuilder* builder, SkString* funcName) {
+ const char* ks;
+ const char* shininess;
+
+ fKSUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "KS", &ks);
+ fShininessUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "Shininess", &shininess);
+
+ static const GrGLShaderVar gLightArgs[] = {
+ GrGLShaderVar("normal", kVec3f_GrSLType),
+ GrGLShaderVar("surfaceToLight", kVec3f_GrSLType),
+ GrGLShaderVar("lightColor", kVec3f_GrSLType)
+ };
+ SkString lightBody;
+ lightBody.appendf("\tvec3 halfDir = vec3(normalize(surfaceToLight + vec3(0, 0, 1)));\n");
+ lightBody.appendf("\tfloat colorScale = %s * pow(dot(normal, halfDir), %s);\n", ks, shininess);
+ lightBody.appendf("\tvec3 color = lightColor * clamp(colorScale, 0.0, 1.0);\n");
+ lightBody.appendf("\treturn vec4(color, max(max(color.r, color.g), color.b));\n");
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec4f_GrSLType,
+ "light",
+ SK_ARRAY_COUNT(gLightArgs),
+ gLightArgs,
+ lightBody.c_str(),
+ funcName);
+}
+
+void GrGLSpecularLightingEffect::setData(const GrGLUniformManager& uman,
+ const GrDrawEffect& drawEffect) {
+ INHERITED::setData(uman, drawEffect);
+ const GrSpecularLightingEffect& spec = drawEffect.castEffect<GrSpecularLightingEffect>();
+ uman.set1f(fKSUni, spec.ks());
+ uman.set1f(fShininessUni, spec.shininess());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+void GrGLLight::emitLightColorUniform(GrGLShaderBuilder* builder) {
+ fColorUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec3f_GrSLType, "LightColor");
+}
+
+void GrGLLight::emitLightColor(GrGLShaderBuilder* builder,
+ const char *surfaceToLight) {
+ builder->fsCodeAppend(builder->getUniformCStr(this->lightColorUni()));
+}
+
+void GrGLLight::setData(const GrGLUniformManager& uman,
+ const SkLight* light,
+ const SkIPoint&) const {
+ setUniformPoint3(uman, fColorUni, light->color() * SkScalarInvert(SkIntToScalar(255)));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void GrGLDistantLight::setData(const GrGLUniformManager& uman,
+ const SkLight* light,
+ const SkIPoint& offset) const {
+ INHERITED::setData(uman, light, offset);
+ SkASSERT(light->type() == SkLight::kDistant_LightType);
+ const SkDistantLight* distantLight = static_cast<const SkDistantLight*>(light);
+ setUniformNormal3(uman, fDirectionUni, distantLight->direction());
+}
+
+void GrGLDistantLight::emitSurfaceToLight(GrGLShaderBuilder* builder, const char* z) {
+ const char* dir;
+ fDirectionUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType, kVec3f_GrSLType,
+ "LightDirection", &dir);
+ builder->fsCodeAppend(dir);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void GrGLPointLight::setData(const GrGLUniformManager& uman,
+ const SkLight* light,
+ const SkIPoint& offset) const {
+ INHERITED::setData(uman, light, offset);
+ SkASSERT(light->type() == SkLight::kPoint_LightType);
+ const SkPointLight* pointLight = static_cast<const SkPointLight*>(light);
+ SkPoint3 location = pointLight->location();
+ location.fX -= offset.fX;
+ location.fY -= offset.fY;
+ setUniformPoint3(uman, fLocationUni, location);
+}
+
+void GrGLPointLight::emitSurfaceToLight(GrGLShaderBuilder* builder, const char* z) {
+ const char* loc;
+ fLocationUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType, kVec3f_GrSLType,
+ "LightLocation", &loc);
+ builder->fsCodeAppendf("normalize(%s - vec3(%s.xy, %s))", loc, builder->fragmentPosition(), z);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void GrGLSpotLight::setData(const GrGLUniformManager& uman,
+ const SkLight* light,
+ const SkIPoint& offset) const {
+ INHERITED::setData(uman, light, offset);
+ SkASSERT(light->type() == SkLight::kSpot_LightType);
+ const SkSpotLight* spotLight = static_cast<const SkSpotLight *>(light);
+ SkPoint3 location = spotLight->location();
+ location.fX -= offset.fX;
+ location.fY -= offset.fY;
+ setUniformPoint3(uman, fLocationUni, location);
+ uman.set1f(fExponentUni, spotLight->specularExponent());
+ uman.set1f(fCosInnerConeAngleUni, spotLight->cosInnerConeAngle());
+ uman.set1f(fCosOuterConeAngleUni, spotLight->cosOuterConeAngle());
+ uman.set1f(fConeScaleUni, spotLight->coneScale());
+ setUniformNormal3(uman, fSUni, spotLight->s());
+}
+
+void GrGLSpotLight::emitSurfaceToLight(GrGLShaderBuilder* builder, const char* z) {
+ const char* location;
+ fLocationUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec3f_GrSLType, "LightLocation", &location);
+ builder->fsCodeAppendf("normalize(%s - vec3(%s.xy, %s))",
+ location, builder->fragmentPosition(), z);
+}
+
+void GrGLSpotLight::emitLightColor(GrGLShaderBuilder* builder,
+ const char *surfaceToLight) {
+
+ const char* color = builder->getUniformCStr(this->lightColorUni()); // created by parent class.
+
+ const char* exponent;
+ const char* cosInner;
+ const char* cosOuter;
+ const char* coneScale;
+ const char* s;
+ fExponentUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "Exponent", &exponent);
+ fCosInnerConeAngleUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "CosInnerConeAngle", &cosInner);
+ fCosOuterConeAngleUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "CosOuterConeAngle", &cosOuter);
+ fConeScaleUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "ConeScale", &coneScale);
+ fSUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec3f_GrSLType, "S", &s);
+
+ static const GrGLShaderVar gLightColorArgs[] = {
+ GrGLShaderVar("surfaceToLight", kVec3f_GrSLType)
+ };
+ SkString lightColorBody;
+ lightColorBody.appendf("\tfloat cosAngle = -dot(surfaceToLight, %s);\n", s);
+ lightColorBody.appendf("\tif (cosAngle < %s) {\n", cosOuter);
+ lightColorBody.appendf("\t\treturn vec3(0);\n");
+ lightColorBody.appendf("\t}\n");
+ lightColorBody.appendf("\tfloat scale = pow(cosAngle, %s);\n", exponent);
+ lightColorBody.appendf("\tif (cosAngle < %s) {\n", cosInner);
+ lightColorBody.appendf("\t\treturn %s * scale * (cosAngle - %s) * %s;\n",
+ color, cosOuter, coneScale);
+ lightColorBody.appendf("\t}\n");
+ lightColorBody.appendf("\treturn %s;\n", color);
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec3f_GrSLType,
+ "lightColor",
+ SK_ARRAY_COUNT(gLightColorArgs),
+ gLightColorArgs,
+ lightColorBody.c_str(),
+ &fLightColorFunc);
+
+ builder->fsCodeAppendf("%s(%s)", fLightColorFunc.c_str(), surfaceToLight);
+}
+
+#endif
+
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkLightingImageFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkDiffuseLightingImageFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkSpecularLightingImageFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkDistantLight)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkPointLight)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkSpotLight)
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
diff --git a/effects/SkMagnifierImageFilter.cpp b/effects/SkMagnifierImageFilter.cpp
new file mode 100644
index 00000000..f4a72b8d
--- /dev/null
+++ b/effects/SkMagnifierImageFilter.cpp
@@ -0,0 +1,354 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBitmap.h"
+#include "SkMagnifierImageFilter.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+
+////////////////////////////////////////////////////////////////////////////////
+#if SK_SUPPORT_GPU
+#include "effects/GrSingleTextureEffect.h"
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
+#include "gl/GrGLSL.h"
+#include "gl/GrGLTexture.h"
+#include "GrTBackendEffectFactory.h"
+
+class GrGLMagnifierEffect;
+
+class GrMagnifierEffect : public GrSingleTextureEffect {
+
+public:
+ static GrEffectRef* Create(GrTexture* texture,
+ float xOffset,
+ float yOffset,
+ float xZoom,
+ float yZoom,
+ float xInset,
+ float yInset) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrMagnifierEffect, (texture,
+ xOffset,
+ yOffset,
+ xZoom,
+ yZoom,
+ xInset,
+ yInset)));
+ return CreateEffectRef(effect);
+ }
+
+ virtual ~GrMagnifierEffect() {};
+
+ static const char* Name() { return "Magnifier"; }
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+ virtual void getConstantColorComponents(GrColor* color, uint32_t* validFlags) const SK_OVERRIDE;
+
+ float x_offset() const { return fXOffset; }
+ float y_offset() const { return fYOffset; }
+ float x_zoom() const { return fXZoom; }
+ float y_zoom() const { return fYZoom; }
+ float x_inset() const { return fXInset; }
+ float y_inset() const { return fYInset; }
+
+ typedef GrGLMagnifierEffect GLEffect;
+
+private:
+ GrMagnifierEffect(GrTexture* texture,
+ float xOffset,
+ float yOffset,
+ float xZoom,
+ float yZoom,
+ float xInset,
+ float yInset)
+ : GrSingleTextureEffect(texture, MakeDivByTextureWHMatrix(texture))
+ , fXOffset(xOffset)
+ , fYOffset(yOffset)
+ , fXZoom(xZoom)
+ , fYZoom(yZoom)
+ , fXInset(xInset)
+ , fYInset(yInset) {}
+
+ virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE;
+
+ GR_DECLARE_EFFECT_TEST;
+
+ float fXOffset;
+ float fYOffset;
+ float fXZoom;
+ float fYZoom;
+ float fXInset;
+ float fYInset;
+
+ typedef GrSingleTextureEffect INHERITED;
+};
+
+// For brevity
+typedef GrGLUniformManager::UniformHandle UniformHandle;
+
+class GrGLMagnifierEffect : public GrGLEffect {
+public:
+ GrGLMagnifierEffect(const GrBackendEffectFactory&, const GrDrawEffect&);
+
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+ static inline EffectKey GenKey(const GrDrawEffect&, const GrGLCaps&);
+
+private:
+ UniformHandle fOffsetVar;
+ UniformHandle fZoomVar;
+ UniformHandle fInsetVar;
+
+ GrGLEffectMatrix fEffectMatrix;
+
+ typedef GrGLEffect INHERITED;
+};
+
+GrGLMagnifierEffect::GrGLMagnifierEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect)
+ : INHERITED(factory)
+ , fOffsetVar(GrGLUniformManager::kInvalidUniformHandle)
+ , fZoomVar(GrGLUniformManager::kInvalidUniformHandle)
+ , fInsetVar(GrGLUniformManager::kInvalidUniformHandle)
+ , fEffectMatrix(drawEffect.castEffect<GrMagnifierEffect>().coordsType()) {
+}
+
+void GrGLMagnifierEffect::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect&,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ const char* coords;
+ fEffectMatrix.emitCodeMakeFSCoords2D(builder, key, &coords);
+ fOffsetVar = builder->addUniform(
+ GrGLShaderBuilder::kFragment_ShaderType |
+ GrGLShaderBuilder::kVertex_ShaderType,
+ kVec2f_GrSLType, "uOffset");
+ fZoomVar = builder->addUniform(
+ GrGLShaderBuilder::kFragment_ShaderType |
+ GrGLShaderBuilder::kVertex_ShaderType,
+ kVec2f_GrSLType, "uZoom");
+ fInsetVar = builder->addUniform(
+ GrGLShaderBuilder::kFragment_ShaderType |
+ GrGLShaderBuilder::kVertex_ShaderType,
+ kVec2f_GrSLType, "uInset");
+
+ builder->fsCodeAppendf("\t\tvec2 coord = %s;\n", coords);
+ builder->fsCodeAppendf("\t\tvec2 zoom_coord = %s + %s / %s;\n",
+ builder->getUniformCStr(fOffsetVar),
+ coords,
+ builder->getUniformCStr(fZoomVar));
+
+ builder->fsCodeAppend("\t\tvec2 delta = min(coord, vec2(1.0, 1.0) - coord);\n");
+
+ builder->fsCodeAppendf("\t\tdelta = delta / %s;\n", builder->getUniformCStr(fInsetVar));
+
+ builder->fsCodeAppend("\t\tfloat weight = 0.0;\n");
+ builder->fsCodeAppend("\t\tif (delta.s < 2.0 && delta.t < 2.0) {\n");
+ builder->fsCodeAppend("\t\t\tdelta = vec2(2.0, 2.0) - delta;\n");
+ builder->fsCodeAppend("\t\t\tfloat dist = length(delta);\n");
+ builder->fsCodeAppend("\t\t\tdist = max(2.0 - dist, 0.0);\n");
+ builder->fsCodeAppend("\t\t\tweight = min(dist * dist, 1.0);\n");
+ builder->fsCodeAppend("\t\t} else {\n");
+ builder->fsCodeAppend("\t\t\tvec2 delta_squared = delta * delta;\n");
+ builder->fsCodeAppend("\t\t\tweight = min(min(delta_squared.s, delta_squared.y), 1.0);\n");
+ builder->fsCodeAppend("\t\t}\n");
+
+ builder->fsCodeAppend("\t\tvec2 mix_coord = mix(coord, zoom_coord, weight);\n");
+ builder->fsCodeAppend("\t\tvec4 output_color = ");
+ builder->appendTextureLookup(GrGLShaderBuilder::kFragment_ShaderType, samplers[0], "mix_coord");
+ builder->fsCodeAppend(";\n");
+
+ builder->fsCodeAppendf("\t\t%s = output_color;", outputColor);
+ SkString modulate;
+ GrGLSLMulVarBy4f(&modulate, 2, outputColor, inputColor);
+ builder->fsCodeAppend(modulate.c_str());
+}
+
+void GrGLMagnifierEffect::setData(const GrGLUniformManager& uman,
+ const GrDrawEffect& drawEffect) {
+ const GrMagnifierEffect& zoom = drawEffect.castEffect<GrMagnifierEffect>();
+ uman.set2f(fOffsetVar, zoom.x_offset(), zoom.y_offset());
+ uman.set2f(fZoomVar, zoom.x_zoom(), zoom.y_zoom());
+ uman.set2f(fInsetVar, zoom.x_inset(), zoom.y_inset());
+ fEffectMatrix.setData(uman, zoom.getMatrix(), drawEffect, zoom.texture(0));
+}
+
+GrGLEffect::EffectKey GrGLMagnifierEffect::GenKey(const GrDrawEffect& drawEffect,
+ const GrGLCaps&) {
+ const GrMagnifierEffect& zoom = drawEffect.castEffect<GrMagnifierEffect>();
+ return GrGLEffectMatrix::GenKey(zoom.getMatrix(),
+ drawEffect,
+ zoom.coordsType(),
+ zoom.texture(0));
+}
+
+/////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_EFFECT_TEST(GrMagnifierEffect);
+
+GrEffectRef* GrMagnifierEffect::TestCreate(SkMWCRandom* random,
+ GrContext* context,
+ const GrDrawTargetCaps&,
+ GrTexture** textures) {
+ const int kMaxWidth = 200;
+ const int kMaxHeight = 200;
+ const int kMaxInset = 20;
+ uint32_t width = random->nextULessThan(kMaxWidth);
+ uint32_t height = random->nextULessThan(kMaxHeight);
+ uint32_t x = random->nextULessThan(kMaxWidth - width);
+ uint32_t y = random->nextULessThan(kMaxHeight - height);
+ SkScalar inset = SkIntToScalar(random->nextULessThan(kMaxInset));
+
+ SkAutoTUnref<SkMagnifierImageFilter> filter(
+ new SkMagnifierImageFilter(
+ SkRect::MakeXYWH(SkIntToScalar(x), SkIntToScalar(y),
+ SkIntToScalar(width), SkIntToScalar(height)),
+ inset));
+ GrEffectRef* effect;
+ filter->asNewEffect(&effect, textures[0], SkIPoint::Make(0, 0));
+ GrAssert(NULL != effect);
+ return effect;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+const GrBackendEffectFactory& GrMagnifierEffect::getFactory() const {
+ return GrTBackendEffectFactory<GrMagnifierEffect>::getInstance();
+}
+
+bool GrMagnifierEffect::onIsEqual(const GrEffect& sBase) const {
+ const GrMagnifierEffect& s = CastEffect<GrMagnifierEffect>(sBase);
+ return (this->texture(0) == s.texture(0) &&
+ this->fXOffset == s.fXOffset &&
+ this->fYOffset == s.fYOffset &&
+ this->fXZoom == s.fXZoom &&
+ this->fYZoom == s.fYZoom &&
+ this->fXInset == s.fXInset &&
+ this->fYInset == s.fYInset);
+}
+
+void GrMagnifierEffect::getConstantColorComponents(GrColor* color, uint32_t* validFlags) const {
+ this->updateConstantColorComponentsForModulation(color, validFlags);
+}
+
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+SkMagnifierImageFilter::SkMagnifierImageFilter(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {
+ float x = buffer.readScalar();
+ float y = buffer.readScalar();
+ float width = buffer.readScalar();
+ float height = buffer.readScalar();
+ fSrcRect = SkRect::MakeXYWH(x, y, width, height);
+ fInset = buffer.readScalar();
+}
+
+// FIXME: implement single-input semantics
+SkMagnifierImageFilter::SkMagnifierImageFilter(SkRect srcRect, SkScalar inset)
+ : INHERITED(0), fSrcRect(srcRect), fInset(inset) {
+ SkASSERT(srcRect.x() >= 0 && srcRect.y() >= 0 && inset >= 0);
+}
+
+#if SK_SUPPORT_GPU
+bool SkMagnifierImageFilter::asNewEffect(GrEffectRef** effect, GrTexture* texture, const SkIPoint&) const {
+ if (effect) {
+ *effect = GrMagnifierEffect::Create(texture,
+ fSrcRect.x() / texture->width(),
+ fSrcRect.y() / texture->height(),
+ texture->width() / fSrcRect.width(),
+ texture->height() / fSrcRect.height(),
+ fInset / texture->width(),
+ fInset / texture->height());
+ }
+ return true;
+}
+#endif
+
+void SkMagnifierImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeScalar(fSrcRect.x());
+ buffer.writeScalar(fSrcRect.y());
+ buffer.writeScalar(fSrcRect.width());
+ buffer.writeScalar(fSrcRect.height());
+ buffer.writeScalar(fInset);
+}
+
+bool SkMagnifierImageFilter::onFilterImage(Proxy*, const SkBitmap& src,
+ const SkMatrix&, SkBitmap* dst,
+ SkIPoint* offset) {
+ SkASSERT(src.config() == SkBitmap::kARGB_8888_Config);
+ SkASSERT(fSrcRect.width() < src.width());
+ SkASSERT(fSrcRect.height() < src.height());
+
+ if (src.config() != SkBitmap::kARGB_8888_Config) {
+ return false;
+ }
+
+ SkAutoLockPixels alp(src);
+ SkASSERT(src.getPixels());
+ if (!src.getPixels() || src.width() <= 0 || src.height() <= 0) {
+ return false;
+ }
+
+ SkScalar inv_inset = fInset > 0 ? SkScalarInvert(fInset) : SK_Scalar1;
+
+ SkScalar inv_x_zoom = fSrcRect.width() / src.width();
+ SkScalar inv_y_zoom = fSrcRect.height() / src.height();
+
+ dst->setConfig(src.config(), src.width(), src.height());
+ dst->allocPixels();
+ SkColor* sptr = src.getAddr32(0, 0);
+ SkColor* dptr = dst->getAddr32(0, 0);
+ int width = src.width(), height = src.height();
+ for (int y = 0; y < height; ++y) {
+ for (int x = 0; x < width; ++x) {
+ SkScalar x_dist = SkMin32(x, width - x - 1) * inv_inset;
+ SkScalar y_dist = SkMin32(y, height - y - 1) * inv_inset;
+ SkScalar weight = 0;
+
+ static const SkScalar kScalar2 = SkScalar(2);
+
+ // To create a smooth curve at the corners, we need to work on
+ // a square twice the size of the inset.
+ if (x_dist < kScalar2 && y_dist < kScalar2) {
+ x_dist = kScalar2 - x_dist;
+ y_dist = kScalar2 - y_dist;
+
+ SkScalar dist = SkScalarSqrt(SkScalarSquare(x_dist) +
+ SkScalarSquare(y_dist));
+ dist = SkMaxScalar(kScalar2 - dist, 0);
+ weight = SkMinScalar(SkScalarSquare(dist), SK_Scalar1);
+ } else {
+ SkScalar sqDist = SkMinScalar(SkScalarSquare(x_dist),
+ SkScalarSquare(y_dist));
+ weight = SkMinScalar(sqDist, SK_Scalar1);
+ }
+
+ SkScalar x_interp = SkScalarMul(weight, (fSrcRect.x() + x * inv_x_zoom)) +
+ (SK_Scalar1 - weight) * x;
+ SkScalar y_interp = SkScalarMul(weight, (fSrcRect.y() + y * inv_y_zoom)) +
+ (SK_Scalar1 - weight) * y;
+
+ int x_val = SkMin32(SkScalarFloorToInt(x_interp), width - 1);
+ int y_val = SkMin32(SkScalarFloorToInt(y_interp), height - 1);
+
+ *dptr = sptr[y_val * width + x_val];
+ dptr++;
+ }
+ }
+ return true;
+}
diff --git a/effects/SkMatrixConvolutionImageFilter.cpp b/effects/SkMatrixConvolutionImageFilter.cpp
new file mode 100644
index 00000000..b3ce7ecc
--- /dev/null
+++ b/effects/SkMatrixConvolutionImageFilter.cpp
@@ -0,0 +1,587 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkMatrixConvolutionImageFilter.h"
+#include "SkBitmap.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkRect.h"
+#include "SkUnPreMultiply.h"
+
+#if SK_SUPPORT_GPU
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
+#include "effects/GrSingleTextureEffect.h"
+#include "GrTBackendEffectFactory.h"
+#include "GrTexture.h"
+#include "SkMatrix.h"
+
+#endif
+
+SkMatrixConvolutionImageFilter::SkMatrixConvolutionImageFilter(const SkISize& kernelSize, const SkScalar* kernel, SkScalar gain, SkScalar bias, const SkIPoint& target, TileMode tileMode, bool convolveAlpha, SkImageFilter* input)
+ : INHERITED(input),
+ fKernelSize(kernelSize),
+ fGain(gain),
+ fBias(bias),
+ fTarget(target),
+ fTileMode(tileMode),
+ fConvolveAlpha(convolveAlpha) {
+ uint32_t size = fKernelSize.fWidth * fKernelSize.fHeight;
+ fKernel = SkNEW_ARRAY(SkScalar, size);
+ memcpy(fKernel, kernel, size * sizeof(SkScalar));
+ SkASSERT(kernelSize.fWidth >= 1 && kernelSize.fHeight >= 1);
+ SkASSERT(target.fX >= 0 && target.fX < kernelSize.fWidth);
+ SkASSERT(target.fY >= 0 && target.fY < kernelSize.fHeight);
+}
+
+SkMatrixConvolutionImageFilter::SkMatrixConvolutionImageFilter(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ fKernelSize.fWidth = buffer.readInt();
+ fKernelSize.fHeight = buffer.readInt();
+ uint32_t size = fKernelSize.fWidth * fKernelSize.fHeight;
+ fKernel = SkNEW_ARRAY(SkScalar, size);
+ SkDEBUGCODE(uint32_t readSize = )buffer.readScalarArray(fKernel);
+ SkASSERT(readSize == size);
+ fGain = buffer.readScalar();
+ fBias = buffer.readScalar();
+ fTarget.fX = buffer.readInt();
+ fTarget.fY = buffer.readInt();
+ fTileMode = (TileMode) buffer.readInt();
+ fConvolveAlpha = buffer.readBool();
+}
+
+void SkMatrixConvolutionImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeInt(fKernelSize.fWidth);
+ buffer.writeInt(fKernelSize.fHeight);
+ buffer.writeScalarArray(fKernel, fKernelSize.fWidth * fKernelSize.fHeight);
+ buffer.writeScalar(fGain);
+ buffer.writeScalar(fBias);
+ buffer.writeInt(fTarget.fX);
+ buffer.writeInt(fTarget.fY);
+ buffer.writeInt((int) fTileMode);
+ buffer.writeBool(fConvolveAlpha);
+}
+
+SkMatrixConvolutionImageFilter::~SkMatrixConvolutionImageFilter() {
+ delete[] fKernel;
+}
+
+class UncheckedPixelFetcher {
+public:
+ static inline SkPMColor fetch(const SkBitmap& src, int x, int y) {
+ return *src.getAddr32(x, y);
+ }
+};
+
+class ClampPixelFetcher {
+public:
+ static inline SkPMColor fetch(const SkBitmap& src, int x, int y) {
+ x = SkClampMax(x, src.width() - 1);
+ y = SkClampMax(y, src.height() - 1);
+ return *src.getAddr32(x, y);
+ }
+};
+
+class RepeatPixelFetcher {
+public:
+ static inline SkPMColor fetch(const SkBitmap& src, int x, int y) {
+ x %= src.width();
+ y %= src.height();
+ if (x < 0) {
+ x += src.width();
+ }
+ if (y < 0) {
+ y += src.height();
+ }
+ return *src.getAddr32(x, y);
+ }
+};
+
+class ClampToBlackPixelFetcher {
+public:
+ static inline SkPMColor fetch(const SkBitmap& src, int x, int y) {
+ if (x < 0 || x >= src.width() || y < 0 || y >= src.height()) {
+ return 0;
+ } else {
+ return *src.getAddr32(x, y);
+ }
+ }
+};
+
+template<class PixelFetcher, bool convolveAlpha>
+void SkMatrixConvolutionImageFilter::filterPixels(const SkBitmap& src, SkBitmap* result, const SkIRect& rect) {
+ for (int y = rect.fTop; y < rect.fBottom; ++y) {
+ SkPMColor* dptr = result->getAddr32(rect.fLeft, y);
+ for (int x = rect.fLeft; x < rect.fRight; ++x) {
+ SkScalar sumA = 0, sumR = 0, sumG = 0, sumB = 0;
+ for (int cy = 0; cy < fKernelSize.fHeight; cy++) {
+ for (int cx = 0; cx < fKernelSize.fWidth; cx++) {
+ SkPMColor s = PixelFetcher::fetch(src, x + cx - fTarget.fX, y + cy - fTarget.fY);
+ SkScalar k = fKernel[cy * fKernelSize.fWidth + cx];
+ if (convolveAlpha) {
+ sumA += SkScalarMul(SkIntToScalar(SkGetPackedA32(s)), k);
+ }
+ sumR += SkScalarMul(SkIntToScalar(SkGetPackedR32(s)), k);
+ sumG += SkScalarMul(SkIntToScalar(SkGetPackedG32(s)), k);
+ sumB += SkScalarMul(SkIntToScalar(SkGetPackedB32(s)), k);
+ }
+ }
+ int a = convolveAlpha
+ ? SkClampMax(SkScalarFloorToInt(SkScalarMul(sumA, fGain) + fBias), 255)
+ : 255;
+ int r = SkClampMax(SkScalarFloorToInt(SkScalarMul(sumR, fGain) + fBias), a);
+ int g = SkClampMax(SkScalarFloorToInt(SkScalarMul(sumG, fGain) + fBias), a);
+ int b = SkClampMax(SkScalarFloorToInt(SkScalarMul(sumB, fGain) + fBias), a);
+ if (!convolveAlpha) {
+ a = SkGetPackedA32(PixelFetcher::fetch(src, x, y));
+ *dptr++ = SkPreMultiplyARGB(a, r, g, b);
+ } else {
+ *dptr++ = SkPackARGB32(a, r, g, b);
+ }
+ }
+ }
+}
+
+template<class PixelFetcher>
+void SkMatrixConvolutionImageFilter::filterPixels(const SkBitmap& src, SkBitmap* result, const SkIRect& rect) {
+ if (fConvolveAlpha) {
+ filterPixels<PixelFetcher, true>(src, result, rect);
+ } else {
+ filterPixels<PixelFetcher, false>(src, result, rect);
+ }
+}
+
+void SkMatrixConvolutionImageFilter::filterInteriorPixels(const SkBitmap& src, SkBitmap* result, const SkIRect& rect) {
+ filterPixels<UncheckedPixelFetcher>(src, result, rect);
+}
+
+void SkMatrixConvolutionImageFilter::filterBorderPixels(const SkBitmap& src, SkBitmap* result, const SkIRect& rect) {
+ switch (fTileMode) {
+ case kClamp_TileMode:
+ filterPixels<ClampPixelFetcher>(src, result, rect);
+ break;
+ case kRepeat_TileMode:
+ filterPixels<RepeatPixelFetcher>(src, result, rect);
+ break;
+ case kClampToBlack_TileMode:
+ filterPixels<ClampToBlackPixelFetcher>(src, result, rect);
+ break;
+ }
+}
+
+// FIXME: This should be refactored to SkImageFilterUtils for
+// use by other filters. For now, we assume the input is always
+// premultiplied and unpremultiply it
+static SkBitmap unpremultiplyBitmap(const SkBitmap& src)
+{
+ SkAutoLockPixels alp(src);
+ if (!src.getPixels()) {
+ return SkBitmap();
+ }
+ SkBitmap result;
+ result.setConfig(src.config(), src.width(), src.height());
+ result.allocPixels();
+ if (!result.getPixels()) {
+ return SkBitmap();
+ }
+ for (int y = 0; y < src.height(); ++y) {
+ const uint32_t* srcRow = src.getAddr32(0, y);
+ uint32_t* dstRow = result.getAddr32(0, y);
+ for (int x = 0; x < src.width(); ++x) {
+ dstRow[x] = SkUnPreMultiply::PMColorToColor(srcRow[x]);
+ }
+ }
+ return result;
+}
+
+bool SkMatrixConvolutionImageFilter::onFilterImage(Proxy* proxy,
+ const SkBitmap& source,
+ const SkMatrix& matrix,
+ SkBitmap* result,
+ SkIPoint* loc) {
+ SkBitmap src = source;
+ if (getInput(0) && !getInput(0)->filterImage(proxy, source, matrix, &src, loc)) {
+ return false;
+ }
+
+ if (src.config() != SkBitmap::kARGB_8888_Config) {
+ return false;
+ }
+
+ if (!fConvolveAlpha && !src.isOpaque()) {
+ src = unpremultiplyBitmap(src);
+ }
+
+ SkAutoLockPixels alp(src);
+ if (!src.getPixels()) {
+ return false;
+ }
+
+ result->setConfig(src.config(), src.width(), src.height());
+ result->allocPixels();
+
+ SkIRect interior = SkIRect::MakeXYWH(fTarget.fX, fTarget.fY,
+ src.width() - fKernelSize.fWidth + 1,
+ src.height() - fKernelSize.fHeight + 1);
+ SkIRect top = SkIRect::MakeWH(src.width(), fTarget.fY);
+ SkIRect bottom = SkIRect::MakeLTRB(0, interior.bottom(),
+ src.width(), src.height());
+ SkIRect left = SkIRect::MakeXYWH(0, interior.top(),
+ fTarget.fX, interior.height());
+ SkIRect right = SkIRect::MakeLTRB(interior.right(), interior.top(),
+ src.width(), interior.bottom());
+ filterBorderPixels(src, result, top);
+ filterBorderPixels(src, result, left);
+ filterInteriorPixels(src, result, interior);
+ filterBorderPixels(src, result, right);
+ filterBorderPixels(src, result, bottom);
+ return true;
+}
+
+#if SK_SUPPORT_GPU
+
+///////////////////////////////////////////////////////////////////////////////
+
+class GrGLMatrixConvolutionEffect;
+
+class GrMatrixConvolutionEffect : public GrSingleTextureEffect {
+public:
+ typedef SkMatrixConvolutionImageFilter::TileMode TileMode;
+ static GrEffectRef* Create(GrTexture* texture,
+ const SkISize& kernelSize,
+ const SkScalar* kernel,
+ SkScalar gain,
+ SkScalar bias,
+ const SkIPoint& target,
+ TileMode tileMode,
+ bool convolveAlpha) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrMatrixConvolutionEffect, (texture,
+ kernelSize,
+ kernel,
+ gain,
+ bias,
+ target,
+ tileMode,
+ convolveAlpha)));
+ return CreateEffectRef(effect);
+ }
+ virtual ~GrMatrixConvolutionEffect();
+
+ virtual void getConstantColorComponents(GrColor* color,
+ uint32_t* validFlags) const SK_OVERRIDE {
+ // TODO: Try to do better?
+ *validFlags = 0;
+ }
+
+ static const char* Name() { return "MatrixConvolution"; }
+ const SkISize& kernelSize() const { return fKernelSize; }
+ const float* target() const { return fTarget; }
+ const float* kernel() const { return fKernel; }
+ float gain() const { return fGain; }
+ float bias() const { return fBias; }
+ TileMode tileMode() const { return fTileMode; }
+ bool convolveAlpha() const { return fConvolveAlpha; }
+
+ typedef GrGLMatrixConvolutionEffect GLEffect;
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+
+private:
+ GrMatrixConvolutionEffect(GrTexture*,
+ const SkISize& kernelSize,
+ const SkScalar* kernel,
+ SkScalar gain,
+ SkScalar bias,
+ const SkIPoint& target,
+ TileMode tileMode,
+ bool convolveAlpha);
+
+ virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE;
+
+ SkISize fKernelSize;
+ float *fKernel;
+ float fGain;
+ float fBias;
+ float fTarget[2];
+ TileMode fTileMode;
+ bool fConvolveAlpha;
+
+ GR_DECLARE_EFFECT_TEST;
+
+ typedef GrSingleTextureEffect INHERITED;
+};
+
+class GrGLMatrixConvolutionEffect : public GrGLEffect {
+public:
+ GrGLMatrixConvolutionEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& effect);
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ static inline EffectKey GenKey(const GrDrawEffect&, const GrGLCaps&);
+
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+private:
+ typedef GrGLUniformManager::UniformHandle UniformHandle;
+ typedef SkMatrixConvolutionImageFilter::TileMode TileMode;
+ SkISize fKernelSize;
+ TileMode fTileMode;
+ bool fConvolveAlpha;
+
+ UniformHandle fKernelUni;
+ UniformHandle fImageIncrementUni;
+ UniformHandle fTargetUni;
+ UniformHandle fGainUni;
+ UniformHandle fBiasUni;
+
+ GrGLEffectMatrix fEffectMatrix;
+
+ typedef GrGLEffect INHERITED;
+};
+
+GrGLMatrixConvolutionEffect::GrGLMatrixConvolutionEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect)
+ : INHERITED(factory)
+ , fKernelUni(GrGLUniformManager::kInvalidUniformHandle)
+ , fImageIncrementUni(GrGLUniformManager::kInvalidUniformHandle)
+ , fTargetUni(GrGLUniformManager::kInvalidUniformHandle)
+ , fGainUni(GrGLUniformManager::kInvalidUniformHandle)
+ , fBiasUni(GrGLUniformManager::kInvalidUniformHandle)
+ , fEffectMatrix(drawEffect.castEffect<GrMatrixConvolutionEffect>().coordsType()) {
+ const GrMatrixConvolutionEffect& m = drawEffect.castEffect<GrMatrixConvolutionEffect>();
+ fKernelSize = m.kernelSize();
+ fTileMode = m.tileMode();
+ fConvolveAlpha = m.convolveAlpha();
+}
+
+static void appendTextureLookup(GrGLShaderBuilder* builder,
+ const GrGLShaderBuilder::TextureSampler& sampler,
+ const char* coord,
+ SkMatrixConvolutionImageFilter::TileMode tileMode) {
+ SkString clampedCoord;
+ switch (tileMode) {
+ case SkMatrixConvolutionImageFilter::kClamp_TileMode:
+ clampedCoord.printf("clamp(%s, 0.0, 1.0)", coord);
+ coord = clampedCoord.c_str();
+ break;
+ case SkMatrixConvolutionImageFilter::kRepeat_TileMode:
+ clampedCoord.printf("fract(%s)", coord);
+ coord = clampedCoord.c_str();
+ break;
+ case SkMatrixConvolutionImageFilter::kClampToBlack_TileMode:
+ builder->fsCodeAppendf("clamp(%s, 0.0, 1.0) != %s ? vec4(0, 0, 0, 0) : ", coord, coord);
+ break;
+ }
+ builder->appendTextureLookup(GrGLShaderBuilder::kFragment_ShaderType, sampler, coord);
+}
+
+void GrGLMatrixConvolutionEffect::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect&,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ const char* coords;
+ fEffectMatrix.emitCodeMakeFSCoords2D(builder, key, &coords);
+ fImageIncrementUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec2f_GrSLType, "ImageIncrement");
+ fKernelUni = builder->addUniformArray(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "Kernel", fKernelSize.width() * fKernelSize.height());
+ fTargetUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec2f_GrSLType, "Target");
+ fGainUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "Gain");
+ fBiasUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "Bias");
+
+ const char* target = builder->getUniformCStr(fTargetUni);
+ const char* imgInc = builder->getUniformCStr(fImageIncrementUni);
+ const char* kernel = builder->getUniformCStr(fKernelUni);
+ const char* gain = builder->getUniformCStr(fGainUni);
+ const char* bias = builder->getUniformCStr(fBiasUni);
+ int kWidth = fKernelSize.width();
+ int kHeight = fKernelSize.height();
+
+ builder->fsCodeAppend("\t\tvec4 sum = vec4(0, 0, 0, 0);\n");
+ builder->fsCodeAppendf("\t\tvec2 coord = %s - %s * %s;\n", coords, target, imgInc);
+ builder->fsCodeAppendf("\t\tfor (int y = 0; y < %d; y++) {\n", kHeight);
+ builder->fsCodeAppendf("\t\t\tfor (int x = 0; x < %d; x++) {\n", kWidth);
+ builder->fsCodeAppendf("\t\t\t\tfloat k = %s[y * %d + x];\n", kernel, kWidth);
+ builder->fsCodeAppendf("\t\t\t\tvec2 coord2 = coord + vec2(x, y) * %s;\n", imgInc);
+ builder->fsCodeAppend("\t\t\t\tvec4 c = ");
+ appendTextureLookup(builder, samplers[0], "coord2", fTileMode);
+ builder->fsCodeAppend(";\n");
+ if (!fConvolveAlpha) {
+ builder->fsCodeAppend("\t\t\t\tc.rgb /= c.a;\n");
+ }
+ builder->fsCodeAppend("\t\t\t\tsum += c * k;\n");
+ builder->fsCodeAppend("\t\t\t}\n");
+ builder->fsCodeAppend("\t\t}\n");
+ if (fConvolveAlpha) {
+ builder->fsCodeAppendf("\t\t%s = sum * %s + %s;\n", outputColor, gain, bias);
+ builder->fsCodeAppendf("\t\t%s.rgb = clamp(%s.rgb, 0.0, %s.a);\n", outputColor, outputColor, outputColor);
+ } else {
+ builder->fsCodeAppend("\t\tvec4 c = ");
+ appendTextureLookup(builder, samplers[0], coords, fTileMode);
+ builder->fsCodeAppend(";\n");
+ builder->fsCodeAppendf("\t\t%s.a = c.a;\n", outputColor);
+ builder->fsCodeAppendf("\t\t%s.rgb = sum.rgb * %s + %s;\n", outputColor, gain, bias);
+ builder->fsCodeAppendf("\t\t%s.rgb *= %s.a;\n", outputColor, outputColor);
+ }
+}
+
+namespace {
+
+int encodeXY(int x, int y) {
+ SkASSERT(x >= 1 && y >= 1 && x * y <= 32);
+ if (y < x)
+ return 0x40 | encodeXY(y, x);
+ else
+ return (0x40 >> x) | (y - x);
+}
+
+};
+
+GrGLEffect::EffectKey GrGLMatrixConvolutionEffect::GenKey(const GrDrawEffect& drawEffect,
+ const GrGLCaps&) {
+ const GrMatrixConvolutionEffect& m = drawEffect.castEffect<GrMatrixConvolutionEffect>();
+ EffectKey key = encodeXY(m.kernelSize().width(), m.kernelSize().height());
+ key |= m.tileMode() << 7;
+ key |= m.convolveAlpha() ? 1 << 9 : 0;
+ key <<= GrGLEffectMatrix::kKeyBits;
+ EffectKey matrixKey = GrGLEffectMatrix::GenKey(m.getMatrix(),
+ drawEffect,
+ m.coordsType(),
+ m.texture(0));
+ return key | matrixKey;
+}
+
+void GrGLMatrixConvolutionEffect::setData(const GrGLUniformManager& uman,
+ const GrDrawEffect& drawEffect) {
+ const GrMatrixConvolutionEffect& conv = drawEffect.castEffect<GrMatrixConvolutionEffect>();
+ GrTexture& texture = *conv.texture(0);
+ // the code we generated was for a specific kernel size
+ GrAssert(conv.kernelSize() == fKernelSize);
+ GrAssert(conv.tileMode() == fTileMode);
+ float imageIncrement[2];
+ float ySign = texture.origin() == kTopLeft_GrSurfaceOrigin ? 1.0f : -1.0f;
+ imageIncrement[0] = 1.0f / texture.width();
+ imageIncrement[1] = ySign / texture.height();
+ uman.set2fv(fImageIncrementUni, 0, 1, imageIncrement);
+ uman.set2fv(fTargetUni, 0, 1, conv.target());
+ uman.set1fv(fKernelUni, 0, fKernelSize.width() * fKernelSize.height(), conv.kernel());
+ uman.set1f(fGainUni, conv.gain());
+ uman.set1f(fBiasUni, conv.bias());
+ fEffectMatrix.setData(uman,
+ conv.getMatrix(),
+ drawEffect,
+ conv.texture(0));
+}
+
+GrMatrixConvolutionEffect::GrMatrixConvolutionEffect(GrTexture* texture,
+ const SkISize& kernelSize,
+ const SkScalar* kernel,
+ SkScalar gain,
+ SkScalar bias,
+ const SkIPoint& target,
+ TileMode tileMode,
+ bool convolveAlpha)
+ : INHERITED(texture, MakeDivByTextureWHMatrix(texture)),
+ fKernelSize(kernelSize),
+ fGain(SkScalarToFloat(gain)),
+ fBias(SkScalarToFloat(bias) / 255.0f),
+ fTileMode(tileMode),
+ fConvolveAlpha(convolveAlpha) {
+ fKernel = new float[kernelSize.width() * kernelSize.height()];
+ for (int i = 0; i < kernelSize.width() * kernelSize.height(); i++) {
+ fKernel[i] = SkScalarToFloat(kernel[i]);
+ }
+ fTarget[0] = static_cast<float>(target.x());
+ fTarget[1] = static_cast<float>(target.y());
+}
+
+GrMatrixConvolutionEffect::~GrMatrixConvolutionEffect() {
+ delete[] fKernel;
+}
+
+const GrBackendEffectFactory& GrMatrixConvolutionEffect::getFactory() const {
+ return GrTBackendEffectFactory<GrMatrixConvolutionEffect>::getInstance();
+}
+
+bool GrMatrixConvolutionEffect::onIsEqual(const GrEffect& sBase) const {
+ const GrMatrixConvolutionEffect& s = CastEffect<GrMatrixConvolutionEffect>(sBase);
+ return this->texture(0) == s.texture(0) &&
+ fKernelSize == s.kernelSize() &&
+ !memcmp(fKernel, s.kernel(), fKernelSize.width() * fKernelSize.height() * sizeof(float)) &&
+ fGain == s.gain() &&
+ fBias == s.bias() &&
+ fTarget == s.target() &&
+ fTileMode == s.tileMode() &&
+ fConvolveAlpha == s.convolveAlpha();
+}
+
+GR_DEFINE_EFFECT_TEST(GrMatrixConvolutionEffect);
+
+// A little bit less than the minimum # uniforms required by DX9SM2 (32).
+// Allows for a 5x5 kernel (or 25x1, for that matter).
+#define MAX_KERNEL_SIZE 25
+
+GrEffectRef* GrMatrixConvolutionEffect::TestCreate(SkMWCRandom* random,
+ GrContext* context,
+ const GrDrawTargetCaps&,
+ GrTexture* textures[]) {
+ int texIdx = random->nextBool() ? GrEffectUnitTest::kSkiaPMTextureIdx :
+ GrEffectUnitTest::kAlphaTextureIdx;
+ int width = random->nextRangeU(1, MAX_KERNEL_SIZE);
+ int height = random->nextRangeU(1, MAX_KERNEL_SIZE / width);
+ SkISize kernelSize = SkISize::Make(width, height);
+ SkAutoTDeleteArray<SkScalar> kernel(new SkScalar[width * height]);
+ for (int i = 0; i < width * height; i++) {
+ kernel.get()[i] = random->nextSScalar1();
+ }
+ SkScalar gain = random->nextSScalar1();
+ SkScalar bias = random->nextSScalar1();
+ SkIPoint target = SkIPoint::Make(random->nextRangeU(0, kernelSize.width()),
+ random->nextRangeU(0, kernelSize.height()));
+ TileMode tileMode = static_cast<TileMode>(random->nextRangeU(0, 2));
+ bool convolveAlpha = random->nextBool();
+ return GrMatrixConvolutionEffect::Create(textures[texIdx],
+ kernelSize,
+ kernel.get(),
+ gain,
+ bias,
+ target,
+ tileMode,
+ convolveAlpha);
+}
+
+bool SkMatrixConvolutionImageFilter::asNewEffect(GrEffectRef** effect,
+ GrTexture* texture,
+ const SkIPoint&) const {
+ if (!effect) {
+ return fKernelSize.width() * fKernelSize.height() <= MAX_KERNEL_SIZE;
+ }
+ SkASSERT(fKernelSize.width() * fKernelSize.height() <= MAX_KERNEL_SIZE);
+ *effect = GrMatrixConvolutionEffect::Create(texture,
+ fKernelSize,
+ fKernel,
+ fGain,
+ fBias,
+ fTarget,
+ fTileMode,
+ fConvolveAlpha);
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#endif
diff --git a/effects/SkMergeImageFilter.cpp b/effects/SkMergeImageFilter.cpp
new file mode 100755
index 00000000..0c1388f9
--- /dev/null
+++ b/effects/SkMergeImageFilter.cpp
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkMergeImageFilter.h"
+#include "SkCanvas.h"
+#include "SkDevice.h"
+#include "SkFlattenableBuffers.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkMergeImageFilter::initAllocModes() {
+ int inputCount = countInputs();
+ if (inputCount) {
+ size_t size = sizeof(uint8_t) * inputCount;
+ if (size <= sizeof(fStorage)) {
+ fModes = SkTCast<uint8_t*>(fStorage);
+ } else {
+ fModes = SkTCast<uint8_t*>(sk_malloc_throw(size));
+ }
+ } else {
+ fModes = NULL;
+ }
+}
+
+void SkMergeImageFilter::initModes(const SkXfermode::Mode modes[]) {
+ if (modes) {
+ this->initAllocModes();
+ int inputCount = countInputs();
+ for (int i = 0; i < inputCount; ++i) {
+ fModes[i] = SkToU8(modes[i]);
+ }
+ } else {
+ fModes = NULL;
+ }
+}
+
+SkMergeImageFilter::SkMergeImageFilter(SkImageFilter* first, SkImageFilter* second,
+ SkXfermode::Mode mode) : INHERITED(first, second) {
+ if (SkXfermode::kSrcOver_Mode != mode) {
+ SkXfermode::Mode modes[] = { mode, mode };
+ this->initModes(modes);
+ } else {
+ fModes = NULL;
+ }
+}
+
+SkMergeImageFilter::SkMergeImageFilter(SkImageFilter* filters[], int count,
+ const SkXfermode::Mode modes[]) : INHERITED(count, filters) {
+ this->initModes(modes);
+}
+
+SkMergeImageFilter::~SkMergeImageFilter() {
+
+ if (fModes != SkTCast<uint8_t*>(fStorage)) {
+ sk_free(fModes);
+ }
+}
+
+bool SkMergeImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
+ SkIRect* dst) {
+ if (countInputs() < 1) {
+ return false;
+ }
+
+ SkIRect totalBounds;
+
+ int inputCount = countInputs();
+ for (int i = 0; i < inputCount; ++i) {
+ SkImageFilter* filter = getInput(i);
+ SkIRect r;
+ if (filter) {
+ if (!filter->filterBounds(src, ctm, &r)) {
+ return false;
+ }
+ } else {
+ r = src;
+ }
+ if (0 == i) {
+ totalBounds = r;
+ } else {
+ totalBounds.join(r);
+ }
+ }
+
+ // don't modify dst until now, so we don't accidentally change it in the
+ // loop, but then return false on the next filter.
+ *dst = totalBounds;
+ return true;
+}
+
+bool SkMergeImageFilter::onFilterImage(Proxy* proxy, const SkBitmap& src,
+ const SkMatrix& ctm,
+ SkBitmap* result, SkIPoint* loc) {
+ if (countInputs() < 1) {
+ return false;
+ }
+
+ const SkIRect srcBounds = SkIRect::MakeXYWH(loc->x(), loc->y(),
+ src.width(), src.height());
+ SkIRect bounds;
+ if (!this->filterBounds(srcBounds, ctm, &bounds)) {
+ return false;
+ }
+
+ const int x0 = bounds.left();
+ const int y0 = bounds.top();
+
+ SkAutoTUnref<SkDevice> dst(proxy->createDevice(bounds.width(), bounds.height()));
+ if (NULL == dst) {
+ return false;
+ }
+ SkCanvas canvas(dst);
+ SkPaint paint;
+
+ int inputCount = countInputs();
+ for (int i = 0; i < inputCount; ++i) {
+ SkBitmap tmp;
+ const SkBitmap* srcPtr;
+ SkIPoint pos = *loc;
+ SkImageFilter* filter = getInput(i);
+ if (filter) {
+ if (!filter->filterImage(proxy, src, ctm, &tmp, &pos)) {
+ return false;
+ }
+ srcPtr = &tmp;
+ } else {
+ srcPtr = &src;
+ }
+
+ if (fModes) {
+ paint.setXfermodeMode((SkXfermode::Mode)fModes[i]);
+ } else {
+ paint.setXfermode(NULL);
+ }
+ canvas.drawSprite(*srcPtr, pos.x() - x0, pos.y() - y0, &paint);
+ }
+
+ loc->set(bounds.left(), bounds.top());
+ *result = dst->accessBitmap(false);
+ return true;
+}
+
+void SkMergeImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+
+ buffer.writeBool(fModes != NULL);
+ if (fModes) {
+ buffer.writeByteArray(fModes, countInputs() * sizeof(fModes[0]));
+ }
+}
+
+SkMergeImageFilter::SkMergeImageFilter(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ bool hasModes = buffer.readBool();
+ if (hasModes) {
+ this->initAllocModes();
+ SkASSERT(buffer.getArrayCount() == countInputs() * sizeof(fModes[0]));
+ buffer.readByteArray(fModes);
+ } else {
+ fModes = 0;
+ }
+}
diff --git a/effects/SkMorphologyImageFilter.cpp b/effects/SkMorphologyImageFilter.cpp
new file mode 100644
index 00000000..fb8fd000
--- /dev/null
+++ b/effects/SkMorphologyImageFilter.cpp
@@ -0,0 +1,533 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkMorphologyImageFilter.h"
+#include "SkBitmap.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkRect.h"
+#if SK_SUPPORT_GPU
+#include "GrContext.h"
+#include "GrTexture.h"
+#include "GrTBackendEffectFactory.h"
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
+#include "effects/Gr1DKernelEffect.h"
+#include "SkImageFilterUtils.h"
+#endif
+
+SkMorphologyImageFilter::SkMorphologyImageFilter(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {
+ fRadius.fWidth = buffer.readInt();
+ fRadius.fHeight = buffer.readInt();
+}
+
+SkMorphologyImageFilter::SkMorphologyImageFilter(int radiusX, int radiusY, SkImageFilter* input)
+ : INHERITED(input), fRadius(SkISize::Make(radiusX, radiusY)) {
+}
+
+
+void SkMorphologyImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeInt(fRadius.fWidth);
+ buffer.writeInt(fRadius.fHeight);
+}
+
+static void erode(const SkPMColor* src, SkPMColor* dst,
+ int radius, int width, int height,
+ int srcStrideX, int srcStrideY,
+ int dstStrideX, int dstStrideY)
+{
+ radius = SkMin32(radius, width - 1);
+ const SkPMColor* upperSrc = src + radius * srcStrideX;
+ for (int x = 0; x < width; ++x) {
+ const SkPMColor* lp = src;
+ const SkPMColor* up = upperSrc;
+ SkPMColor* dptr = dst;
+ for (int y = 0; y < height; ++y) {
+ int minB = 255, minG = 255, minR = 255, minA = 255;
+ for (const SkPMColor* p = lp; p <= up; p += srcStrideX) {
+ int b = SkGetPackedB32(*p);
+ int g = SkGetPackedG32(*p);
+ int r = SkGetPackedR32(*p);
+ int a = SkGetPackedA32(*p);
+ if (b < minB) minB = b;
+ if (g < minG) minG = g;
+ if (r < minR) minR = r;
+ if (a < minA) minA = a;
+ }
+ *dptr = SkPackARGB32(minA, minR, minG, minB);
+ dptr += dstStrideY;
+ lp += srcStrideY;
+ up += srcStrideY;
+ }
+ if (x >= radius) src += srcStrideX;
+ if (x + radius < width - 1) upperSrc += srcStrideX;
+ dst += dstStrideX;
+ }
+}
+
+static void erodeX(const SkBitmap& src, SkBitmap* dst, int radiusX)
+{
+ erode(src.getAddr32(0, 0), dst->getAddr32(0, 0),
+ radiusX, src.width(), src.height(),
+ 1, src.rowBytesAsPixels(), 1, dst->rowBytesAsPixels());
+}
+
+static void erodeY(const SkBitmap& src, SkBitmap* dst, int radiusY)
+{
+ erode(src.getAddr32(0, 0), dst->getAddr32(0, 0),
+ radiusY, src.height(), src.width(),
+ src.rowBytesAsPixels(), 1, dst->rowBytesAsPixels(), 1);
+}
+
+static void dilate(const SkPMColor* src, SkPMColor* dst,
+ int radius, int width, int height,
+ int srcStrideX, int srcStrideY,
+ int dstStrideX, int dstStrideY)
+{
+ radius = SkMin32(radius, width - 1);
+ const SkPMColor* upperSrc = src + radius * srcStrideX;
+ for (int x = 0; x < width; ++x) {
+ const SkPMColor* lp = src;
+ const SkPMColor* up = upperSrc;
+ SkPMColor* dptr = dst;
+ for (int y = 0; y < height; ++y) {
+ int maxB = 0, maxG = 0, maxR = 0, maxA = 0;
+ for (const SkPMColor* p = lp; p <= up; p += srcStrideX) {
+ int b = SkGetPackedB32(*p);
+ int g = SkGetPackedG32(*p);
+ int r = SkGetPackedR32(*p);
+ int a = SkGetPackedA32(*p);
+ if (b > maxB) maxB = b;
+ if (g > maxG) maxG = g;
+ if (r > maxR) maxR = r;
+ if (a > maxA) maxA = a;
+ }
+ *dptr = SkPackARGB32(maxA, maxR, maxG, maxB);
+ dptr += dstStrideY;
+ lp += srcStrideY;
+ up += srcStrideY;
+ }
+ if (x >= radius) src += srcStrideX;
+ if (x + radius < width - 1) upperSrc += srcStrideX;
+ dst += dstStrideX;
+ }
+}
+
+static void dilateX(const SkBitmap& src, SkBitmap* dst, int radiusX)
+{
+ dilate(src.getAddr32(0, 0), dst->getAddr32(0, 0),
+ radiusX, src.width(), src.height(),
+ 1, src.rowBytesAsPixels(), 1, dst->rowBytesAsPixels());
+}
+
+static void dilateY(const SkBitmap& src, SkBitmap* dst, int radiusY)
+{
+ dilate(src.getAddr32(0, 0), dst->getAddr32(0, 0),
+ radiusY, src.height(), src.width(),
+ src.rowBytesAsPixels(), 1, dst->rowBytesAsPixels(), 1);
+}
+
+bool SkErodeImageFilter::onFilterImage(Proxy* proxy,
+ const SkBitmap& source, const SkMatrix& ctm,
+ SkBitmap* dst, SkIPoint* offset) {
+ SkBitmap src = source;
+ if (getInput(0) && !getInput(0)->filterImage(proxy, source, ctm, &src, offset)) {
+ return false;
+ }
+
+ if (src.config() != SkBitmap::kARGB_8888_Config) {
+ return false;
+ }
+
+ SkAutoLockPixels alp(src);
+ if (!src.getPixels()) {
+ return false;
+ }
+
+ dst->setConfig(src.config(), src.width(), src.height());
+ dst->allocPixels();
+
+ int width = radius().width();
+ int height = radius().height();
+
+ if (width < 0 || height < 0) {
+ return false;
+ }
+
+ if (width == 0 && height == 0) {
+ src.copyTo(dst, dst->config());
+ return true;
+ }
+
+ SkBitmap temp;
+ temp.setConfig(dst->config(), dst->width(), dst->height());
+ if (!temp.allocPixels()) {
+ return false;
+ }
+
+ if (width > 0 && height > 0) {
+ erodeX(src, &temp, width);
+ erodeY(temp, dst, height);
+ } else if (width > 0) {
+ erodeX(src, dst, width);
+ } else if (height > 0) {
+ erodeY(src, dst, height);
+ }
+ return true;
+}
+
+bool SkDilateImageFilter::onFilterImage(Proxy* proxy,
+ const SkBitmap& source, const SkMatrix& ctm,
+ SkBitmap* dst, SkIPoint* offset) {
+ SkBitmap src = source;
+ if (getInput(0) && !getInput(0)->filterImage(proxy, source, ctm, &src, offset)) {
+ return false;
+ }
+ if (src.config() != SkBitmap::kARGB_8888_Config) {
+ return false;
+ }
+
+ SkAutoLockPixels alp(src);
+ if (!src.getPixels()) {
+ return false;
+ }
+
+ dst->setConfig(src.config(), src.width(), src.height());
+ dst->allocPixels();
+
+ int width = radius().width();
+ int height = radius().height();
+
+ if (width < 0 || height < 0) {
+ return false;
+ }
+
+ if (width == 0 && height == 0) {
+ src.copyTo(dst, dst->config());
+ return true;
+ }
+
+ SkBitmap temp;
+ temp.setConfig(dst->config(), dst->width(), dst->height());
+ if (!temp.allocPixels()) {
+ return false;
+ }
+
+ if (width > 0 && height > 0) {
+ dilateX(src, &temp, width);
+ dilateY(temp, dst, height);
+ } else if (width > 0) {
+ dilateX(src, dst, width);
+ } else if (height > 0) {
+ dilateY(src, dst, height);
+ }
+ return true;
+}
+
+#if SK_SUPPORT_GPU
+
+///////////////////////////////////////////////////////////////////////////////
+
+class GrGLMorphologyEffect;
+
+/**
+ * Morphology effects. Depending upon the type of morphology, either the
+ * component-wise min (Erode_Type) or max (Dilate_Type) of all pixels in the
+ * kernel is selected as the new color. The new color is modulated by the input
+ * color.
+ */
+class GrMorphologyEffect : public Gr1DKernelEffect {
+
+public:
+
+ enum MorphologyType {
+ kErode_MorphologyType,
+ kDilate_MorphologyType,
+ };
+
+ static GrEffectRef* Create(GrTexture* tex, Direction dir, int radius, MorphologyType type) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrMorphologyEffect, (tex, dir, radius, type)));
+ return CreateEffectRef(effect);
+ }
+
+ virtual ~GrMorphologyEffect();
+
+ MorphologyType type() const { return fType; }
+
+ static const char* Name() { return "Morphology"; }
+
+ typedef GrGLMorphologyEffect GLEffect;
+
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+ virtual void getConstantColorComponents(GrColor* color, uint32_t* validFlags) const SK_OVERRIDE;
+
+protected:
+
+ MorphologyType fType;
+
+private:
+ virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE;
+
+ GrMorphologyEffect(GrTexture*, Direction, int radius, MorphologyType);
+
+ GR_DECLARE_EFFECT_TEST;
+
+ typedef Gr1DKernelEffect INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class GrGLMorphologyEffect : public GrGLEffect {
+public:
+ GrGLMorphologyEffect (const GrBackendEffectFactory&, const GrDrawEffect&);
+
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ static inline EffectKey GenKey(const GrDrawEffect&, const GrGLCaps&);
+
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+private:
+ int width() const { return GrMorphologyEffect::WidthFromRadius(fRadius); }
+
+ int fRadius;
+ GrMorphologyEffect::MorphologyType fType;
+ GrGLUniformManager::UniformHandle fImageIncrementUni;
+ GrGLEffectMatrix fEffectMatrix;
+
+ typedef GrGLEffect INHERITED;
+};
+
+GrGLMorphologyEffect::GrGLMorphologyEffect(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect)
+ : INHERITED(factory)
+ , fImageIncrementUni(GrGLUniformManager::kInvalidUniformHandle)
+ , fEffectMatrix(drawEffect.castEffect<GrMorphologyEffect>().coordsType()) {
+ const GrMorphologyEffect& m = drawEffect.castEffect<GrMorphologyEffect>();
+ fRadius = m.radius();
+ fType = m.type();
+}
+
+void GrGLMorphologyEffect::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect&,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ const char* coords;
+ fEffectMatrix.emitCodeMakeFSCoords2D(builder, key, &coords);
+ fImageIncrementUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec2f_GrSLType, "ImageIncrement");
+
+ const char* func;
+ switch (fType) {
+ case GrMorphologyEffect::kErode_MorphologyType:
+ builder->fsCodeAppendf("\t\t%s = vec4(1, 1, 1, 1);\n", outputColor);
+ func = "min";
+ break;
+ case GrMorphologyEffect::kDilate_MorphologyType:
+ builder->fsCodeAppendf("\t\t%s = vec4(0, 0, 0, 0);\n", outputColor);
+ func = "max";
+ break;
+ default:
+ GrCrash("Unexpected type");
+ func = ""; // suppress warning
+ break;
+ }
+ const char* imgInc = builder->getUniformCStr(fImageIncrementUni);
+
+ builder->fsCodeAppendf("\t\tvec2 coord = %s - %d.0 * %s;\n", coords, fRadius, imgInc);
+ builder->fsCodeAppendf("\t\tfor (int i = 0; i < %d; i++) {\n", this->width());
+ builder->fsCodeAppendf("\t\t\t%s = %s(%s, ", outputColor, func, outputColor);
+ builder->appendTextureLookup(GrGLShaderBuilder::kFragment_ShaderType, samplers[0], "coord");
+ builder->fsCodeAppend(");\n");
+ builder->fsCodeAppendf("\t\t\tcoord += %s;\n", imgInc);
+ builder->fsCodeAppend("\t\t}\n");
+ SkString modulate;
+ GrGLSLMulVarBy4f(&modulate, 2, outputColor, inputColor);
+ builder->fsCodeAppend(modulate.c_str());
+}
+
+GrGLEffect::EffectKey GrGLMorphologyEffect::GenKey(const GrDrawEffect& drawEffect,
+ const GrGLCaps&) {
+ const GrMorphologyEffect& m = drawEffect.castEffect<GrMorphologyEffect>();
+ EffectKey key = static_cast<EffectKey>(m.radius());
+ key |= (m.type() << 8);
+ key <<= GrGLEffectMatrix::kKeyBits;
+ EffectKey matrixKey = GrGLEffectMatrix::GenKey(m.getMatrix(),
+ drawEffect,
+ m.coordsType(),
+ m.texture(0));
+ return key | matrixKey;
+}
+
+void GrGLMorphologyEffect::setData(const GrGLUniformManager& uman,
+ const GrDrawEffect& drawEffect) {
+ const Gr1DKernelEffect& kern = drawEffect.castEffect<Gr1DKernelEffect>();
+ GrTexture& texture = *kern.texture(0);
+ // the code we generated was for a specific kernel radius
+ GrAssert(kern.radius() == fRadius);
+ float imageIncrement[2] = { 0 };
+ switch (kern.direction()) {
+ case Gr1DKernelEffect::kX_Direction:
+ imageIncrement[0] = 1.0f / texture.width();
+ break;
+ case Gr1DKernelEffect::kY_Direction:
+ imageIncrement[1] = 1.0f / texture.height();
+ break;
+ default:
+ GrCrash("Unknown filter direction.");
+ }
+ uman.set2fv(fImageIncrementUni, 0, 1, imageIncrement);
+ fEffectMatrix.setData(uman, kern.getMatrix(), drawEffect, kern.texture(0));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrMorphologyEffect::GrMorphologyEffect(GrTexture* texture,
+ Direction direction,
+ int radius,
+ MorphologyType type)
+ : Gr1DKernelEffect(texture, direction, radius)
+ , fType(type) {
+}
+
+GrMorphologyEffect::~GrMorphologyEffect() {
+}
+
+const GrBackendEffectFactory& GrMorphologyEffect::getFactory() const {
+ return GrTBackendEffectFactory<GrMorphologyEffect>::getInstance();
+}
+
+bool GrMorphologyEffect::onIsEqual(const GrEffect& sBase) const {
+ const GrMorphologyEffect& s = CastEffect<GrMorphologyEffect>(sBase);
+ return (this->texture(0) == s.texture(0) &&
+ this->radius() == s.radius() &&
+ this->direction() == s.direction() &&
+ this->type() == s.type());
+}
+
+void GrMorphologyEffect::getConstantColorComponents(GrColor* color, uint32_t* validFlags) const {
+ // This is valid because the color components of the result of the kernel all come
+ // exactly from existing values in the source texture.
+ this->updateConstantColorComponentsForModulation(color, validFlags);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_EFFECT_TEST(GrMorphologyEffect);
+
+GrEffectRef* GrMorphologyEffect::TestCreate(SkMWCRandom* random,
+ GrContext*,
+ const GrDrawTargetCaps&,
+ GrTexture* textures[]) {
+ int texIdx = random->nextBool() ? GrEffectUnitTest::kSkiaPMTextureIdx :
+ GrEffectUnitTest::kAlphaTextureIdx;
+ Direction dir = random->nextBool() ? kX_Direction : kY_Direction;
+ static const int kMaxRadius = 10;
+ int radius = random->nextRangeU(1, kMaxRadius);
+ MorphologyType type = random->nextBool() ? GrMorphologyEffect::kErode_MorphologyType :
+ GrMorphologyEffect::kDilate_MorphologyType;
+
+ return GrMorphologyEffect::Create(textures[texIdx], dir, radius, type);
+}
+
+namespace {
+
+void apply_morphology_pass(GrContext* context,
+ GrTexture* texture,
+ const SkIRect& rect,
+ int radius,
+ GrMorphologyEffect::MorphologyType morphType,
+ Gr1DKernelEffect::Direction direction) {
+ GrPaint paint;
+ paint.addColorEffect(GrMorphologyEffect::Create(texture,
+ direction,
+ radius,
+ morphType))->unref();
+ context->drawRect(paint, SkRect::MakeFromIRect(rect));
+}
+
+GrTexture* apply_morphology(GrTexture* srcTexture,
+ const SkIRect& rect,
+ GrMorphologyEffect::MorphologyType morphType,
+ SkISize radius) {
+ GrContext* context = srcTexture->getContext();
+ srcTexture->ref();
+
+ GrContext::AutoMatrix am;
+ am.setIdentity(context);
+
+ GrContext::AutoClip acs(context, SkRect::MakeWH(SkIntToScalar(srcTexture->width()),
+ SkIntToScalar(srcTexture->height())));
+
+ GrTextureDesc desc;
+ desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
+ desc.fWidth = rect.width();
+ desc.fHeight = rect.height();
+ desc.fConfig = kSkia8888_GrPixelConfig;
+
+ if (radius.fWidth > 0) {
+ GrAutoScratchTexture ast(context, desc);
+ GrContext::AutoRenderTarget art(context, ast.texture()->asRenderTarget());
+ apply_morphology_pass(context, srcTexture, rect, radius.fWidth,
+ morphType, Gr1DKernelEffect::kX_Direction);
+ SkIRect clearRect = SkIRect::MakeXYWH(rect.fLeft, rect.fBottom,
+ rect.width(), radius.fHeight);
+ context->clear(&clearRect, 0x0);
+ srcTexture->unref();
+ srcTexture = ast.detach();
+ }
+ if (radius.fHeight > 0) {
+ GrAutoScratchTexture ast(context, desc);
+ GrContext::AutoRenderTarget art(context, ast.texture()->asRenderTarget());
+ apply_morphology_pass(context, srcTexture, rect, radius.fHeight,
+ morphType, Gr1DKernelEffect::kY_Direction);
+ srcTexture->unref();
+ srcTexture = ast.detach();
+ }
+ return srcTexture;
+}
+
+};
+
+bool SkDilateImageFilter::filterImageGPU(Proxy* proxy, const SkBitmap& src, const SkMatrix& ctm,
+ SkBitmap* result, SkIPoint* offset) {
+ SkBitmap inputBM;
+ if (!SkImageFilterUtils::GetInputResultGPU(getInput(0), proxy, src, ctm, &inputBM, offset)) {
+ return false;
+ }
+ GrTexture* input = inputBM.getTexture();
+ SkIRect bounds;
+ src.getBounds(&bounds);
+ SkAutoTUnref<GrTexture> resultTex(apply_morphology(input, bounds,
+ GrMorphologyEffect::kDilate_MorphologyType, radius()));
+ return SkImageFilterUtils::WrapTexture(resultTex, src.width(), src.height(), result);
+}
+
+bool SkErodeImageFilter::filterImageGPU(Proxy* proxy, const SkBitmap& src, const SkMatrix& ctm,
+ SkBitmap* result, SkIPoint* offset) {
+ SkBitmap inputBM;
+ if (!SkImageFilterUtils::GetInputResultGPU(getInput(0), proxy, src, ctm, &inputBM, offset)) {
+ return false;
+ }
+ GrTexture* input = inputBM.getTexture();
+ SkIRect bounds;
+ src.getBounds(&bounds);
+ SkAutoTUnref<GrTexture> resultTex(apply_morphology(input, bounds,
+ GrMorphologyEffect::kErode_MorphologyType, radius()));
+ return SkImageFilterUtils::WrapTexture(resultTex, src.width(), src.height(), result);
+}
+
+#endif
diff --git a/effects/SkOffsetImageFilter.cpp b/effects/SkOffsetImageFilter.cpp
new file mode 100644
index 00000000..ad5e49d7
--- /dev/null
+++ b/effects/SkOffsetImageFilter.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkOffsetImageFilter.h"
+#include "SkBitmap.h"
+#include "SkMatrix.h"
+#include "SkFlattenableBuffers.h"
+
+bool SkOffsetImageFilter::onFilterImage(Proxy* proxy, const SkBitmap& source,
+ const SkMatrix& matrix,
+ SkBitmap* result,
+ SkIPoint* loc) {
+ SkBitmap src = source;
+ if (getInput(0) && !getInput(0)->filterImage(proxy, source, matrix, &src, loc)) {
+ return false;
+ }
+
+ SkVector vec;
+ matrix.mapVectors(&vec, &fOffset, 1);
+
+ loc->fX += SkScalarRoundToInt(vec.fX);
+ loc->fY += SkScalarRoundToInt(vec.fY);
+ *result = src;
+ return true;
+}
+
+bool SkOffsetImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
+ SkIRect* dst) {
+ SkVector vec;
+ ctm.mapVectors(&vec, &fOffset, 1);
+
+ *dst = src;
+ dst->offset(SkScalarRoundToInt(vec.fX), SkScalarRoundToInt(vec.fY));
+ return true;
+}
+
+void SkOffsetImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writePoint(fOffset);
+}
+
+SkOffsetImageFilter::SkOffsetImageFilter(SkScalar dx, SkScalar dy,
+ SkImageFilter* input) : INHERITED(input) {
+ fOffset.set(dx, dy);
+}
+
+SkOffsetImageFilter::SkOffsetImageFilter(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ buffer.readPoint(&fOffset);
+}
diff --git a/effects/SkPaintFlagsDrawFilter.cpp b/effects/SkPaintFlagsDrawFilter.cpp
new file mode 100644
index 00000000..dc1c0074
--- /dev/null
+++ b/effects/SkPaintFlagsDrawFilter.cpp
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkPaintFlagsDrawFilter.h"
+#include "SkPaint.h"
+
+SkPaintFlagsDrawFilter::SkPaintFlagsDrawFilter(uint32_t clearFlags,
+ uint32_t setFlags) {
+ fClearFlags = SkToU16(clearFlags & SkPaint::kAllFlags);
+ fSetFlags = SkToU16(setFlags & SkPaint::kAllFlags);
+}
+
+bool SkPaintFlagsDrawFilter::filter(SkPaint* paint, Type) {
+ paint->setFlags((paint->getFlags() & ~fClearFlags) | fSetFlags);
+ return true;
+}
diff --git a/effects/SkPerlinNoiseShader.cpp b/effects/SkPerlinNoiseShader.cpp
new file mode 100644
index 00000000..1d26920c
--- /dev/null
+++ b/effects/SkPerlinNoiseShader.cpp
@@ -0,0 +1,1378 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkDither.h"
+#include "SkPerlinNoiseShader.h"
+#include "SkFlattenableBuffers.h"
+#include "SkShader.h"
+#include "SkUnPreMultiply.h"
+#include "SkString.h"
+
+#if SK_SUPPORT_GPU
+#include "GrContext.h"
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
+#include "GrTBackendEffectFactory.h"
+#include "SkGr.h"
+#endif
+
+static const int kBlockSize = 256;
+static const int kBlockMask = kBlockSize - 1;
+static const int kPerlinNoise = 4096;
+static const int kRandMaximum = SK_MaxS32; // 2**31 - 1
+
+namespace {
+
+// noiseValue is the color component's value (or color)
+// limitValue is the maximum perlin noise array index value allowed
+// newValue is the current noise dimension (either width or height)
+inline int checkNoise(int noiseValue, int limitValue, int newValue) {
+ // If the noise value would bring us out of bounds of the current noise array while we are
+ // stiching noise tiles together, wrap the noise around the current dimension of the noise to
+ // stay within the array bounds in a continuous fashion (so that tiling lines are not visible)
+ if (noiseValue >= limitValue) {
+ noiseValue -= newValue;
+ }
+ if (noiseValue >= limitValue - 1) {
+ noiseValue -= newValue - 1;
+ }
+ return noiseValue;
+}
+
+inline SkScalar smoothCurve(SkScalar t) {
+ static const SkScalar SK_Scalar3 = SkFloatToScalar(3.0f);
+
+ // returns t * t * (3 - 2 * t)
+ return SkScalarMul(SkScalarSquare(t), SK_Scalar3 - 2 * t);
+}
+
+} // end namespace
+
+struct SkPerlinNoiseShader::StitchData {
+ StitchData()
+ : fWidth(0)
+ , fWrapX(0)
+ , fHeight(0)
+ , fWrapY(0)
+ {}
+
+ bool operator==(const StitchData& other) const {
+ return fWidth == other.fWidth &&
+ fWrapX == other.fWrapX &&
+ fHeight == other.fHeight &&
+ fWrapY == other.fWrapY;
+ }
+
+ int fWidth; // How much to subtract to wrap for stitching.
+ int fWrapX; // Minimum value to wrap.
+ int fHeight;
+ int fWrapY;
+};
+
+struct SkPerlinNoiseShader::PaintingData {
+ PaintingData(const SkISize& tileSize)
+ : fSeed(0)
+ , fTileSize(tileSize)
+ , fPermutationsBitmap(NULL)
+ , fNoiseBitmap(NULL)
+ {}
+
+ ~PaintingData()
+ {
+ SkDELETE(fPermutationsBitmap);
+ SkDELETE(fNoiseBitmap);
+ }
+
+ int fSeed;
+ uint8_t fLatticeSelector[kBlockSize];
+ uint16_t fNoise[4][kBlockSize][2];
+ SkPoint fGradient[4][kBlockSize];
+ SkISize fTileSize;
+ SkVector fBaseFrequency;
+ StitchData fStitchDataInit;
+
+private:
+
+ SkBitmap* fPermutationsBitmap;
+ SkBitmap* fNoiseBitmap;
+
+public:
+
+ inline int random() {
+ static const int gRandAmplitude = 16807; // 7**5; primitive root of m
+ static const int gRandQ = 127773; // m / a
+ static const int gRandR = 2836; // m % a
+
+ int result = gRandAmplitude * (fSeed % gRandQ) - gRandR * (fSeed / gRandQ);
+ if (result <= 0)
+ result += kRandMaximum;
+ fSeed = result;
+ return result;
+ }
+
+ void init(SkScalar seed)
+ {
+ static const SkScalar gInvBlockSizef = SkScalarInvert(SkIntToScalar(kBlockSize));
+
+ // The seed value clamp to the range [1, kRandMaximum - 1].
+ fSeed = SkScalarRoundToInt(seed);
+ if (fSeed <= 0) {
+ fSeed = -(fSeed % (kRandMaximum - 1)) + 1;
+ }
+ if (fSeed > kRandMaximum - 1) {
+ fSeed = kRandMaximum - 1;
+ }
+ for (int channel = 0; channel < 4; ++channel) {
+ for (int i = 0; i < kBlockSize; ++i) {
+ fLatticeSelector[i] = i;
+ fNoise[channel][i][0] = (random() % (2 * kBlockSize));
+ fNoise[channel][i][1] = (random() % (2 * kBlockSize));
+ }
+ }
+ for (int i = kBlockSize - 1; i > 0; --i) {
+ int k = fLatticeSelector[i];
+ int j = random() % kBlockSize;
+ SkASSERT(j >= 0);
+ SkASSERT(j < kBlockSize);
+ fLatticeSelector[i] = fLatticeSelector[j];
+ fLatticeSelector[j] = k;
+ }
+
+ // Perform the permutations now
+ {
+ // Copy noise data
+ uint16_t noise[4][kBlockSize][2];
+ for (int i = 0; i < kBlockSize; ++i) {
+ for (int channel = 0; channel < 4; ++channel) {
+ for (int j = 0; j < 2; ++j) {
+ noise[channel][i][j] = fNoise[channel][i][j];
+ }
+ }
+ }
+ // Do permutations on noise data
+ for (int i = 0; i < kBlockSize; ++i) {
+ for (int channel = 0; channel < 4; ++channel) {
+ for (int j = 0; j < 2; ++j) {
+ fNoise[channel][i][j] = noise[channel][fLatticeSelector[i]][j];
+ }
+ }
+ }
+ }
+
+ // Half of the largest possible value for 16 bit unsigned int
+ static const SkScalar gHalfMax16bits = SkFloatToScalar(32767.5f);
+
+ // Compute gradients from permutated noise data
+ for (int channel = 0; channel < 4; ++channel) {
+ for (int i = 0; i < kBlockSize; ++i) {
+ fGradient[channel][i] = SkPoint::Make(
+ SkScalarMul(SkIntToScalar(fNoise[channel][i][0] - kBlockSize),
+ gInvBlockSizef),
+ SkScalarMul(SkIntToScalar(fNoise[channel][i][1] - kBlockSize),
+ gInvBlockSizef));
+ fGradient[channel][i].normalize();
+ // Put the normalized gradient back into the noise data
+ fNoise[channel][i][0] = SkScalarRoundToInt(SkScalarMul(
+ fGradient[channel][i].fX + SK_Scalar1, gHalfMax16bits));
+ fNoise[channel][i][1] = SkScalarRoundToInt(SkScalarMul(
+ fGradient[channel][i].fY + SK_Scalar1, gHalfMax16bits));
+ }
+ }
+
+ // Invalidate bitmaps
+ SkDELETE(fPermutationsBitmap);
+ fPermutationsBitmap = NULL;
+ SkDELETE(fNoiseBitmap);
+ fNoiseBitmap = NULL;
+ }
+
+ void stitch() {
+ SkScalar tileWidth = SkIntToScalar(fTileSize.width());
+ SkScalar tileHeight = SkIntToScalar(fTileSize.height());
+ SkASSERT(tileWidth > 0 && tileHeight > 0);
+ // When stitching tiled turbulence, the frequencies must be adjusted
+ // so that the tile borders will be continuous.
+ if (fBaseFrequency.fX) {
+ SkScalar lowFrequencx = SkScalarDiv(
+ SkScalarMulFloor(tileWidth, fBaseFrequency.fX), tileWidth);
+ SkScalar highFrequencx = SkScalarDiv(
+ SkScalarMulCeil(tileWidth, fBaseFrequency.fX), tileWidth);
+ // BaseFrequency should be non-negative according to the standard.
+ if (SkScalarDiv(fBaseFrequency.fX, lowFrequencx) <
+ SkScalarDiv(highFrequencx, fBaseFrequency.fX)) {
+ fBaseFrequency.fX = lowFrequencx;
+ } else {
+ fBaseFrequency.fX = highFrequencx;
+ }
+ }
+ if (fBaseFrequency.fY) {
+ SkScalar lowFrequency = SkScalarDiv(
+ SkScalarMulFloor(tileHeight, fBaseFrequency.fY), tileHeight);
+ SkScalar highFrequency = SkScalarDiv(
+ SkScalarMulCeil(tileHeight, fBaseFrequency.fY), tileHeight);
+ if (SkScalarDiv(fBaseFrequency.fY, lowFrequency) <
+ SkScalarDiv(highFrequency, fBaseFrequency.fY)) {
+ fBaseFrequency.fY = lowFrequency;
+ } else {
+ fBaseFrequency.fY = highFrequency;
+ }
+ }
+ // Set up TurbulenceInitial stitch values.
+ fStitchDataInit.fWidth =
+ SkScalarMulRound(tileWidth, fBaseFrequency.fX);
+ fStitchDataInit.fWrapX = kPerlinNoise + fStitchDataInit.fWidth;
+ fStitchDataInit.fHeight =
+ SkScalarMulRound(tileHeight, fBaseFrequency.fY);
+ fStitchDataInit.fWrapY = kPerlinNoise + fStitchDataInit.fHeight;
+ }
+
+ SkBitmap* getPermutationsBitmap()
+ {
+ if (!fPermutationsBitmap) {
+ fPermutationsBitmap = SkNEW(SkBitmap);
+ fPermutationsBitmap->setConfig(SkBitmap::kA8_Config, kBlockSize, 1);
+ fPermutationsBitmap->allocPixels();
+ uint8_t* bitmapPixels = fPermutationsBitmap->getAddr8(0, 0);
+ memcpy(bitmapPixels, fLatticeSelector, sizeof(uint8_t) * kBlockSize);
+ }
+ return fPermutationsBitmap;
+ }
+
+ SkBitmap* getNoiseBitmap()
+ {
+ if (!fNoiseBitmap) {
+ fNoiseBitmap = SkNEW(SkBitmap);
+ fNoiseBitmap->setConfig(SkBitmap::kARGB_8888_Config, kBlockSize, 4);
+ fNoiseBitmap->allocPixels();
+ uint32_t* bitmapPixels = fNoiseBitmap->getAddr32(0, 0);
+ memcpy(bitmapPixels, fNoise[0][0], sizeof(uint16_t) * kBlockSize * 4 * 2);
+ }
+ return fNoiseBitmap;
+ }
+};
+
+SkShader* SkPerlinNoiseShader::CreateFractalNoise(SkScalar baseFrequencyX, SkScalar baseFrequencyY,
+ int numOctaves, SkScalar seed,
+ const SkISize* tileSize) {
+ return SkNEW_ARGS(SkPerlinNoiseShader, (kFractalNoise_Type, baseFrequencyX, baseFrequencyY,
+ numOctaves, seed, tileSize));
+}
+
+SkShader* SkPerlinNoiseShader::CreateTubulence(SkScalar baseFrequencyX, SkScalar baseFrequencyY,
+ int numOctaves, SkScalar seed,
+ const SkISize* tileSize) {
+ return SkNEW_ARGS(SkPerlinNoiseShader, (kTurbulence_Type, baseFrequencyX, baseFrequencyY,
+ numOctaves, seed, tileSize));
+}
+
+SkPerlinNoiseShader::SkPerlinNoiseShader(SkPerlinNoiseShader::Type type,
+ SkScalar baseFrequencyX,
+ SkScalar baseFrequencyY,
+ int numOctaves,
+ SkScalar seed,
+ const SkISize* tileSize)
+ : fType(type)
+ , fBaseFrequencyX(baseFrequencyX)
+ , fBaseFrequencyY(baseFrequencyY)
+ , fNumOctaves(numOctaves & 0xFF /*[0,255] octaves allowed*/)
+ , fSeed(seed)
+ , fStitchTiles((tileSize != NULL) && !tileSize->isEmpty())
+ , fPaintingData(NULL)
+{
+ SkASSERT(numOctaves >= 0 && numOctaves < 256);
+ setTileSize(fStitchTiles ? *tileSize : SkISize::Make(0,0));
+ fMatrix.reset();
+}
+
+SkPerlinNoiseShader::SkPerlinNoiseShader(SkFlattenableReadBuffer& buffer) :
+ INHERITED(buffer), fPaintingData(NULL) {
+ fType = (SkPerlinNoiseShader::Type) buffer.readInt();
+ fBaseFrequencyX = buffer.readScalar();
+ fBaseFrequencyY = buffer.readScalar();
+ fNumOctaves = buffer.readInt();
+ fSeed = buffer.readScalar();
+ fStitchTiles = buffer.readBool();
+ fTileSize.fWidth = buffer.readInt();
+ fTileSize.fHeight = buffer.readInt();
+ setTileSize(fTileSize);
+ fMatrix.reset();
+}
+
+SkPerlinNoiseShader::~SkPerlinNoiseShader() {
+ // Safety, should have been done in endContext()
+ SkDELETE(fPaintingData);
+}
+
+void SkPerlinNoiseShader::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeInt((int) fType);
+ buffer.writeScalar(fBaseFrequencyX);
+ buffer.writeScalar(fBaseFrequencyY);
+ buffer.writeInt(fNumOctaves);
+ buffer.writeScalar(fSeed);
+ buffer.writeBool(fStitchTiles);
+ buffer.writeInt(fTileSize.fWidth);
+ buffer.writeInt(fTileSize.fHeight);
+}
+
+void SkPerlinNoiseShader::initPaint(PaintingData& paintingData)
+{
+ paintingData.init(fSeed);
+
+ // Set frequencies to original values
+ paintingData.fBaseFrequency.set(fBaseFrequencyX, fBaseFrequencyY);
+ // Adjust frequecies based on size if stitching is enabled
+ if (fStitchTiles) {
+ paintingData.stitch();
+ }
+}
+
+void SkPerlinNoiseShader::setTileSize(const SkISize& tileSize) {
+ fTileSize = tileSize;
+
+ if (NULL == fPaintingData) {
+ fPaintingData = SkNEW_ARGS(PaintingData, (fTileSize));
+ initPaint(*fPaintingData);
+ } else {
+ // Set Size
+ fPaintingData->fTileSize = fTileSize;
+ // Set frequencies to original values
+ fPaintingData->fBaseFrequency.set(fBaseFrequencyX, fBaseFrequencyY);
+ // Adjust frequecies based on size if stitching is enabled
+ if (fStitchTiles) {
+ fPaintingData->stitch();
+ }
+ }
+}
+
+SkScalar SkPerlinNoiseShader::noise2D(int channel, const PaintingData& paintingData,
+ const StitchData& stitchData, const SkPoint& noiseVector)
+{
+ struct Noise {
+ int noisePositionIntegerValue;
+ SkScalar noisePositionFractionValue;
+ Noise(SkScalar component)
+ {
+ SkScalar position = component + kPerlinNoise;
+ noisePositionIntegerValue = SkScalarFloorToInt(position);
+ noisePositionFractionValue = position - SkIntToScalar(noisePositionIntegerValue);
+ }
+ };
+ Noise noiseX(noiseVector.x());
+ Noise noiseY(noiseVector.y());
+ SkScalar u, v;
+ // If stitching, adjust lattice points accordingly.
+ if (fStitchTiles) {
+ noiseX.noisePositionIntegerValue =
+ checkNoise(noiseX.noisePositionIntegerValue, stitchData.fWrapX, stitchData.fWidth);
+ noiseY.noisePositionIntegerValue =
+ checkNoise(noiseY.noisePositionIntegerValue, stitchData.fWrapY, stitchData.fHeight);
+ }
+ noiseX.noisePositionIntegerValue &= kBlockMask;
+ noiseY.noisePositionIntegerValue &= kBlockMask;
+ int latticeIndex =
+ paintingData.fLatticeSelector[noiseX.noisePositionIntegerValue] +
+ noiseY.noisePositionIntegerValue;
+ int nextLatticeIndex =
+ paintingData.fLatticeSelector[(noiseX.noisePositionIntegerValue + 1) & kBlockMask] +
+ noiseY.noisePositionIntegerValue;
+ SkScalar sx = smoothCurve(noiseX.noisePositionFractionValue);
+ SkScalar sy = smoothCurve(noiseY.noisePositionFractionValue);
+ // This is taken 1:1 from SVG spec: http://www.w3.org/TR/SVG11/filters.html#feTurbulenceElement
+ SkPoint fractionValue = SkPoint::Make(noiseX.noisePositionFractionValue,
+ noiseY.noisePositionFractionValue); // Offset (0,0)
+ u = paintingData.fGradient[channel][latticeIndex & kBlockMask].dot(fractionValue);
+ fractionValue.fX -= SK_Scalar1; // Offset (-1,0)
+ v = paintingData.fGradient[channel][nextLatticeIndex & kBlockMask].dot(fractionValue);
+ SkScalar a = SkScalarInterp(u, v, sx);
+ fractionValue.fY -= SK_Scalar1; // Offset (-1,-1)
+ v = paintingData.fGradient[channel][(nextLatticeIndex + 1) & kBlockMask].dot(fractionValue);
+ fractionValue.fX = noiseX.noisePositionFractionValue; // Offset (0,-1)
+ u = paintingData.fGradient[channel][(latticeIndex + 1) & kBlockMask].dot(fractionValue);
+ SkScalar b = SkScalarInterp(u, v, sx);
+ return SkScalarInterp(a, b, sy);
+}
+
+SkScalar SkPerlinNoiseShader::calculateTurbulenceValueForPoint(
+ int channel, const PaintingData& paintingData, StitchData& stitchData, const SkPoint& point)
+{
+ if (fStitchTiles) {
+ // Set up TurbulenceInitial stitch values.
+ stitchData = paintingData.fStitchDataInit;
+ }
+ SkScalar turbulenceFunctionResult = 0;
+ SkPoint noiseVector(SkPoint::Make(SkScalarMul(point.x(), paintingData.fBaseFrequency.fX),
+ SkScalarMul(point.y(), paintingData.fBaseFrequency.fY)));
+ SkScalar ratio = SK_Scalar1;
+ for (int octave = 0; octave < fNumOctaves; ++octave) {
+ SkScalar noise = noise2D(channel, paintingData, stitchData, noiseVector);
+ turbulenceFunctionResult += SkScalarDiv(
+ (fType == kFractalNoise_Type) ? noise : SkScalarAbs(noise), ratio);
+ noiseVector.fX *= 2;
+ noiseVector.fY *= 2;
+ ratio *= 2;
+ if (fStitchTiles) {
+ // Update stitch values
+ stitchData.fWidth *= 2;
+ stitchData.fWrapX = stitchData.fWidth + kPerlinNoise;
+ stitchData.fHeight *= 2;
+ stitchData.fWrapY = stitchData.fHeight + kPerlinNoise;
+ }
+ }
+
+ // The value of turbulenceFunctionResult comes from ((turbulenceFunctionResult) + 1) / 2
+ // by fractalNoise and (turbulenceFunctionResult) by turbulence.
+ if (fType == kFractalNoise_Type) {
+ turbulenceFunctionResult =
+ SkScalarMul(turbulenceFunctionResult, SK_ScalarHalf) + SK_ScalarHalf;
+ }
+
+ if (channel == 3) { // Scale alpha by paint value
+ turbulenceFunctionResult = SkScalarMul(turbulenceFunctionResult,
+ SkScalarDiv(SkIntToScalar(getPaintAlpha()), SkIntToScalar(255)));
+ }
+
+ // Clamp result
+ return SkScalarPin(turbulenceFunctionResult, 0, SK_Scalar1);
+}
+
+SkPMColor SkPerlinNoiseShader::shade(const SkPoint& point, StitchData& stitchData) {
+ SkMatrix matrix = fMatrix;
+ SkMatrix invMatrix;
+ if (!matrix.invert(&invMatrix)) {
+ invMatrix.reset();
+ } else {
+ invMatrix.postConcat(invMatrix); // Square the matrix
+ }
+ // This (1,1) translation is due to WebKit's 1 based coordinates for the noise
+ // (as opposed to 0 based, usually). The same adjustment is in the setData() function.
+ matrix.postTranslate(SK_Scalar1, SK_Scalar1);
+ SkPoint newPoint;
+ matrix.mapPoints(&newPoint, &point, 1);
+ invMatrix.mapPoints(&newPoint, &newPoint, 1);
+ newPoint.fX = SkScalarRoundToScalar(newPoint.fX);
+ newPoint.fY = SkScalarRoundToScalar(newPoint.fY);
+
+ U8CPU rgba[4];
+ for (int channel = 3; channel >= 0; --channel) {
+ rgba[channel] = SkScalarFloorToInt(255 *
+ calculateTurbulenceValueForPoint(channel, *fPaintingData, stitchData, newPoint));
+ }
+ return SkPreMultiplyARGB(rgba[3], rgba[0], rgba[1], rgba[2]);
+}
+
+bool SkPerlinNoiseShader::setContext(const SkBitmap& device, const SkPaint& paint,
+ const SkMatrix& matrix) {
+ fMatrix = matrix;
+ return INHERITED::setContext(device, paint, matrix);
+}
+
+void SkPerlinNoiseShader::shadeSpan(int x, int y, SkPMColor result[], int count) {
+ SkPoint point = SkPoint::Make(SkIntToScalar(x), SkIntToScalar(y));
+ StitchData stitchData;
+ for (int i = 0; i < count; ++i) {
+ result[i] = shade(point, stitchData);
+ point.fX += SK_Scalar1;
+ }
+}
+
+void SkPerlinNoiseShader::shadeSpan16(int x, int y, uint16_t result[], int count) {
+ SkPoint point = SkPoint::Make(SkIntToScalar(x), SkIntToScalar(y));
+ StitchData stitchData;
+ DITHER_565_SCAN(y);
+ for (int i = 0; i < count; ++i) {
+ unsigned dither = DITHER_VALUE(x);
+ result[i] = SkDitherRGB32To565(shade(point, stitchData), dither);
+ DITHER_INC_X(x);
+ point.fX += SK_Scalar1;
+ }
+}
+
+/////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+
+#include "GrTBackendEffectFactory.h"
+
+class GrGLNoise : public GrGLEffect {
+public:
+ GrGLNoise(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect);
+ virtual ~GrGLNoise() {}
+
+ static inline EffectKey GenKey(const GrDrawEffect&, const GrGLCaps&);
+
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+protected:
+ SkPerlinNoiseShader::Type fType;
+ bool fStitchTiles;
+ int fNumOctaves;
+ GrGLUniformManager::UniformHandle fBaseFrequencyUni;
+ GrGLUniformManager::UniformHandle fAlphaUni;
+ GrGLUniformManager::UniformHandle fInvMatrixUni;
+ GrGLEffectMatrix fEffectMatrix;
+
+private:
+ typedef GrGLEffect INHERITED;
+};
+
+class GrGLPerlinNoise : public GrGLNoise {
+public:
+ GrGLPerlinNoise(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect)
+ : GrGLNoise(factory, drawEffect) {}
+ virtual ~GrGLPerlinNoise() {}
+
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+private:
+ GrGLUniformManager::UniformHandle fStitchDataUni;
+
+ typedef GrGLNoise INHERITED;
+};
+
+class GrGLSimplexNoise : public GrGLNoise {
+ // Note : This is for reference only. GrGLPerlinNoise is used for processing.
+public:
+ GrGLSimplexNoise(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect)
+ : GrGLNoise(factory, drawEffect) {}
+
+ virtual ~GrGLSimplexNoise() {}
+
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+private:
+ GrGLUniformManager::UniformHandle fSeedUni;
+
+ typedef GrGLNoise INHERITED;
+};
+
+/////////////////////////////////////////////////////////////////////
+
+class GrNoiseEffect : public GrEffect {
+public:
+ virtual ~GrNoiseEffect() { }
+
+ SkPerlinNoiseShader::Type type() const { return fType; }
+ bool stitchTiles() const { return fStitchTiles; }
+ const SkVector& baseFrequency() const { return fBaseFrequency; }
+ int numOctaves() const { return fNumOctaves; }
+ const SkMatrix& matrix() const { return fMatrix; }
+ uint8_t alpha() const { return fAlpha; }
+ GrGLEffectMatrix::CoordsType coordsType() const { return GrEffect::kLocal_CoordsType; }
+
+ void getConstantColorComponents(GrColor*, uint32_t* validFlags) const SK_OVERRIDE {
+ *validFlags = 0; // This is noise. Nothing is constant.
+ }
+
+protected:
+ virtual bool onIsEqual(const GrEffect& sBase) const SK_OVERRIDE {
+ const GrNoiseEffect& s = CastEffect<GrNoiseEffect>(sBase);
+ return fType == s.fType &&
+ fBaseFrequency == s.fBaseFrequency &&
+ fNumOctaves == s.fNumOctaves &&
+ fStitchTiles == s.fStitchTiles &&
+ fMatrix == s.fMatrix &&
+ fAlpha == s.fAlpha;
+ }
+
+ GrNoiseEffect(SkPerlinNoiseShader::Type type, const SkVector& baseFrequency, int numOctaves,
+ bool stitchTiles, const SkMatrix& matrix, uint8_t alpha)
+ : fType(type)
+ , fBaseFrequency(baseFrequency)
+ , fNumOctaves(numOctaves)
+ , fStitchTiles(stitchTiles)
+ , fMatrix(matrix)
+ , fAlpha(alpha) {
+ }
+
+ SkPerlinNoiseShader::Type fType;
+ SkVector fBaseFrequency;
+ int fNumOctaves;
+ bool fStitchTiles;
+ SkMatrix fMatrix;
+ uint8_t fAlpha;
+
+private:
+ typedef GrEffect INHERITED;
+};
+
+class GrPerlinNoiseEffect : public GrNoiseEffect {
+public:
+ static GrEffectRef* Create(SkPerlinNoiseShader::Type type, const SkVector& baseFrequency,
+ int numOctaves, bool stitchTiles,
+ const SkPerlinNoiseShader::StitchData& stitchData,
+ GrTexture* permutationsTexture, GrTexture* noiseTexture,
+ const SkMatrix& matrix, uint8_t alpha) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrPerlinNoiseEffect, (type, baseFrequency, numOctaves,
+ stitchTiles, stitchData, permutationsTexture, noiseTexture, matrix, alpha)));
+ return CreateEffectRef(effect);
+ }
+
+ virtual ~GrPerlinNoiseEffect() { }
+
+ static const char* Name() { return "PerlinNoise"; }
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<GrPerlinNoiseEffect>::getInstance();
+ }
+ const SkPerlinNoiseShader::StitchData& stitchData() const { return fStitchData; }
+
+ typedef GrGLPerlinNoise GLEffect;
+
+private:
+ virtual bool onIsEqual(const GrEffect& sBase) const SK_OVERRIDE {
+ const GrPerlinNoiseEffect& s = CastEffect<GrPerlinNoiseEffect>(sBase);
+ return INHERITED::onIsEqual(sBase) &&
+ fPermutationsAccess.getTexture() == s.fPermutationsAccess.getTexture() &&
+ fNoiseAccess.getTexture() == s.fNoiseAccess.getTexture() &&
+ fStitchData == s.fStitchData;
+ }
+
+ GrPerlinNoiseEffect(SkPerlinNoiseShader::Type type, const SkVector& baseFrequency,
+ int numOctaves, bool stitchTiles,
+ const SkPerlinNoiseShader::StitchData& stitchData,
+ GrTexture* permutationsTexture, GrTexture* noiseTexture,
+ const SkMatrix& matrix, uint8_t alpha)
+ : GrNoiseEffect(type, baseFrequency, numOctaves, stitchTiles, matrix, alpha)
+ , fPermutationsAccess(permutationsTexture)
+ , fNoiseAccess(noiseTexture)
+ , fStitchData(stitchData) {
+ this->addTextureAccess(&fPermutationsAccess);
+ this->addTextureAccess(&fNoiseAccess);
+ }
+
+ GR_DECLARE_EFFECT_TEST;
+
+ GrTextureAccess fPermutationsAccess;
+ GrTextureAccess fNoiseAccess;
+ SkPerlinNoiseShader::StitchData fStitchData;
+
+ typedef GrNoiseEffect INHERITED;
+};
+
+class GrSimplexNoiseEffect : public GrNoiseEffect {
+ // Note : This is for reference only. GrPerlinNoiseEffect is used for processing.
+public:
+ static GrEffectRef* Create(SkPerlinNoiseShader::Type type, const SkVector& baseFrequency,
+ int numOctaves, bool stitchTiles, const SkScalar seed,
+ const SkMatrix& matrix, uint8_t alpha) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrSimplexNoiseEffect, (type, baseFrequency, numOctaves,
+ stitchTiles, seed, matrix, alpha)));
+ return CreateEffectRef(effect);
+ }
+
+ virtual ~GrSimplexNoiseEffect() { }
+
+ static const char* Name() { return "SimplexNoise"; }
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<GrSimplexNoiseEffect>::getInstance();
+ }
+ const SkScalar& seed() const { return fSeed; }
+
+ typedef GrGLSimplexNoise GLEffect;
+
+private:
+ virtual bool onIsEqual(const GrEffect& sBase) const SK_OVERRIDE {
+ const GrSimplexNoiseEffect& s = CastEffect<GrSimplexNoiseEffect>(sBase);
+ return INHERITED::onIsEqual(sBase) && fSeed == s.fSeed;
+ }
+
+ GrSimplexNoiseEffect(SkPerlinNoiseShader::Type type, const SkVector& baseFrequency,
+ int numOctaves, bool stitchTiles, const SkScalar seed,
+ const SkMatrix& matrix, uint8_t alpha)
+ : GrNoiseEffect(type, baseFrequency, numOctaves, stitchTiles, matrix, alpha)
+ , fSeed(seed) {
+ }
+
+ SkScalar fSeed;
+
+ typedef GrNoiseEffect INHERITED;
+};
+
+/////////////////////////////////////////////////////////////////////
+GR_DEFINE_EFFECT_TEST(GrPerlinNoiseEffect);
+
+GrEffectRef* GrPerlinNoiseEffect::TestCreate(SkMWCRandom* random,
+ GrContext* context,
+ const GrDrawTargetCaps&,
+ GrTexture**) {
+ int numOctaves = random->nextRangeU(2, 10);
+ bool stitchTiles = random->nextBool();
+ SkScalar seed = SkIntToScalar(random->nextU());
+ SkISize tileSize = SkISize::Make(random->nextRangeU(4, 4096), random->nextRangeU(4, 4096));
+ SkScalar baseFrequencyX = random->nextRangeScalar(SkFloatToScalar(0.01f),
+ SkFloatToScalar(0.99f));
+ SkScalar baseFrequencyY = random->nextRangeScalar(SkFloatToScalar(0.01f),
+ SkFloatToScalar(0.99f));
+
+ SkShader* shader = random->nextBool() ?
+ SkPerlinNoiseShader::CreateFractalNoise(baseFrequencyX, baseFrequencyY, numOctaves, seed,
+ stitchTiles ? &tileSize : NULL) :
+ SkPerlinNoiseShader::CreateTubulence(baseFrequencyX, baseFrequencyY, numOctaves, seed,
+ stitchTiles ? &tileSize : NULL);
+
+ SkPaint paint;
+ GrEffectRef* effect = shader->asNewEffect(context, paint);
+
+ SkDELETE(shader);
+
+ return effect;
+}
+
+/////////////////////////////////////////////////////////////////////
+
+void GrGLSimplexNoise::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect&,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) {
+ sk_ignore_unused_variable(inputColor);
+
+ const char* vCoords;
+ fEffectMatrix.emitCodeMakeFSCoords2D(builder, key, &vCoords);
+
+ fSeedUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "seed");
+ const char* seedUni = builder->getUniformCStr(fSeedUni);
+ fInvMatrixUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kMat33f_GrSLType, "invMatrix");
+ const char* invMatrixUni = builder->getUniformCStr(fInvMatrixUni);
+ fBaseFrequencyUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec2f_GrSLType, "baseFrequency");
+ const char* baseFrequencyUni = builder->getUniformCStr(fBaseFrequencyUni);
+ fAlphaUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "alpha");
+ const char* alphaUni = builder->getUniformCStr(fAlphaUni);
+
+ // Add vec3 modulo 289 function
+ static const GrGLShaderVar gVec3Args[] = {
+ GrGLShaderVar("x", kVec3f_GrSLType)
+ };
+
+ SkString mod289_3_funcName;
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType, kVec3f_GrSLType,
+ "mod289", SK_ARRAY_COUNT(gVec3Args), gVec3Args,
+ "const vec2 C = vec2(1.0 / 289.0, 289.0);\n"
+ "return x - floor(x * C.xxx) * C.yyy;", &mod289_3_funcName);
+
+ // Add vec4 modulo 289 function
+ static const GrGLShaderVar gVec4Args[] = {
+ GrGLShaderVar("x", kVec4f_GrSLType)
+ };
+
+ SkString mod289_4_funcName;
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType, kVec4f_GrSLType,
+ "mod289", SK_ARRAY_COUNT(gVec4Args), gVec4Args,
+ "const vec2 C = vec2(1.0 / 289.0, 289.0);\n"
+ "return x - floor(x * C.xxxx) * C.yyyy;", &mod289_4_funcName);
+
+ // Add vec4 permute function
+ SkString permuteCode;
+ permuteCode.appendf("const vec2 C = vec2(34.0, 1.0);\n"
+ "return %s(((x * C.xxxx) + C.yyyy) * x);", mod289_4_funcName.c_str());
+ SkString permuteFuncName;
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType, kVec4f_GrSLType,
+ "permute", SK_ARRAY_COUNT(gVec4Args), gVec4Args,
+ permuteCode.c_str(), &permuteFuncName);
+
+ // Add vec4 taylorInvSqrt function
+ SkString taylorInvSqrtFuncName;
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType, kVec4f_GrSLType,
+ "taylorInvSqrt", SK_ARRAY_COUNT(gVec4Args), gVec4Args,
+ "const vec2 C = vec2(-0.85373472095314, 1.79284291400159);\n"
+ "return x * C.xxxx + C.yyyy;", &taylorInvSqrtFuncName);
+
+ // Add vec3 noise function
+ static const GrGLShaderVar gNoiseVec3Args[] = {
+ GrGLShaderVar("v", kVec3f_GrSLType)
+ };
+
+ SkString noiseCode;
+ noiseCode.append(
+ "const vec2 C = vec2(1.0/6.0, 1.0/3.0);\n"
+ "const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);\n"
+
+ // First corner
+ "vec3 i = floor(v + dot(v, C.yyy));\n"
+ "vec3 x0 = v - i + dot(i, C.xxx);\n"
+
+ // Other corners
+ "vec3 g = step(x0.yzx, x0.xyz);\n"
+ "vec3 l = 1.0 - g;\n"
+ "vec3 i1 = min(g.xyz, l.zxy);\n"
+ "vec3 i2 = max(g.xyz, l.zxy);\n"
+
+ "vec3 x1 = x0 - i1 + C.xxx;\n"
+ "vec3 x2 = x0 - i2 + C.yyy;\n" // 2.0*C.x = 1/3 = C.y
+ "vec3 x3 = x0 - D.yyy;\n" // -1.0+3.0*C.x = -0.5 = -D.y
+ );
+
+ noiseCode.appendf(
+ // Permutations
+ "i = %s(i);\n"
+ "vec4 p = %s(%s(%s(\n"
+ " i.z + vec4(0.0, i1.z, i2.z, 1.0)) +\n"
+ " i.y + vec4(0.0, i1.y, i2.y, 1.0)) +\n"
+ " i.x + vec4(0.0, i1.x, i2.x, 1.0));\n",
+ mod289_3_funcName.c_str(), permuteFuncName.c_str(), permuteFuncName.c_str(),
+ permuteFuncName.c_str());
+
+ noiseCode.append(
+ // Gradients: 7x7 points over a square, mapped onto an octahedron.
+ // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
+ "float n_ = 0.142857142857;\n" // 1.0/7.0
+ "vec3 ns = n_ * D.wyz - D.xzx;\n"
+
+ "vec4 j = p - 49.0 * floor(p * ns.z * ns.z);\n" // mod(p,7*7)
+
+ "vec4 x_ = floor(j * ns.z);\n"
+ "vec4 y_ = floor(j - 7.0 * x_);" // mod(j,N)
+
+ "vec4 x = x_ *ns.x + ns.yyyy;\n"
+ "vec4 y = y_ *ns.x + ns.yyyy;\n"
+ "vec4 h = 1.0 - abs(x) - abs(y);\n"
+
+ "vec4 b0 = vec4(x.xy, y.xy);\n"
+ "vec4 b1 = vec4(x.zw, y.zw);\n"
+ );
+
+ noiseCode.append(
+ "vec4 s0 = floor(b0) * 2.0 + 1.0;\n"
+ "vec4 s1 = floor(b1) * 2.0 + 1.0;\n"
+ "vec4 sh = -step(h, vec4(0.0));\n"
+
+ "vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;\n"
+ "vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;\n"
+
+ "vec3 p0 = vec3(a0.xy, h.x);\n"
+ "vec3 p1 = vec3(a0.zw, h.y);\n"
+ "vec3 p2 = vec3(a1.xy, h.z);\n"
+ "vec3 p3 = vec3(a1.zw, h.w);\n"
+ );
+
+ noiseCode.appendf(
+ // Normalise gradients
+ "vec4 norm = %s(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));\n"
+ "p0 *= norm.x;\n"
+ "p1 *= norm.y;\n"
+ "p2 *= norm.z;\n"
+ "p3 *= norm.w;\n"
+
+ // Mix final noise value
+ "vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);\n"
+ "m = m * m;\n"
+ "return 42.0 * dot(m*m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3)));",
+ taylorInvSqrtFuncName.c_str());
+
+ SkString noiseFuncName;
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType, kFloat_GrSLType,
+ "snoise", SK_ARRAY_COUNT(gNoiseVec3Args), gNoiseVec3Args,
+ noiseCode.c_str(), &noiseFuncName);
+
+ const char* noiseVecIni = "noiseVecIni";
+ const char* factors = "factors";
+ const char* sum = "sum";
+ const char* xOffsets = "xOffsets";
+ const char* yOffsets = "yOffsets";
+ const char* channel = "channel";
+
+ // Fill with some prime numbers
+ builder->fsCodeAppendf("\t\tconst vec4 %s = vec4(13.0, 53.0, 101.0, 151.0);\n", xOffsets);
+ builder->fsCodeAppendf("\t\tconst vec4 %s = vec4(109.0, 167.0, 23.0, 67.0);\n", yOffsets);
+
+ // There are rounding errors if the floor operation is not performed here
+ builder->fsCodeAppendf(
+ "\t\tvec3 %s = vec3(floor((%s*vec3(%s, 1.0)).xy) * vec2(0.66) * %s, 0.0);\n",
+ noiseVecIni, invMatrixUni, vCoords, baseFrequencyUni);
+
+ // Perturb the texcoords with three components of noise
+ builder->fsCodeAppendf("\t\t%s += 0.1 * vec3(%s(%s + vec3( 0.0, 0.0, %s)),"
+ "%s(%s + vec3( 43.0, 17.0, %s)),"
+ "%s(%s + vec3(-17.0, -43.0, %s)));\n",
+ noiseVecIni, noiseFuncName.c_str(), noiseVecIni, seedUni,
+ noiseFuncName.c_str(), noiseVecIni, seedUni,
+ noiseFuncName.c_str(), noiseVecIni, seedUni);
+
+ builder->fsCodeAppendf("\t\t%s = vec4(0.0);\n", outputColor);
+
+ builder->fsCodeAppendf("\t\tvec3 %s = vec3(1.0);\n", factors);
+ builder->fsCodeAppendf("\t\tfloat %s = 0.0;\n", sum);
+
+ // Loop over all octaves
+ builder->fsCodeAppendf("\t\tfor (int octave = 0; octave < %d; ++octave) {\n", fNumOctaves);
+
+ // Loop over the 4 channels
+ builder->fsCodeAppendf("\t\t\tfor (int %s = 3; %s >= 0; --%s) {\n", channel, channel, channel);
+
+ builder->fsCodeAppendf(
+ "\t\t\t\t%s[channel] += %s.x * %s(%s * %s.yyy - vec3(%s[%s], %s[%s], %s * %s.z));\n",
+ outputColor, factors, noiseFuncName.c_str(), noiseVecIni, factors, xOffsets, channel,
+ yOffsets, channel, seedUni, factors);
+
+ builder->fsCodeAppend("\t\t\t}\n"); // end of the for loop on channels
+
+ builder->fsCodeAppendf("\t\t\t%s += %s.x;\n", sum, factors);
+ builder->fsCodeAppendf("\t\t\t%s *= vec3(0.5, 2.0, 0.75);\n", factors);
+
+ builder->fsCodeAppend("\t\t}\n"); // end of the for loop on octaves
+
+ if (fType == SkPerlinNoiseShader::kFractalNoise_Type) {
+ // The value of turbulenceFunctionResult comes from ((turbulenceFunctionResult) + 1) / 2
+ // by fractalNoise and (turbulenceFunctionResult) by turbulence.
+ builder->fsCodeAppendf("\t\t%s = %s * vec4(0.5 / %s) + vec4(0.5);\n",
+ outputColor, outputColor, sum);
+ } else {
+ builder->fsCodeAppendf("\t\t%s = abs(%s / vec4(%s));\n",
+ outputColor, outputColor, sum);
+ }
+
+ builder->fsCodeAppendf("\t\t%s.a *= %s;\n", outputColor, alphaUni);
+
+ // Clamp values
+ builder->fsCodeAppendf("\t\t%s = clamp(%s, 0.0, 1.0);\n", outputColor, outputColor);
+
+ // Pre-multiply the result
+ builder->fsCodeAppendf("\t\t%s = vec4(%s.rgb * %s.aaa, %s.a);\n",
+ outputColor, outputColor, outputColor, outputColor);
+}
+
+void GrGLPerlinNoise::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect&,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ sk_ignore_unused_variable(inputColor);
+
+ const char* vCoords;
+ fEffectMatrix.emitCodeMakeFSCoords2D(builder, key, &vCoords);
+
+ fInvMatrixUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kMat33f_GrSLType, "invMatrix");
+ const char* invMatrixUni = builder->getUniformCStr(fInvMatrixUni);
+ fBaseFrequencyUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec2f_GrSLType, "baseFrequency");
+ const char* baseFrequencyUni = builder->getUniformCStr(fBaseFrequencyUni);
+ fAlphaUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "alpha");
+ const char* alphaUni = builder->getUniformCStr(fAlphaUni);
+
+ const char* stitchDataUni = NULL;
+ if (fStitchTiles) {
+ fStitchDataUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec2f_GrSLType, "stitchData");
+ stitchDataUni = builder->getUniformCStr(fStitchDataUni);
+ }
+
+ // There are 4 lines, so the center of each line is 1/8, 3/8, 5/8 and 7/8
+ const char* chanCoordR = "0.125";
+ const char* chanCoordG = "0.375";
+ const char* chanCoordB = "0.625";
+ const char* chanCoordA = "0.875";
+ const char* chanCoord = "chanCoord";
+ const char* stitchData = "stitchData";
+ const char* ratio = "ratio";
+ const char* noiseXY = "noiseXY";
+ const char* noiseVec = "noiseVec";
+ const char* noiseSmooth = "noiseSmooth";
+ const char* fractVal = "fractVal";
+ const char* uv = "uv";
+ const char* ab = "ab";
+ const char* latticeIdx = "latticeIdx";
+ const char* lattice = "lattice";
+ const char* inc8bit = "0.00390625"; // 1.0 / 256.0
+ // This is the math to convert the two 16bit integer packed into rgba 8 bit input into a
+ // [-1,1] vector and perform a dot product between that vector and the provided vector.
+ const char* dotLattice = "dot(((%s.ga + %s.rb * vec2(%s)) * vec2(2.0) - vec2(1.0)), %s);";
+
+ // Add noise function
+ static const GrGLShaderVar gPerlinNoiseArgs[] = {
+ GrGLShaderVar(chanCoord, kFloat_GrSLType),
+ GrGLShaderVar(noiseVec, kVec2f_GrSLType)
+ };
+
+ static const GrGLShaderVar gPerlinNoiseStitchArgs[] = {
+ GrGLShaderVar(chanCoord, kFloat_GrSLType),
+ GrGLShaderVar(noiseVec, kVec2f_GrSLType),
+ GrGLShaderVar(stitchData, kVec2f_GrSLType)
+ };
+
+ SkString noiseCode;
+
+ noiseCode.appendf("\tvec4 %s = vec4(floor(%s), fract(%s));", noiseXY, noiseVec, noiseVec);
+
+ // smooth curve : t * t * (3 - 2 * t)
+ noiseCode.appendf("\n\tvec2 %s = %s.zw * %s.zw * (vec2(3.0) - vec2(2.0) * %s.zw);",
+ noiseSmooth, noiseXY, noiseXY, noiseXY);
+
+ // Adjust frequencies if we're stitching tiles
+ if (fStitchTiles) {
+ noiseCode.appendf("\n\tif(%s.x >= %s.x) { %s.x -= %s.x; }",
+ noiseXY, stitchData, noiseXY, stitchData);
+ noiseCode.appendf("\n\tif(%s.x >= (%s.x - 1.0)) { %s.x -= (%s.x - 1.0); }",
+ noiseXY, stitchData, noiseXY, stitchData);
+ noiseCode.appendf("\n\tif(%s.y >= %s.y) { %s.y -= %s.y; }",
+ noiseXY, stitchData, noiseXY, stitchData);
+ noiseCode.appendf("\n\tif(%s.y >= (%s.y - 1.0)) { %s.y -= (%s.y - 1.0); }",
+ noiseXY, stitchData, noiseXY, stitchData);
+ }
+
+ // Get texture coordinates and normalize
+ noiseCode.appendf("\n\t%s.xy = fract(floor(mod(%s.xy, 256.0)) / vec2(256.0));\n",
+ noiseXY, noiseXY);
+
+ // Get permutation for x
+ {
+ SkString xCoords("");
+ xCoords.appendf("vec2(%s.x, 0.5)", noiseXY);
+
+ noiseCode.appendf("\n\tvec2 %s;\n\t%s.x = ", latticeIdx, latticeIdx);
+ builder->appendTextureLookup(&noiseCode, samplers[0], xCoords.c_str(), kVec2f_GrSLType);
+ noiseCode.append(".r;");
+ }
+
+ // Get permutation for x + 1
+ {
+ SkString xCoords("");
+ xCoords.appendf("vec2(fract(%s.x + %s), 0.5)", noiseXY, inc8bit);
+
+ noiseCode.appendf("\n\t%s.y = ", latticeIdx);
+ builder->appendTextureLookup(&noiseCode, samplers[0], xCoords.c_str(), kVec2f_GrSLType);
+ noiseCode.append(".r;");
+ }
+
+#if defined(SK_BUILD_FOR_ANDROID)
+ // Android rounding for Tegra devices, like, for example: Xoom (Tegra 2), Nexus 7 (Tegra 3).
+ // The issue is that colors aren't accurate enough on Tegra devices. For example, if an 8 bit
+ // value of 124 (or 0.486275 here) is entered, we can get a texture value of 123.513725
+ // (or 0.484368 here). The following rounding operation prevents these precision issues from
+ // affecting the result of the noise by making sure that we only have multiples of 1/255.
+ // (Note that 1/255 is about 0.003921569, which is the value used here).
+ noiseCode.appendf("\n\t%s = floor(%s * vec2(255.0) + vec2(0.5)) * vec2(0.003921569);",
+ latticeIdx, latticeIdx);
+#endif
+
+ // Get (x,y) coordinates with the permutated x
+ noiseCode.appendf("\n\t%s = fract(%s + %s.yy);", latticeIdx, latticeIdx, noiseXY);
+
+ noiseCode.appendf("\n\tvec2 %s = %s.zw;", fractVal, noiseXY);
+
+ noiseCode.appendf("\n\n\tvec2 %s;", uv);
+ // Compute u, at offset (0,0)
+ {
+ SkString latticeCoords("");
+ latticeCoords.appendf("vec2(%s.x, %s)", latticeIdx, chanCoord);
+ noiseCode.appendf("\n\tvec4 %s = ", lattice);
+ builder->appendTextureLookup(&noiseCode, samplers[1], latticeCoords.c_str(),
+ kVec2f_GrSLType);
+ noiseCode.appendf(".bgra;\n\t%s.x = ", uv);
+ noiseCode.appendf(dotLattice, lattice, lattice, inc8bit, fractVal);
+ }
+
+ noiseCode.appendf("\n\t%s.x -= 1.0;", fractVal);
+ // Compute v, at offset (-1,0)
+ {
+ SkString latticeCoords("");
+ latticeCoords.appendf("vec2(%s.y, %s)", latticeIdx, chanCoord);
+ noiseCode.append("\n\tlattice = ");
+ builder->appendTextureLookup(&noiseCode, samplers[1], latticeCoords.c_str(),
+ kVec2f_GrSLType);
+ noiseCode.appendf(".bgra;\n\t%s.y = ", uv);
+ noiseCode.appendf(dotLattice, lattice, lattice, inc8bit, fractVal);
+ }
+
+ // Compute 'a' as a linear interpolation of 'u' and 'v'
+ noiseCode.appendf("\n\tvec2 %s;", ab);
+ noiseCode.appendf("\n\t%s.x = mix(%s.x, %s.y, %s.x);", ab, uv, uv, noiseSmooth);
+
+ noiseCode.appendf("\n\t%s.y -= 1.0;", fractVal);
+ // Compute v, at offset (-1,-1)
+ {
+ SkString latticeCoords("");
+ latticeCoords.appendf("vec2(fract(%s.y + %s), %s)", latticeIdx, inc8bit, chanCoord);
+ noiseCode.append("\n\tlattice = ");
+ builder->appendTextureLookup(&noiseCode, samplers[1], latticeCoords.c_str(),
+ kVec2f_GrSLType);
+ noiseCode.appendf(".bgra;\n\t%s.y = ", uv);
+ noiseCode.appendf(dotLattice, lattice, lattice, inc8bit, fractVal);
+ }
+
+ noiseCode.appendf("\n\t%s.x += 1.0;", fractVal);
+ // Compute u, at offset (0,-1)
+ {
+ SkString latticeCoords("");
+ latticeCoords.appendf("vec2(fract(%s.x + %s), %s)", latticeIdx, inc8bit, chanCoord);
+ noiseCode.append("\n\tlattice = ");
+ builder->appendTextureLookup(&noiseCode, samplers[1], latticeCoords.c_str(),
+ kVec2f_GrSLType);
+ noiseCode.appendf(".bgra;\n\t%s.x = ", uv);
+ noiseCode.appendf(dotLattice, lattice, lattice, inc8bit, fractVal);
+ }
+
+ // Compute 'b' as a linear interpolation of 'u' and 'v'
+ noiseCode.appendf("\n\t%s.y = mix(%s.x, %s.y, %s.x);", ab, uv, uv, noiseSmooth);
+ // Compute the noise as a linear interpolation of 'a' and 'b'
+ noiseCode.appendf("\n\treturn mix(%s.x, %s.y, %s.y);\n", ab, ab, noiseSmooth);
+
+ SkString noiseFuncName;
+ if (fStitchTiles) {
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType, kFloat_GrSLType,
+ "perlinnoise", SK_ARRAY_COUNT(gPerlinNoiseStitchArgs),
+ gPerlinNoiseStitchArgs, noiseCode.c_str(), &noiseFuncName);
+ } else {
+ builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType, kFloat_GrSLType,
+ "perlinnoise", SK_ARRAY_COUNT(gPerlinNoiseArgs),
+ gPerlinNoiseArgs, noiseCode.c_str(), &noiseFuncName);
+ }
+
+ // There are rounding errors if the floor operation is not performed here
+ builder->fsCodeAppendf("\n\t\tvec2 %s = floor((%s * vec3(%s, 1.0)).xy) * %s;",
+ noiseVec, invMatrixUni, vCoords, baseFrequencyUni);
+
+ // Clear the color accumulator
+ builder->fsCodeAppendf("\n\t\t%s = vec4(0.0);", outputColor);
+
+ if (fStitchTiles) {
+ // Set up TurbulenceInitial stitch values.
+ builder->fsCodeAppendf("\n\t\tvec2 %s = %s;", stitchData, stitchDataUni);
+ }
+
+ builder->fsCodeAppendf("\n\t\tfloat %s = 1.0;", ratio);
+
+ // Loop over all octaves
+ builder->fsCodeAppendf("\n\t\tfor (int octave = 0; octave < %d; ++octave) {", fNumOctaves);
+
+ builder->fsCodeAppendf("\n\t\t\t%s += ", outputColor);
+ if (fType != SkPerlinNoiseShader::kFractalNoise_Type) {
+ builder->fsCodeAppend("abs(");
+ }
+ if (fStitchTiles) {
+ builder->fsCodeAppendf(
+ "vec4(\n\t\t\t\t%s(%s, %s, %s),\n\t\t\t\t%s(%s, %s, %s),"
+ "\n\t\t\t\t%s(%s, %s, %s),\n\t\t\t\t%s(%s, %s, %s))",
+ noiseFuncName.c_str(), chanCoordR, noiseVec, stitchData,
+ noiseFuncName.c_str(), chanCoordG, noiseVec, stitchData,
+ noiseFuncName.c_str(), chanCoordB, noiseVec, stitchData,
+ noiseFuncName.c_str(), chanCoordA, noiseVec, stitchData);
+ } else {
+ builder->fsCodeAppendf(
+ "vec4(\n\t\t\t\t%s(%s, %s),\n\t\t\t\t%s(%s, %s),"
+ "\n\t\t\t\t%s(%s, %s),\n\t\t\t\t%s(%s, %s))",
+ noiseFuncName.c_str(), chanCoordR, noiseVec,
+ noiseFuncName.c_str(), chanCoordG, noiseVec,
+ noiseFuncName.c_str(), chanCoordB, noiseVec,
+ noiseFuncName.c_str(), chanCoordA, noiseVec);
+ }
+ if (fType != SkPerlinNoiseShader::kFractalNoise_Type) {
+ builder->fsCodeAppendf(")"); // end of "abs("
+ }
+ builder->fsCodeAppendf(" * %s;", ratio);
+
+ builder->fsCodeAppendf("\n\t\t\t%s *= vec2(2.0);", noiseVec);
+ builder->fsCodeAppendf("\n\t\t\t%s *= 0.5;", ratio);
+
+ if (fStitchTiles) {
+ builder->fsCodeAppendf("\n\t\t\t%s *= vec2(2.0);", stitchData);
+ }
+ builder->fsCodeAppend("\n\t\t}"); // end of the for loop on octaves
+
+ if (fType == SkPerlinNoiseShader::kFractalNoise_Type) {
+ // The value of turbulenceFunctionResult comes from ((turbulenceFunctionResult) + 1) / 2
+ // by fractalNoise and (turbulenceFunctionResult) by turbulence.
+ builder->fsCodeAppendf("\n\t\t%s = %s * vec4(0.5) + vec4(0.5);", outputColor, outputColor);
+ }
+
+ builder->fsCodeAppendf("\n\t\t%s.a *= %s;", outputColor, alphaUni);
+
+ // Clamp values
+ builder->fsCodeAppendf("\n\t\t%s = clamp(%s, 0.0, 1.0);", outputColor, outputColor);
+
+ // Pre-multiply the result
+ builder->fsCodeAppendf("\n\t\t%s = vec4(%s.rgb * %s.aaa, %s.a);\n",
+ outputColor, outputColor, outputColor, outputColor);
+}
+
+GrGLNoise::GrGLNoise(const GrBackendEffectFactory& factory, const GrDrawEffect& drawEffect)
+ : INHERITED (factory)
+ , fType(drawEffect.castEffect<GrPerlinNoiseEffect>().type())
+ , fStitchTiles(drawEffect.castEffect<GrPerlinNoiseEffect>().stitchTiles())
+ , fNumOctaves(drawEffect.castEffect<GrPerlinNoiseEffect>().numOctaves())
+ , fEffectMatrix(drawEffect.castEffect<GrPerlinNoiseEffect>().coordsType()) {
+}
+
+GrGLEffect::EffectKey GrGLNoise::GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
+ const GrPerlinNoiseEffect& turbulence = drawEffect.castEffect<GrPerlinNoiseEffect>();
+
+ EffectKey key = turbulence.numOctaves();
+
+ key = key << 3; // Make room for next 3 bits
+
+ switch (turbulence.type()) {
+ case SkPerlinNoiseShader::kFractalNoise_Type:
+ key |= 0x1;
+ break;
+ case SkPerlinNoiseShader::kTurbulence_Type:
+ key |= 0x2;
+ break;
+ default:
+ // leave key at 0
+ break;
+ }
+
+ if (turbulence.stitchTiles()) {
+ key |= 0x4; // Flip the 3rd bit if tile stitching is on
+ }
+
+ key = key << GrGLEffectMatrix::kKeyBits;
+
+ SkMatrix m = turbulence.matrix();
+ m.postTranslate(SK_Scalar1, SK_Scalar1);
+ return key | GrGLEffectMatrix::GenKey(m, drawEffect,
+ drawEffect.castEffect<GrPerlinNoiseEffect>().coordsType(), NULL);
+}
+
+void GrGLNoise::setData(const GrGLUniformManager& uman, const GrDrawEffect& drawEffect) {
+ const GrPerlinNoiseEffect& turbulence = drawEffect.castEffect<GrPerlinNoiseEffect>();
+
+ const SkVector& baseFrequency = turbulence.baseFrequency();
+ uman.set2f(fBaseFrequencyUni, baseFrequency.fX, baseFrequency.fY);
+ uman.set1f(fAlphaUni, SkScalarDiv(SkIntToScalar(turbulence.alpha()), SkIntToScalar(255)));
+
+ SkMatrix m = turbulence.matrix();
+ SkMatrix invM;
+ if (!m.invert(&invM)) {
+ invM.reset();
+ } else {
+ invM.postConcat(invM); // Square the matrix
+ }
+ uman.setSkMatrix(fInvMatrixUni, invM);
+
+ // This (1,1) translation is due to WebKit's 1 based coordinates for the noise
+ // (as opposed to 0 based, usually). The same adjustment is in the shadeSpan() functions.
+ m.postTranslate(SK_Scalar1, SK_Scalar1);
+ fEffectMatrix.setData(uman, m, drawEffect, NULL);
+}
+
+void GrGLPerlinNoise::setData(const GrGLUniformManager& uman, const GrDrawEffect& drawEffect) {
+ INHERITED::setData(uman, drawEffect);
+
+ const GrPerlinNoiseEffect& turbulence = drawEffect.castEffect<GrPerlinNoiseEffect>();
+ if (turbulence.stitchTiles()) {
+ const SkPerlinNoiseShader::StitchData& stitchData = turbulence.stitchData();
+ uman.set2f(fStitchDataUni, SkIntToScalar(stitchData.fWidth),
+ SkIntToScalar(stitchData.fHeight));
+ }
+}
+
+void GrGLSimplexNoise::setData(const GrGLUniformManager& uman, const GrDrawEffect& drawEffect) {
+ INHERITED::setData(uman, drawEffect);
+
+ const GrSimplexNoiseEffect& turbulence = drawEffect.castEffect<GrSimplexNoiseEffect>();
+ uman.set1f(fSeedUni, turbulence.seed());
+}
+
+/////////////////////////////////////////////////////////////////////
+
+GrEffectRef* SkPerlinNoiseShader::asNewEffect(GrContext* context, const SkPaint& paint) const {
+ SkASSERT(NULL != context);
+
+ // Either we don't stitch tiles, either we have a valid tile size
+ SkASSERT(!fStitchTiles || !fTileSize.isEmpty());
+
+#ifdef SK_USE_SIMPLEX_NOISE
+ // Simplex noise is currently disabled but can be enabled by defining SK_USE_SIMPLEX_NOISE
+ sk_ignore_unused_variable(context);
+ GrEffectRef* effect =
+ GrSimplexNoiseEffect::Create(fType, fPaintingData->fBaseFrequency,
+ fNumOctaves, fStitchTiles, fSeed,
+ this->getLocalMatrix(), paint.getAlpha());
+#else
+ GrTexture* permutationsTexture = GrLockAndRefCachedBitmapTexture(
+ context, *fPaintingData->getPermutationsBitmap(), NULL);
+ GrTexture* noiseTexture = GrLockAndRefCachedBitmapTexture(
+ context, *fPaintingData->getNoiseBitmap(), NULL);
+
+ GrEffectRef* effect = (NULL != permutationsTexture) && (NULL != noiseTexture) ?
+ GrPerlinNoiseEffect::Create(fType, fPaintingData->fBaseFrequency,
+ fNumOctaves, fStitchTiles,
+ fPaintingData->fStitchDataInit,
+ permutationsTexture, noiseTexture,
+ this->getLocalMatrix(), paint.getAlpha()) :
+ NULL;
+
+ // Unlock immediately, this is not great, but we don't have a way of
+ // knowing when else to unlock it currently. TODO: Remove this when
+ // unref becomes the unlock replacement for all types of textures.
+ if (NULL != permutationsTexture) {
+ GrUnlockAndUnrefCachedBitmapTexture(permutationsTexture);
+ }
+ if (NULL != noiseTexture) {
+ GrUnlockAndUnrefCachedBitmapTexture(noiseTexture);
+ }
+#endif
+
+ return effect;
+}
+
+#else
+
+GrEffectRef* SkPerlinNoiseShader::asNewEffect(GrContext*, const SkPaint&) const {
+ SkDEBUGFAIL("Should not call in GPU-less build");
+ return NULL;
+}
+
+#endif
+
+#ifdef SK_DEVELOPER
+void SkPerlinNoiseShader::toString(SkString* str) const {
+ str->append("SkPerlinNoiseShader: (");
+
+ str->append("type: ");
+ switch (fType) {
+ case kFractalNoise_Type:
+ str->append("\"fractal noise\"");
+ break;
+ case kTurbulence_Type:
+ str->append("\"turbulence\"");
+ break;
+ default:
+ str->append("\"unknown\"");
+ break;
+ }
+ str->append(" base frequency: (");
+ str->appendScalar(fBaseFrequencyX);
+ str->append(", ");
+ str->appendScalar(fBaseFrequencyY);
+ str->append(") number of octaves: ");
+ str->appendS32(fNumOctaves);
+ str->append(" seed: ");
+ str->appendScalar(fSeed);
+ str->append(" stitch tiles: ");
+ str->append(fStitchTiles ? "true " : "false ");
+
+ this->INHERITED::toString(str);
+
+ str->append(")");
+}
+#endif
diff --git a/effects/SkPixelXorXfermode.cpp b/effects/SkPixelXorXfermode.cpp
new file mode 100644
index 00000000..b9c96dc3
--- /dev/null
+++ b/effects/SkPixelXorXfermode.cpp
@@ -0,0 +1,38 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#include "SkPixelXorXfermode.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkString.h"
+
+// we always return an opaque color, 'cause I don't know what to do with
+// the alpha-component and still return a valid premultiplied color.
+SkPMColor SkPixelXorXfermode::xferColor(SkPMColor src, SkPMColor dst) const {
+ SkPMColor res = src ^ dst ^ fOpColor;
+ res |= (SK_A32_MASK << SK_A32_SHIFT); // force it to be opaque
+ return res;
+}
+
+void SkPixelXorXfermode::flatten(SkFlattenableWriteBuffer& wb) const {
+ this->INHERITED::flatten(wb);
+ wb.writeColor(fOpColor);
+}
+
+SkPixelXorXfermode::SkPixelXorXfermode(SkFlattenableReadBuffer& rb)
+ : INHERITED(rb) {
+ fOpColor = rb.readColor();
+}
+
+#ifdef SK_DEVELOPER
+void SkPixelXorXfermode::toString(SkString* str) const {
+ str->append("SkPixelXorXfermode: ");
+ str->appendHex(fOpColor);
+}
+#endif
diff --git a/effects/SkPorterDuff.cpp b/effects/SkPorterDuff.cpp
new file mode 100644
index 00000000..816ddae0
--- /dev/null
+++ b/effects/SkPorterDuff.cpp
@@ -0,0 +1,87 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#include "SkPorterDuff.h"
+#include "SkXfermode.h"
+
+/* This file just exists as a compatibility layer, gluing the PorterDuff API
+ into the (extended) SkXfermode API
+ */
+
+#define MAKE_PAIR(mode) { SkPorterDuff::k##mode##_Mode, SkXfermode::k##mode##_Mode }
+
+// this table must be in SkPorterDuff::Mode order, so it can be indexed directly
+// with a porterduff mode.
+static const struct Pair {
+ SkPorterDuff::Mode fPD;
+ SkXfermode::Mode fXF;
+} gPairs[] = {
+ MAKE_PAIR(Clear),
+ MAKE_PAIR(Src),
+ MAKE_PAIR(Dst),
+ MAKE_PAIR(SrcOver),
+ MAKE_PAIR(DstOver),
+ MAKE_PAIR(SrcIn),
+ MAKE_PAIR(DstIn),
+ MAKE_PAIR(SrcOut),
+ MAKE_PAIR(DstOut),
+ MAKE_PAIR(SrcATop),
+ MAKE_PAIR(DstATop),
+ MAKE_PAIR(Xor),
+ MAKE_PAIR(Darken),
+ MAKE_PAIR(Lighten),
+ MAKE_PAIR(Modulate),
+ MAKE_PAIR(Screen),
+ { SkPorterDuff::kAdd_Mode, SkXfermode::kPlus_Mode },
+#ifdef SK_BUILD_FOR_ANDROID
+ MAKE_PAIR(Overlay),
+#endif
+};
+
+static bool find_pdmode(SkXfermode::Mode src, SkPorterDuff::Mode* dst) {
+ const Pair* pairs = gPairs;
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gPairs); i++) {
+ if (pairs[i].fXF == src) {
+ if (dst) {
+ *dst = pairs[i].fPD;
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+SkXfermode::Mode SkPorterDuff::ToXfermodeMode(Mode mode) {
+ SkASSERT((unsigned)mode < SkPorterDuff::kModeCount);
+ const Pair& pair = gPairs[mode];
+ SkASSERT(pair.fPD == mode);
+ return pair.fXF;
+}
+
+SkXfermode* SkPorterDuff::CreateXfermode(SkPorterDuff::Mode mode) {
+ const Pair& pair = gPairs[mode];
+ SkASSERT(pair.fPD == mode);
+ return SkXfermode::Create(pair.fXF);
+}
+
+bool SkPorterDuff::IsMode(SkXfermode* xfer, Mode* pdmode) {
+ SkXfermode::Mode xfmode;
+ if (!SkXfermode::IsMode(xfer, &xfmode)) {
+ return false;
+ }
+ return find_pdmode(xfmode, pdmode);
+}
+
+SkXfermodeProc SkPorterDuff::GetXfermodeProc(Mode mode) {
+ return SkXfermode::GetProc(gPairs[mode].fXF);
+}
+
+SkXfermodeProc16 SkPorterDuff::GetXfermodeProc16(Mode mode, SkColor srcColor) {
+ return SkXfermode::GetProc16(gPairs[mode].fXF, srcColor);
+}
diff --git a/effects/SkRectShaderImageFilter.cpp b/effects/SkRectShaderImageFilter.cpp
new file mode 100644
index 00000000..ada861fd
--- /dev/null
+++ b/effects/SkRectShaderImageFilter.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkRectShaderImageFilter.h"
+#include "SkBitmap.h"
+#include "SkCanvas.h"
+#include "SkDevice.h"
+#include "SkFlattenableBuffers.h"
+#include "SkShader.h"
+
+SkRectShaderImageFilter* SkRectShaderImageFilter::Create(SkShader* s, const SkRect& rect) {
+ SkASSERT(s);
+ return SkNEW_ARGS(SkRectShaderImageFilter, (s, rect));
+}
+
+SkRectShaderImageFilter::SkRectShaderImageFilter(SkShader* s, const SkRect& rect)
+ : INHERITED(NULL)
+ , fShader(s)
+ , fRect(rect) {
+ SkASSERT(s);
+ s->ref();
+}
+
+SkRectShaderImageFilter::SkRectShaderImageFilter(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {
+ fShader = buffer.readFlattenableT<SkShader>();
+ buffer.readRect(&fRect);
+}
+
+void SkRectShaderImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+
+ buffer.writeFlattenable(fShader);
+ buffer.writeRect(fRect);
+}
+
+SkRectShaderImageFilter::~SkRectShaderImageFilter() {
+ SkSafeUnref(fShader);
+}
+
+bool SkRectShaderImageFilter::onFilterImage(Proxy* proxy,
+ const SkBitmap& source,
+ const SkMatrix&,
+ SkBitmap* result,
+ SkIPoint*) {
+ SkRect rect(fRect);
+ if (rect.isEmpty()) {
+ rect = SkRect::MakeWH(SkIntToScalar(source.width()), SkIntToScalar(source.height()));
+ }
+
+ if (rect.isEmpty()) {
+ return false;
+ }
+
+ SkAutoTUnref<SkDevice> device(proxy->createDevice(SkScalarCeilToInt(rect.width()),
+ SkScalarCeilToInt(rect.height())));
+ SkCanvas canvas(device.get());
+ SkPaint paint;
+ paint.setShader(fShader);
+ canvas.drawRect(rect, paint);
+ *result = device.get()->accessBitmap(false);
+ return true;
+}
diff --git a/effects/SkStippleMaskFilter.cpp b/effects/SkStippleMaskFilter.cpp
new file mode 100644
index 00000000..14f30ec6
--- /dev/null
+++ b/effects/SkStippleMaskFilter.cpp
@@ -0,0 +1,52 @@
+/*
+ * 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 "SkStippleMaskFilter.h"
+#include "SkString.h"
+
+bool SkStippleMaskFilter::filterMask(SkMask* dst,
+ const SkMask& src,
+ const SkMatrix& matrix,
+ SkIPoint* margin) const {
+
+ if (src.fFormat != SkMask::kA8_Format) {
+ return false;
+ }
+
+ dst->fBounds = src.fBounds;
+ dst->fRowBytes = dst->fBounds.width();
+ dst->fFormat = SkMask::kA8_Format;
+ dst->fImage = NULL;
+
+ if (NULL != src.fImage) {
+ size_t dstSize = dst->computeImageSize();
+ if (0 == dstSize) {
+ return false; // too big to allocate, abort
+ }
+
+ dst->fImage = SkMask::AllocImage(dstSize);
+
+ uint8_t* srcScanLine = src.fImage;
+ uint8_t* scanline = dst->fImage;
+
+ for (int y = 0; y < src.fBounds.height(); ++y) {
+ for (int x = 0; x < src.fBounds.width(); ++x) {
+ scanline[x] = srcScanLine[x] && ((x+y) & 0x1) ? 0xFF : 0x00;
+ }
+ scanline += dst->fRowBytes;
+ srcScanLine += src.fRowBytes;
+ }
+ }
+
+ return true;
+}
+
+#ifdef SK_DEVELOPER
+void SkStippleMaskFilter::toString(SkString* str) const {
+ str->append("SkStippleMaskFilter: ()");
+}
+#endif
diff --git a/effects/SkTableColorFilter.cpp b/effects/SkTableColorFilter.cpp
new file mode 100644
index 00000000..cbcc6bc4
--- /dev/null
+++ b/effects/SkTableColorFilter.cpp
@@ -0,0 +1,431 @@
+
+#include "SkBitmap.h"
+#include "SkTableColorFilter.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkUnPreMultiply.h"
+#include "SkString.h"
+
+class SkTable_ColorFilter : public SkColorFilter {
+public:
+ SkTable_ColorFilter(const uint8_t tableA[], const uint8_t tableR[],
+ const uint8_t tableG[], const uint8_t tableB[]) {
+ fBitmap = NULL;
+ fFlags = 0;
+
+ uint8_t* dst = fStorage;
+ if (tableA) {
+ memcpy(dst, tableA, 256);
+ dst += 256;
+ fFlags |= kA_Flag;
+ }
+ if (tableR) {
+ memcpy(dst, tableR, 256);
+ dst += 256;
+ fFlags |= kR_Flag;
+ }
+ if (tableG) {
+ memcpy(dst, tableG, 256);
+ dst += 256;
+ fFlags |= kG_Flag;
+ }
+ if (tableB) {
+ memcpy(dst, tableB, 256);
+ fFlags |= kB_Flag;
+ }
+ }
+
+ virtual ~SkTable_ColorFilter() {
+ SkDELETE(fBitmap);
+ }
+
+ virtual bool asComponentTable(SkBitmap* table) const SK_OVERRIDE;
+
+#if SK_SUPPORT_GPU
+ virtual GrEffectRef* asNewEffect(GrContext* context) const SK_OVERRIDE;
+#endif
+
+ virtual void filterSpan(const SkPMColor src[], int count,
+ SkPMColor dst[]) const SK_OVERRIDE;
+
+ SkDEVCODE(virtual void toString(SkString* str) const SK_OVERRIDE;)
+
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkTable_ColorFilter)
+
+ enum {
+ kA_Flag = 1 << 0,
+ kR_Flag = 1 << 1,
+ kG_Flag = 1 << 2,
+ kB_Flag = 1 << 3,
+ };
+
+protected:
+ SkTable_ColorFilter(SkFlattenableReadBuffer& buffer);
+ virtual void flatten(SkFlattenableWriteBuffer&) const SK_OVERRIDE;
+
+private:
+ mutable const SkBitmap* fBitmap; // lazily allocated
+
+ uint8_t fStorage[256 * 4];
+ unsigned fFlags;
+
+ typedef SkColorFilter INHERITED;
+};
+
+static const uint8_t gIdentityTable[] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
+ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+ 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
+ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+ 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
+ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+ 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+ 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
+ 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
+ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+ 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
+ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+ 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
+ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
+ 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
+ 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
+ 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
+ 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7,
+ 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
+ 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7,
+ 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
+ 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7,
+ 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
+ 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7,
+ 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
+ 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7,
+ 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
+};
+
+void SkTable_ColorFilter::filterSpan(const SkPMColor src[], int count,
+ SkPMColor dst[]) const {
+ const uint8_t* table = fStorage;
+ const uint8_t* tableA = gIdentityTable;
+ const uint8_t* tableR = gIdentityTable;
+ const uint8_t* tableG = gIdentityTable;
+ const uint8_t* tableB = gIdentityTable;
+ if (fFlags & kA_Flag) {
+ tableA = table; table += 256;
+ }
+ if (fFlags & kR_Flag) {
+ tableR = table; table += 256;
+ }
+ if (fFlags & kG_Flag) {
+ tableG = table; table += 256;
+ }
+ if (fFlags & kB_Flag) {
+ tableB = table;
+ }
+
+ const SkUnPreMultiply::Scale* scaleTable = SkUnPreMultiply::GetScaleTable();
+ for (int i = 0; i < count; ++i) {
+ SkPMColor c = src[i];
+ unsigned a, r, g, b;
+ if (0 == c) {
+ a = r = g = b = 0;
+ } else {
+ a = SkGetPackedA32(c);
+ r = SkGetPackedR32(c);
+ g = SkGetPackedG32(c);
+ b = SkGetPackedB32(c);
+
+ if (a < 255) {
+ SkUnPreMultiply::Scale scale = scaleTable[a];
+ r = SkUnPreMultiply::ApplyScale(scale, r);
+ g = SkUnPreMultiply::ApplyScale(scale, g);
+ b = SkUnPreMultiply::ApplyScale(scale, b);
+ }
+ }
+ dst[i] = SkPremultiplyARGBInline(tableA[a], tableR[r],
+ tableG[g], tableB[b]);
+ }
+}
+
+#ifdef SK_DEVELOPER
+void SkTable_ColorFilter::toString(SkString* str) const {
+ str->append("SkTable_ColorFilter");
+}
+#endif
+
+static const uint8_t gCountNibBits[] = {
+ 0, 1, 1, 2,
+ 1, 2, 2, 3,
+ 1, 2, 2, 3,
+ 2, 3, 3, 4
+};
+
+#include "SkPackBits.h"
+
+void SkTable_ColorFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+
+ uint8_t storage[5*256];
+ int count = gCountNibBits[fFlags & 0xF];
+ size_t size = SkPackBits::Pack8(fStorage, count * 256, storage);
+ SkASSERT(size <= sizeof(storage));
+
+// SkDebugf("raw %d packed %d\n", count * 256, size);
+
+ buffer.writeInt(fFlags);
+ buffer.writeByteArray(storage, size);
+}
+
+SkTable_ColorFilter::SkTable_ColorFilter(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ fBitmap = NULL;
+
+ uint8_t storage[5*256];
+
+ fFlags = buffer.readInt();
+
+ size_t size = buffer.getArrayCount();
+ SkASSERT(size <= sizeof(storage));
+ buffer.readByteArray(storage);
+
+ SkDEBUGCODE(size_t raw = ) SkPackBits::Unpack8(storage, size, fStorage);
+
+ SkASSERT(raw <= sizeof(fStorage));
+ SkDEBUGCODE(size_t count = gCountNibBits[fFlags & 0xF]);
+ SkASSERT(raw == count * 256);
+}
+
+bool SkTable_ColorFilter::asComponentTable(SkBitmap* table) const {
+ if (table) {
+ if (NULL == fBitmap) {
+ SkBitmap* bmp = SkNEW(SkBitmap);
+ bmp->setConfig(SkBitmap::kA8_Config, 256, 4, 256);
+ bmp->allocPixels();
+ uint8_t* bitmapPixels = bmp->getAddr8(0, 0);
+ int offset = 0;
+ static const unsigned kFlags[] = { kA_Flag, kR_Flag, kG_Flag, kB_Flag };
+
+ for (int x = 0; x < 4; ++x) {
+ if (!(fFlags & kFlags[x])) {
+ memcpy(bitmapPixels, gIdentityTable, sizeof(gIdentityTable));
+ } else {
+ memcpy(bitmapPixels, fStorage + offset, 256);
+ offset += 256;
+ }
+ bitmapPixels += 256;
+ }
+ fBitmap = bmp;
+ }
+ *table = *fBitmap;
+ }
+ return true;
+}
+
+#if SK_SUPPORT_GPU
+
+#include "GrEffect.h"
+#include "GrTBackendEffectFactory.h"
+#include "gl/GrGLEffect.h"
+#include "SkGr.h"
+
+class GLColorTableEffect;
+
+class ColorTableEffect : public GrEffect {
+public:
+ static GrEffectRef* Create(GrTexture* texture, unsigned flags) {
+ AutoEffectUnref effect(SkNEW_ARGS(ColorTableEffect, (texture, flags)));
+ return CreateEffectRef(effect);
+ }
+
+ virtual ~ColorTableEffect();
+
+ static const char* Name() { return "ColorTable"; }
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+
+ virtual void getConstantColorComponents(GrColor* color, uint32_t* validFlags) const SK_OVERRIDE;
+
+ typedef GLColorTableEffect GLEffect;
+
+private:
+ virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE;
+
+ explicit ColorTableEffect(GrTexture* texture, unsigned flags);
+
+ GR_DECLARE_EFFECT_TEST;
+
+ GrTextureAccess fTextureAccess;
+ unsigned fFlags; // currently not used in shader code, just to assist
+ // getConstantColorComponents().
+
+ typedef GrEffect INHERITED;
+};
+
+class GLColorTableEffect : public GrGLEffect {
+public:
+ GLColorTableEffect(const GrBackendEffectFactory&, const GrDrawEffect&);
+
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE {}
+
+ static EffectKey GenKey(const GrDrawEffect&, const GrGLCaps&);
+
+private:
+
+ typedef GrGLEffect INHERITED;
+};
+
+GLColorTableEffect::GLColorTableEffect(const GrBackendEffectFactory& factory, const GrDrawEffect&)
+ : INHERITED(factory) {
+ }
+
+void GLColorTableEffect::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+
+ static const float kColorScaleFactor = 255.0f / 256.0f;
+ static const float kColorOffsetFactor = 1.0f / 512.0f;
+ if (NULL == inputColor) {
+ // the input color is solid white (all ones).
+ static const float kMaxValue = kColorScaleFactor + kColorOffsetFactor;
+ builder->fsCodeAppendf("\t\tvec4 coord = vec4(%f, %f, %f, %f);\n",
+ kMaxValue, kMaxValue, kMaxValue, kMaxValue);
+
+ } else {
+ builder->fsCodeAppendf("\t\tfloat nonZeroAlpha = max(%s.a, .0001);\n", inputColor);
+ builder->fsCodeAppendf("\t\tvec4 coord = vec4(%s.rgb / nonZeroAlpha, nonZeroAlpha);\n", inputColor);
+ builder->fsCodeAppendf("\t\tcoord = coord * %f + vec4(%f, %f, %f, %f);\n",
+ kColorScaleFactor,
+ kColorOffsetFactor, kColorOffsetFactor,
+ kColorOffsetFactor, kColorOffsetFactor);
+ }
+
+ builder->fsCodeAppendf("\t\t%s.a = ", outputColor);
+ builder->appendTextureLookup(GrGLShaderBuilder::kFragment_ShaderType, samplers[0], "vec2(coord.a, 0.125)");
+ builder->fsCodeAppend(";\n");
+
+ builder->fsCodeAppendf("\t\t%s.r = ", outputColor);
+ builder->appendTextureLookup(GrGLShaderBuilder::kFragment_ShaderType, samplers[0], "vec2(coord.r, 0.375)");
+ builder->fsCodeAppend(";\n");
+
+ builder->fsCodeAppendf("\t\t%s.g = ", outputColor);
+ builder->appendTextureLookup(GrGLShaderBuilder::kFragment_ShaderType, samplers[0], "vec2(coord.g, 0.625)");
+ builder->fsCodeAppend(";\n");
+
+ builder->fsCodeAppendf("\t\t%s.b = ", outputColor);
+ builder->appendTextureLookup(GrGLShaderBuilder::kFragment_ShaderType, samplers[0], "vec2(coord.b, 0.875)");
+ builder->fsCodeAppend(";\n");
+
+ builder->fsCodeAppendf("\t\t%s.rgb *= %s.a;\n", outputColor, outputColor);
+}
+
+GrGLEffect::EffectKey GLColorTableEffect::GenKey(const GrDrawEffect&, const GrGLCaps&) {
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ColorTableEffect::ColorTableEffect(GrTexture* texture, unsigned flags)
+ : fTextureAccess(texture, "a")
+ , fFlags(flags) {
+ this->addTextureAccess(&fTextureAccess);
+}
+
+ColorTableEffect::~ColorTableEffect() {
+}
+
+const GrBackendEffectFactory& ColorTableEffect::getFactory() const {
+ return GrTBackendEffectFactory<ColorTableEffect>::getInstance();
+}
+
+bool ColorTableEffect::onIsEqual(const GrEffect& sBase) const {
+ return this->texture(0) == sBase.texture(0);
+}
+
+void ColorTableEffect::getConstantColorComponents(GrColor* color, uint32_t* validFlags) const {
+ // If we kept the table in the effect then we could actually run known inputs through the
+ // table.
+ if (fFlags & SkTable_ColorFilter::kR_Flag) {
+ *validFlags &= ~kR_GrColorComponentFlag;
+ }
+ if (fFlags & SkTable_ColorFilter::kG_Flag) {
+ *validFlags &= ~kG_GrColorComponentFlag;
+ }
+ if (fFlags & SkTable_ColorFilter::kB_Flag) {
+ *validFlags &= ~kB_GrColorComponentFlag;
+ }
+ if (fFlags & SkTable_ColorFilter::kA_Flag) {
+ *validFlags &= ~kA_GrColorComponentFlag;
+ }
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_EFFECT_TEST(ColorTableEffect);
+
+GrEffectRef* ColorTableEffect::TestCreate(SkMWCRandom* random,
+ GrContext* context,
+ const GrDrawTargetCaps&,
+ GrTexture* textures[]) {
+ static unsigned kAllFlags = SkTable_ColorFilter::kR_Flag | SkTable_ColorFilter::kG_Flag |
+ SkTable_ColorFilter::kB_Flag | SkTable_ColorFilter::kA_Flag;
+ return ColorTableEffect::Create(textures[GrEffectUnitTest::kAlphaTextureIdx], kAllFlags);
+}
+
+GrEffectRef* SkTable_ColorFilter::asNewEffect(GrContext* context) const {
+ SkBitmap bitmap;
+ GrEffectRef* effect = NULL;
+ this->asComponentTable(&bitmap);
+ // passing NULL because this effect does no tiling or filtering.
+ GrTexture* texture = GrLockAndRefCachedBitmapTexture(context, bitmap, NULL);
+ if (NULL != texture) {
+ effect = ColorTableEffect::Create(texture, fFlags);
+
+ // Unlock immediately, this is not great, but we don't have a way of
+ // knowing when else to unlock it currently. TODO: Remove this when
+ // unref becomes the unlock replacement for all types of textures.
+ GrUnlockAndUnrefCachedBitmapTexture(texture);
+ }
+ return effect;
+}
+
+#endif // SK_SUPPORT_GPU
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_CPU_BENDIAN
+#else
+ #define SK_A32_INDEX (3 - (SK_A32_SHIFT >> 3))
+ #define SK_R32_INDEX (3 - (SK_R32_SHIFT >> 3))
+ #define SK_G32_INDEX (3 - (SK_G32_SHIFT >> 3))
+ #define SK_B32_INDEX (3 - (SK_B32_SHIFT >> 3))
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkColorFilter* SkTableColorFilter::Create(const uint8_t table[256]) {
+ return SkNEW_ARGS(SkTable_ColorFilter, (table, table, table, table));
+}
+
+SkColorFilter* SkTableColorFilter::CreateARGB(const uint8_t tableA[256],
+ const uint8_t tableR[256],
+ const uint8_t tableG[256],
+ const uint8_t tableB[256]) {
+ return SkNEW_ARGS(SkTable_ColorFilter, (tableA, tableR, tableG, tableB));
+}
+
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkTableColorFilter)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkTable_ColorFilter)
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
diff --git a/effects/SkTableMaskFilter.cpp b/effects/SkTableMaskFilter.cpp
new file mode 100644
index 00000000..5bff4def
--- /dev/null
+++ b/effects/SkTableMaskFilter.cpp
@@ -0,0 +1,143 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#include "SkTableMaskFilter.h"
+#include "SkFlattenableBuffers.h"
+#include "SkString.h"
+
+SkTableMaskFilter::SkTableMaskFilter() {
+ for (int i = 0; i < 256; i++) {
+ fTable[i] = i;
+ }
+}
+
+SkTableMaskFilter::SkTableMaskFilter(const uint8_t table[256]) {
+ memcpy(fTable, table, sizeof(fTable));
+}
+
+SkTableMaskFilter::~SkTableMaskFilter() {}
+
+bool SkTableMaskFilter::filterMask(SkMask* dst, const SkMask& src,
+ const SkMatrix&, SkIPoint* margin) const {
+ if (src.fFormat != SkMask::kA8_Format) {
+ return false;
+ }
+
+ dst->fBounds = src.fBounds;
+ dst->fRowBytes = SkAlign4(dst->fBounds.width());
+ dst->fFormat = SkMask::kA8_Format;
+ dst->fImage = NULL;
+
+ if (src.fImage) {
+ dst->fImage = SkMask::AllocImage(dst->computeImageSize());
+
+ const uint8_t* srcP = src.fImage;
+ uint8_t* dstP = dst->fImage;
+ const uint8_t* table = fTable;
+ int dstWidth = dst->fBounds.width();
+ int extraZeros = dst->fRowBytes - dstWidth;
+
+ for (int y = dst->fBounds.height() - 1; y >= 0; --y) {
+ for (int x = dstWidth - 1; x >= 0; --x) {
+ dstP[x] = table[srcP[x]];
+ }
+ srcP += src.fRowBytes;
+ // we can't just inc dstP by rowbytes, because if it has any
+ // padding between its width and its rowbytes, we need to zero those
+ // so that the bitters can read those safely if that is faster for
+ // them
+ dstP += dstWidth;
+ for (int i = extraZeros - 1; i >= 0; --i) {
+ *dstP++ = 0;
+ }
+ }
+ }
+
+ if (margin) {
+ margin->set(0, 0);
+ }
+ return true;
+}
+
+SkMask::Format SkTableMaskFilter::getFormat() const {
+ return SkMask::kA8_Format;
+}
+
+void SkTableMaskFilter::flatten(SkFlattenableWriteBuffer& wb) const {
+ this->INHERITED::flatten(wb);
+ wb.writeByteArray(fTable, 256);
+}
+
+SkTableMaskFilter::SkTableMaskFilter(SkFlattenableReadBuffer& rb)
+ : INHERITED(rb) {
+ SkASSERT(256 == rb.getArrayCount());
+ rb.readByteArray(fTable);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SkTableMaskFilter::MakeGammaTable(uint8_t table[256], SkScalar gamma) {
+ const float dx = 1 / 255.0f;
+ const float g = SkScalarToFloat(gamma);
+
+ float x = 0;
+ for (int i = 0; i < 256; i++) {
+ // float ee = powf(x, g) * 255;
+ table[i] = SkPin32(sk_float_round2int(powf(x, g) * 255), 0, 255);
+ x += dx;
+ }
+}
+
+void SkTableMaskFilter::MakeClipTable(uint8_t table[256], uint8_t min,
+ uint8_t max) {
+ if (0 == max) {
+ max = 1;
+ }
+ if (min >= max) {
+ min = max - 1;
+ }
+ SkASSERT(min < max);
+
+ SkFixed scale = (1 << 16) * 255 / (max - min);
+ memset(table, 0, min + 1);
+ for (int i = min + 1; i < max; i++) {
+ int value = SkFixedRound(scale * (i - min));
+ SkASSERT(value <= 255);
+ table[i] = value;
+ }
+ memset(table + max, 255, 256 - max);
+
+#if 0
+ int j;
+ for (j = 0; j < 256; j++) {
+ if (table[j]) {
+ break;
+ }
+ }
+ SkDebugf("%d %d start [%d]", min, max, j);
+ for (; j < 256; j++) {
+ SkDebugf(" %d", table[j]);
+ }
+ SkDebugf("\n\n");
+#endif
+}
+
+#ifdef SK_DEVELOPER
+void SkTableMaskFilter::toString(SkString* str) const {
+ str->append("SkTableMaskFilter: (");
+
+ str->append("table: ");
+ for (int i = 0; i < 255; ++i) {
+ str->appendf("%d, ", fTable[i]);
+ }
+ str->appendf("%d", fTable[255]);
+
+ str->append(")");
+}
+#endif
diff --git a/effects/SkTestImageFilters.cpp b/effects/SkTestImageFilters.cpp
new file mode 100755
index 00000000..a919dedd
--- /dev/null
+++ b/effects/SkTestImageFilters.cpp
@@ -0,0 +1,81 @@
+
+#include "SkTestImageFilters.h"
+#include "SkCanvas.h"
+#include "SkDevice.h"
+#include "SkFlattenableBuffers.h"
+
+// Simple helper canvas that "takes ownership" of the provided device, so that
+// when this canvas goes out of scope, so will its device. Could be replaced
+// with the following:
+//
+// SkCanvas canvas(device);
+// SkAutoTUnref<SkDevice> aur(device);
+//
+class OwnDeviceCanvas : public SkCanvas {
+public:
+ OwnDeviceCanvas(SkDevice* device) : SkCanvas(device) {
+ SkSafeUnref(device);
+ }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkDownSampleImageFilter::onFilterImage(Proxy* proxy, const SkBitmap& src,
+ const SkMatrix&,
+ SkBitmap* result, SkIPoint*) {
+ SkScalar scale = fScale;
+ if (scale > SK_Scalar1 || scale <= 0) {
+ return false;
+ }
+
+ int dstW = SkScalarRoundToInt(src.width() * scale);
+ int dstH = SkScalarRoundToInt(src.height() * scale);
+ if (dstW < 1) {
+ dstW = 1;
+ }
+ if (dstH < 1) {
+ dstH = 1;
+ }
+
+ SkBitmap tmp;
+
+ // downsample
+ {
+ SkDevice* dev = proxy->createDevice(dstW, dstH);
+ if (NULL == dev) {
+ return false;
+ }
+ OwnDeviceCanvas canvas(dev);
+ SkPaint paint;
+
+ paint.setFilterBitmap(true);
+ canvas.scale(scale, scale);
+ canvas.drawBitmap(src, 0, 0, &paint);
+ tmp = dev->accessBitmap(false);
+ }
+
+ // upscale
+ {
+ SkDevice* dev = proxy->createDevice(src.width(), src.height());
+ if (NULL == dev) {
+ return false;
+ }
+ OwnDeviceCanvas canvas(dev);
+
+ SkRect r = SkRect::MakeWH(SkIntToScalar(src.width()),
+ SkIntToScalar(src.height()));
+ canvas.drawBitmapRect(tmp, NULL, r, NULL);
+ *result = dev->accessBitmap(false);
+ }
+ return true;
+}
+
+void SkDownSampleImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+
+ buffer.writeScalar(fScale);
+}
+
+SkDownSampleImageFilter::SkDownSampleImageFilter(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ fScale = buffer.readScalar();
+}
diff --git a/effects/SkTransparentShader.cpp b/effects/SkTransparentShader.cpp
new file mode 100644
index 00000000..02744816
--- /dev/null
+++ b/effects/SkTransparentShader.cpp
@@ -0,0 +1,122 @@
+
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#include "SkTransparentShader.h"
+#include "SkColorPriv.h"
+#include "SkString.h"
+
+bool SkTransparentShader::setContext(const SkBitmap& device,
+ const SkPaint& paint,
+ const SkMatrix& matrix) {
+ fDevice = &device;
+ fAlpha = paint.getAlpha();
+
+ return this->INHERITED::setContext(device, paint, matrix);
+}
+
+uint32_t SkTransparentShader::getFlags() {
+ uint32_t flags = this->INHERITED::getFlags();
+
+ switch (fDevice->getConfig()) {
+ case SkBitmap::kRGB_565_Config:
+ flags |= kHasSpan16_Flag;
+ if (fAlpha == 255)
+ flags |= kOpaqueAlpha_Flag;
+ break;
+ case SkBitmap::kARGB_8888_Config:
+ if (fAlpha == 255 && fDevice->isOpaque())
+ flags |= kOpaqueAlpha_Flag;
+ break;
+ default:
+ break;
+ }
+ return flags;
+}
+
+void SkTransparentShader::shadeSpan(int x, int y, SkPMColor span[], int count) {
+ unsigned scale = SkAlpha255To256(fAlpha);
+
+ switch (fDevice->getConfig()) {
+ case SkBitmap::kARGB_8888_Config:
+ if (scale == 256) {
+ SkPMColor* src = fDevice->getAddr32(x, y);
+ if (src != span) {
+ memcpy(span, src, count * sizeof(SkPMColor));
+ }
+ } else {
+ const SkPMColor* src = fDevice->getAddr32(x, y);
+ for (int i = count - 1; i >= 0; --i) {
+ span[i] = SkAlphaMulQ(src[i], scale);
+ }
+ }
+ break;
+ case SkBitmap::kRGB_565_Config: {
+ const uint16_t* src = fDevice->getAddr16(x, y);
+ if (scale == 256) {
+ for (int i = count - 1; i >= 0; --i) {
+ span[i] = SkPixel16ToPixel32(src[i]);
+ }
+ } else {
+ unsigned alpha = fAlpha;
+ for (int i = count - 1; i >= 0; --i) {
+ uint16_t c = src[i];
+ unsigned r = SkPacked16ToR32(c);
+ unsigned g = SkPacked16ToG32(c);
+ unsigned b = SkPacked16ToB32(c);
+
+ span[i] = SkPackARGB32( alpha,
+ SkAlphaMul(r, scale),
+ SkAlphaMul(g, scale),
+ SkAlphaMul(b, scale));
+ }
+ }
+ break;
+ }
+ case SkBitmap::kIndex8_Config:
+ SkDEBUGFAIL("index8 not supported as a destination device");
+ break;
+ case SkBitmap::kA8_Config: {
+ const uint8_t* src = fDevice->getAddr8(x, y);
+ if (scale == 256) {
+ for (int i = count - 1; i >= 0; --i) {
+ span[i] = SkPackARGB32(src[i], 0, 0, 0);
+ }
+ } else {
+ for (int i = count - 1; i >= 0; --i) {
+ span[i] = SkPackARGB32(SkAlphaMul(src[i], scale), 0, 0, 0);
+ }
+ }
+ break;
+ }
+ case SkBitmap::kA1_Config:
+ SkDEBUGFAIL("kA1_Config umimplemented at this time");
+ break;
+ default: // to avoid warnings
+ break;
+ }
+}
+
+void SkTransparentShader::shadeSpan16(int x, int y, uint16_t span[], int count) {
+ SkASSERT(fDevice->getConfig() == SkBitmap::kRGB_565_Config);
+
+ uint16_t* src = fDevice->getAddr16(x, y);
+ if (src != span) {
+ memcpy(span, src, count << 1);
+ }
+}
+
+#ifdef SK_DEVELOPER
+void SkTransparentShader::toString(SkString* str) const {
+ str->append("SkTransparentShader: (");
+
+ this->INHERITED::toString(str);
+
+ str->append(")");
+}
+#endif
diff --git a/effects/SkXfermodeImageFilter.cpp b/effects/SkXfermodeImageFilter.cpp
new file mode 100644
index 00000000..898bad11
--- /dev/null
+++ b/effects/SkXfermodeImageFilter.cpp
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkXfermodeImageFilter.h"
+#include "SkCanvas.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkXfermode.h"
+#if SK_SUPPORT_GPU
+#include "GrContext.h"
+#include "effects/GrSimpleTextureEffect.h"
+#include "SkGr.h"
+#include "SkImageFilterUtils.h"
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkXfermodeImageFilter::SkXfermodeImageFilter(SkXfermode* mode,
+ SkImageFilter* background,
+ SkImageFilter* foreground)
+ : INHERITED(background, foreground), fMode(mode) {
+ SkSafeRef(fMode);
+}
+
+SkXfermodeImageFilter::~SkXfermodeImageFilter() {
+ SkSafeUnref(fMode);
+}
+
+SkXfermodeImageFilter::SkXfermodeImageFilter(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer) {
+ fMode = buffer.readFlattenableT<SkXfermode>();
+}
+
+void SkXfermodeImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeFlattenable(fMode);
+}
+
+bool SkXfermodeImageFilter::onFilterImage(Proxy* proxy,
+ const SkBitmap& src,
+ const SkMatrix& ctm,
+ SkBitmap* dst,
+ SkIPoint* offset) {
+ SkBitmap background = src, foreground = src;
+ SkImageFilter* backgroundInput = getInput(0);
+ SkImageFilter* foregroundInput = getInput(1);
+ SkIPoint backgroundOffset = SkIPoint::Make(0, 0);
+ if (backgroundInput &&
+ !backgroundInput->filterImage(proxy, src, ctm, &background, &backgroundOffset)) {
+ return false;
+ }
+ SkIPoint foregroundOffset = SkIPoint::Make(0, 0);
+ if (foregroundInput &&
+ !foregroundInput->filterImage(proxy, src, ctm, &foreground, &foregroundOffset)) {
+ return false;
+ }
+ dst->setConfig(background.config(), background.width(), background.height());
+ dst->allocPixels();
+ SkCanvas canvas(*dst);
+ SkPaint paint;
+ paint.setXfermodeMode(SkXfermode::kSrc_Mode);
+ canvas.drawBitmap(background, 0, 0, &paint);
+ paint.setXfermode(fMode);
+ canvas.drawBitmap(foreground,
+ SkIntToScalar(foregroundOffset.fX - backgroundOffset.fX),
+ SkIntToScalar(foregroundOffset.fY - backgroundOffset.fY),
+ &paint);
+ offset->fX += backgroundOffset.fX;
+ offset->fY += backgroundOffset.fY;
+ return true;
+}
+
+#if SK_SUPPORT_GPU
+
+bool SkXfermodeImageFilter::filterImageGPU(Proxy* proxy,
+ const SkBitmap& src,
+ const SkMatrix& ctm,
+ SkBitmap* result,
+ SkIPoint* offset) {
+ SkBitmap background;
+ SkIPoint backgroundOffset = SkIPoint::Make(0, 0);
+ if (!SkImageFilterUtils::GetInputResultGPU(getInput(0), proxy, src, ctm, &background,
+ &backgroundOffset)) {
+ return false;
+ }
+ GrTexture* backgroundTex = background.getTexture();
+ SkBitmap foreground;
+ SkIPoint foregroundOffset = SkIPoint::Make(0, 0);
+ if (!SkImageFilterUtils::GetInputResultGPU(getInput(1), proxy, src, ctm, &foreground,
+ &foregroundOffset)) {
+ return false;
+ }
+ GrTexture* foregroundTex = foreground.getTexture();
+ GrContext* context = foregroundTex->getContext();
+
+ GrEffectRef* xferEffect = NULL;
+
+ GrTextureDesc desc;
+ desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
+ desc.fWidth = src.width();
+ desc.fHeight = src.height();
+ desc.fConfig = kSkia8888_GrPixelConfig;
+
+ GrAutoScratchTexture ast(context, desc);
+ SkAutoTUnref<GrTexture> dst(ast.detach());
+
+ GrContext::AutoRenderTarget art(context, dst->asRenderTarget());
+
+ SkXfermode::Coeff sm, dm;
+ if (!SkXfermode::AsNewEffectOrCoeff(fMode, context, &xferEffect, &sm, &dm, backgroundTex)) {
+ return false;
+ }
+
+ SkMatrix foregroundMatrix = GrEffect::MakeDivByTextureWHMatrix(foregroundTex);
+ foregroundMatrix.preTranslate(SkIntToScalar(backgroundOffset.fX-foregroundOffset.fX),
+ SkIntToScalar(backgroundOffset.fY-foregroundOffset.fY));
+
+
+ SkRect srcRect;
+ src.getBounds(&srcRect);
+ if (NULL != xferEffect) {
+ GrPaint paint;
+ paint.addColorTextureEffect(foregroundTex, foregroundMatrix);
+ paint.addColorEffect(xferEffect)->unref();
+ context->drawRect(paint, srcRect);
+ } else {
+ GrPaint backgroundPaint;
+ SkMatrix backgroundMatrix = GrEffect::MakeDivByTextureWHMatrix(backgroundTex);
+ backgroundPaint.addColorTextureEffect(backgroundTex, backgroundMatrix);
+ context->drawRect(backgroundPaint, srcRect);
+
+ GrPaint foregroundPaint;
+ foregroundPaint.setBlendFunc(sk_blend_to_grblend(sm), sk_blend_to_grblend(dm));
+ foregroundPaint.addColorTextureEffect(foregroundTex, foregroundMatrix);
+ context->drawRect(foregroundPaint, srcRect);
+ }
+ offset->fX += backgroundOffset.fX;
+ offset->fY += backgroundOffset.fY;
+ return SkImageFilterUtils::WrapTexture(dst, src.width(), src.height(), result);
+}
+
+#endif
diff --git a/effects/gradients/SkBitmapCache.cpp b/effects/gradients/SkBitmapCache.cpp
new file mode 100644
index 00000000..95175e4f
--- /dev/null
+++ b/effects/gradients/SkBitmapCache.cpp
@@ -0,0 +1,153 @@
+
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#include "SkBitmapCache.h"
+
+struct SkBitmapCache::Entry {
+ Entry* fPrev;
+ Entry* fNext;
+
+ void* fBuffer;
+ size_t fSize;
+ SkBitmap fBitmap;
+
+ Entry(const void* buffer, size_t size, const SkBitmap& bm)
+ : fPrev(NULL),
+ fNext(NULL),
+ fBitmap(bm) {
+ fBuffer = sk_malloc_throw(size);
+ fSize = size;
+ memcpy(fBuffer, buffer, size);
+ }
+
+ ~Entry() { sk_free(fBuffer); }
+
+ bool equals(const void* buffer, size_t size) const {
+ return (fSize == size) && !memcmp(fBuffer, buffer, size);
+ }
+};
+
+SkBitmapCache::SkBitmapCache(int max) : fMaxEntries(max) {
+ fEntryCount = 0;
+ fHead = fTail = NULL;
+
+ this->validate();
+}
+
+SkBitmapCache::~SkBitmapCache() {
+ this->validate();
+
+ Entry* entry = fHead;
+ while (entry) {
+ Entry* next = entry->fNext;
+ delete entry;
+ entry = next;
+ }
+}
+
+SkBitmapCache::Entry* SkBitmapCache::detach(Entry* entry) const {
+ if (entry->fPrev) {
+ SkASSERT(fHead != entry);
+ entry->fPrev->fNext = entry->fNext;
+ } else {
+ SkASSERT(fHead == entry);
+ fHead = entry->fNext;
+ }
+ if (entry->fNext) {
+ SkASSERT(fTail != entry);
+ entry->fNext->fPrev = entry->fPrev;
+ } else {
+ SkASSERT(fTail == entry);
+ fTail = entry->fPrev;
+ }
+ return entry;
+}
+
+void SkBitmapCache::attachToHead(Entry* entry) const {
+ entry->fPrev = NULL;
+ entry->fNext = fHead;
+ if (fHead) {
+ fHead->fPrev = entry;
+ } else {
+ fTail = entry;
+ }
+ fHead = entry;
+}
+
+bool SkBitmapCache::find(const void* buffer, size_t size, SkBitmap* bm) const {
+ AutoValidate av(this);
+
+ Entry* entry = fHead;
+ while (entry) {
+ if (entry->equals(buffer, size)) {
+ if (bm) {
+ *bm = entry->fBitmap;
+ }
+ // move to the head of our list, so we purge it last
+ this->detach(entry);
+ this->attachToHead(entry);
+ return true;
+ }
+ entry = entry->fNext;
+ }
+ return false;
+}
+
+void SkBitmapCache::add(const void* buffer, size_t len, const SkBitmap& bm) {
+ AutoValidate av(this);
+
+ if (fEntryCount == fMaxEntries) {
+ SkASSERT(fTail);
+ delete this->detach(fTail);
+ fEntryCount -= 1;
+ }
+
+ Entry* entry = SkNEW_ARGS(Entry, (buffer, len, bm));
+ this->attachToHead(entry);
+ fEntryCount += 1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_DEBUG
+
+void SkBitmapCache::validate() const {
+ SkASSERT(fEntryCount >= 0 && fEntryCount <= fMaxEntries);
+
+ if (fEntryCount > 0) {
+ SkASSERT(NULL == fHead->fPrev);
+ SkASSERT(NULL == fTail->fNext);
+
+ if (fEntryCount == 1) {
+ SkASSERT(fHead == fTail);
+ } else {
+ SkASSERT(fHead != fTail);
+ }
+
+ Entry* entry = fHead;
+ int count = 0;
+ while (entry) {
+ count += 1;
+ entry = entry->fNext;
+ }
+ SkASSERT(count == fEntryCount);
+
+ entry = fTail;
+ while (entry) {
+ count -= 1;
+ entry = entry->fPrev;
+ }
+ SkASSERT(0 == count);
+ } else {
+ SkASSERT(NULL == fHead);
+ SkASSERT(NULL == fTail);
+ }
+}
+
+#endif
diff --git a/effects/gradients/SkBitmapCache.h b/effects/gradients/SkBitmapCache.h
new file mode 100644
index 00000000..d2af5e13
--- /dev/null
+++ b/effects/gradients/SkBitmapCache.h
@@ -0,0 +1,49 @@
+
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#ifndef SkBitmapCache_DEFINED
+#define SkBitmapCache_DEFINED
+
+#include "SkBitmap.h"
+
+class SkBitmapCache : SkNoncopyable {
+public:
+ SkBitmapCache(int maxEntries);
+ ~SkBitmapCache();
+
+ bool find(const void* buffer, size_t len, SkBitmap*) const;
+ void add(const void* buffer, size_t len, const SkBitmap&);
+
+private:
+ int fEntryCount;
+ const int fMaxEntries;
+
+ struct Entry;
+ mutable Entry* fHead;
+ mutable Entry* fTail;
+
+ inline Entry* detach(Entry*) const;
+ inline void attachToHead(Entry*) const;
+
+#ifdef SK_DEBUG
+ void validate() const;
+#else
+ void validate() const {}
+#endif
+
+ class AutoValidate : SkNoncopyable {
+ public:
+ AutoValidate(const SkBitmapCache* bc) : fBC(bc) { bc->validate(); }
+ ~AutoValidate() { fBC->validate(); }
+ private:
+ const SkBitmapCache* fBC;
+ };
+};
+
+#endif
diff --git a/effects/gradients/SkClampRange.cpp b/effects/gradients/SkClampRange.cpp
new file mode 100644
index 00000000..3e2ca8e9
--- /dev/null
+++ b/effects/gradients/SkClampRange.cpp
@@ -0,0 +1,165 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#include "SkClampRange.h"
+
+/*
+ * returns [0..count] for the number of steps (<= count) for which x0 <= edge
+ * given each step is followed by x0 += dx
+ */
+static int chop(int64_t x0, SkFixed edge, int64_t x1, int64_t dx, int count) {
+ SkASSERT(dx > 0);
+ SkASSERT(count >= 0);
+
+ if (x0 >= edge) {
+ return 0;
+ }
+ if (x1 <= edge) {
+ return count;
+ }
+ int64_t n = (edge - x0 + dx - 1) / dx;
+ SkASSERT(n >= 0);
+ SkASSERT(n <= count);
+ return (int)n;
+}
+
+static bool overflows_fixed(int64_t x) {
+ return x < -SK_FixedMax || x > SK_FixedMax;
+}
+
+void SkClampRange::initFor1(SkFixed fx) {
+ fCount0 = fCount1 = fCount2 = 0;
+ if (fx <= 0) {
+ fCount0 = 1;
+ } else if (fx < 0xFFFF) {
+ fCount1 = 1;
+ fFx1 = fx;
+ } else {
+ fCount2 = 1;
+ }
+}
+
+void SkClampRange::init(SkFixed fx0, SkFixed dx0, int count, int v0, int v1) {
+ SkASSERT(count > 0);
+
+ fV0 = v0;
+ fV1 = v1;
+ fOverflowed = false;
+
+ // special case 1 == count, as it is slightly common for skia
+ // and avoids us ever calling divide or 64bit multiply
+ if (1 == count) {
+ this->initFor1(fx0);
+ return;
+ }
+
+ int64_t fx = fx0;
+ int64_t dx = dx0;
+ // start with ex equal to the last computed value
+ int64_t ex = fx + (count - 1) * dx;
+ fOverflowed = overflows_fixed(ex);
+
+ if ((uint64_t)(fx | ex) <= 0xFFFF) {
+ fCount0 = fCount2 = 0;
+ fCount1 = count;
+ fFx1 = fx0;
+ return;
+ }
+ if (fx <= 0 && ex <= 0) {
+ fCount1 = fCount2 = 0;
+ fCount0 = count;
+ return;
+ }
+ if (fx >= 0xFFFF && ex >= 0xFFFF) {
+ fCount0 = fCount1 = 0;
+ fCount2 = count;
+ return;
+ }
+
+ int extraCount = 0;
+
+ // now make ex be 1 past the last computed value
+ ex += dx;
+ fOverflowed = overflows_fixed(ex);
+ // now check for over/under flow
+ if (fOverflowed) {
+ int originalCount = count;
+ int64_t ccount;
+ bool swap = dx < 0;
+ if (swap) {
+ dx = -dx;
+ fx = -fx;
+ }
+ ccount = (SK_FixedMax - fx + dx - 1) / dx;
+ if (swap) {
+ dx = -dx;
+ fx = -fx;
+ }
+ SkASSERT(ccount > 0 && ccount <= SK_FixedMax);
+
+ count = (int)ccount;
+ if (0 == count) {
+ this->initFor1(fx0);
+ if (dx > 0) {
+ fCount2 += originalCount - 1;
+ } else {
+ fCount0 += originalCount - 1;
+ }
+ return;
+ }
+ extraCount = originalCount - count;
+ ex = fx + dx * count;
+ }
+
+ bool doSwap = dx < 0;
+
+ if (doSwap) {
+ ex -= dx;
+ fx -= dx;
+ SkTSwap(fx, ex);
+ dx = -dx;
+ }
+
+
+ fCount0 = chop(fx, 0, ex, dx, count);
+ count -= fCount0;
+ fx += fCount0 * dx;
+ SkASSERT(fx >= 0);
+ SkASSERT(fCount0 == 0 || (fx - dx) < 0);
+ fCount1 = chop(fx, 0xFFFF, ex, dx, count);
+ count -= fCount1;
+ fCount2 = count;
+
+#ifdef SK_DEBUG
+ fx += fCount1 * dx;
+ SkASSERT(fx <= ex);
+ if (fCount2 > 0) {
+ SkASSERT(fx >= 0xFFFF);
+ if (fCount1 > 0) {
+ SkASSERT(fx - dx < 0xFFFF);
+ }
+ }
+#endif
+
+ if (doSwap) {
+ SkTSwap(fCount0, fCount2);
+ SkTSwap(fV0, fV1);
+ dx = -dx;
+ }
+
+ if (fCount1 > 0) {
+ fFx1 = fx0 + fCount0 * (int)dx;
+ }
+
+ if (dx > 0) {
+ fCount2 += extraCount;
+ } else {
+ fCount0 += extraCount;
+ }
+}
diff --git a/effects/gradients/SkClampRange.h b/effects/gradients/SkClampRange.h
new file mode 100644
index 00000000..376dc93b
--- /dev/null
+++ b/effects/gradients/SkClampRange.h
@@ -0,0 +1,38 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#ifndef SkClampRange_DEFINED
+#define SkClampRange_DEFINED
+
+#include "SkFixed.h"
+
+/**
+ * Iteration fixed fx by dx, clamping as you go to [0..0xFFFF], this class
+ * computes the (up to) 3 spans there are:
+ *
+ * range0: use constant value V0
+ * range1: iterate as usual fx += dx
+ * range2: use constant value V1
+ */
+struct SkClampRange {
+ int fCount0; // count for fV0
+ int fCount1; // count for interpolating (fV0...fV1)
+ int fCount2; // count for fV1
+ SkFixed fFx1; // initial fx value for the fCount1 range.
+ // only valid if fCount1 > 0
+ int fV0, fV1;
+ bool fOverflowed; // true if we had to clamp due to numerical overflow
+
+ void init(SkFixed fx, SkFixed dx, int count, int v0, int v1);
+
+private:
+ void initFor1(SkFixed fx);
+};
+
+#endif
diff --git a/effects/gradients/SkGradientShader.cpp b/effects/gradients/SkGradientShader.cpp
new file mode 100644
index 00000000..de43b69a
--- /dev/null
+++ b/effects/gradients/SkGradientShader.cpp
@@ -0,0 +1,986 @@
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkGradientShaderPriv.h"
+#include "SkLinearGradient.h"
+#include "SkRadialGradient.h"
+#include "SkTwoPointRadialGradient.h"
+#include "SkTwoPointConicalGradient.h"
+#include "SkSweepGradient.h"
+
+SkGradientShaderBase::SkGradientShaderBase(const Descriptor& desc) {
+ SkASSERT(desc.fCount > 1);
+
+ fCacheAlpha = 256; // init to a value that paint.getAlpha() can't return
+
+ fMapper = desc.fMapper;
+ SkSafeRef(fMapper);
+ fGradFlags = SkToU8(desc.fFlags);
+
+ SkASSERT((unsigned)desc.fTileMode < SkShader::kTileModeCount);
+ SkASSERT(SkShader::kTileModeCount == SK_ARRAY_COUNT(gTileProcs));
+ fTileMode = desc.fTileMode;
+ fTileProc = gTileProcs[desc.fTileMode];
+
+ fCache16 = fCache16Storage = NULL;
+ fCache32 = NULL;
+ fCache32PixelRef = NULL;
+
+ /* Note: we let the caller skip the first and/or last position.
+ i.e. pos[0] = 0.3, pos[1] = 0.7
+ In these cases, we insert dummy entries to ensure that the final data
+ will be bracketed by [0, 1].
+ i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1
+
+ Thus colorCount (the caller's value, and fColorCount (our value) may
+ differ by up to 2. In the above example:
+ colorCount = 2
+ fColorCount = 4
+ */
+ fColorCount = desc.fCount;
+ // check if we need to add in dummy start and/or end position/colors
+ bool dummyFirst = false;
+ bool dummyLast = false;
+ if (desc.fPos) {
+ dummyFirst = desc.fPos[0] != 0;
+ dummyLast = desc.fPos[desc.fCount - 1] != SK_Scalar1;
+ fColorCount += dummyFirst + dummyLast;
+ }
+
+ if (fColorCount > kColorStorageCount) {
+ size_t size = sizeof(SkColor) + sizeof(Rec);
+ fOrigColors = reinterpret_cast<SkColor*>(
+ sk_malloc_throw(size * fColorCount));
+ }
+ else {
+ fOrigColors = fStorage;
+ }
+
+ // Now copy over the colors, adding the dummies as needed
+ {
+ SkColor* origColors = fOrigColors;
+ if (dummyFirst) {
+ *origColors++ = desc.fColors[0];
+ }
+ memcpy(origColors, desc.fColors, desc.fCount * sizeof(SkColor));
+ if (dummyLast) {
+ origColors += desc.fCount;
+ *origColors = desc.fColors[desc.fCount - 1];
+ }
+ }
+
+ fRecs = (Rec*)(fOrigColors + fColorCount);
+ if (fColorCount > 2) {
+ Rec* recs = fRecs;
+ recs->fPos = 0;
+ // recs->fScale = 0; // unused;
+ recs += 1;
+ if (desc.fPos) {
+ /* We need to convert the user's array of relative positions into
+ fixed-point positions and scale factors. We need these results
+ to be strictly monotonic (no two values equal or out of order).
+ Hence this complex loop that just jams a zero for the scale
+ value if it sees a segment out of order, and it assures that
+ we start at 0 and end at 1.0
+ */
+ SkFixed prev = 0;
+ int startIndex = dummyFirst ? 0 : 1;
+ int count = desc.fCount + dummyLast;
+ for (int i = startIndex; i < count; i++) {
+ // force the last value to be 1.0
+ SkFixed curr;
+ if (i == desc.fCount) { // we're really at the dummyLast
+ curr = SK_Fixed1;
+ } else {
+ curr = SkScalarToFixed(desc.fPos[i]);
+ }
+ // pin curr withing range
+ if (curr < 0) {
+ curr = 0;
+ } else if (curr > SK_Fixed1) {
+ curr = SK_Fixed1;
+ }
+ recs->fPos = curr;
+ if (curr > prev) {
+ recs->fScale = (1 << 24) / (curr - prev);
+ } else {
+ recs->fScale = 0; // ignore this segment
+ }
+ // get ready for the next value
+ prev = curr;
+ recs += 1;
+ }
+ } else { // assume even distribution
+ SkFixed dp = SK_Fixed1 / (desc.fCount - 1);
+ SkFixed p = dp;
+ SkFixed scale = (desc.fCount - 1) << 8; // (1 << 24) / dp
+ for (int i = 1; i < desc.fCount; i++) {
+ recs->fPos = p;
+ recs->fScale = scale;
+ recs += 1;
+ p += dp;
+ }
+ }
+ }
+ this->initCommon();
+}
+
+static uint32_t pack_mode_flags(SkShader::TileMode mode, uint32_t flags) {
+ SkASSERT(0 == (flags >> 28));
+ SkASSERT(0 == ((uint32_t)mode >> 4));
+ return (flags << 4) | mode;
+}
+
+static SkShader::TileMode unpack_mode(uint32_t packed) {
+ return (SkShader::TileMode)(packed & 0xF);
+}
+
+static uint32_t unpack_flags(uint32_t packed) {
+ return packed >> 4;
+}
+
+SkGradientShaderBase::SkGradientShaderBase(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+ fCacheAlpha = 256;
+
+ fMapper = buffer.readFlattenableT<SkUnitMapper>();
+
+ fCache16 = fCache16Storage = NULL;
+ fCache32 = NULL;
+ fCache32PixelRef = NULL;
+
+ int colorCount = fColorCount = buffer.getArrayCount();
+ if (colorCount > kColorStorageCount) {
+ size_t size = sizeof(SkColor) + sizeof(SkPMColor) + sizeof(Rec);
+ fOrigColors = (SkColor*)sk_malloc_throw(size * colorCount);
+ } else {
+ fOrigColors = fStorage;
+ }
+ buffer.readColorArray(fOrigColors);
+
+ {
+ uint32_t packed = buffer.readUInt();
+ fGradFlags = SkToU8(unpack_flags(packed));
+ fTileMode = unpack_mode(packed);
+ }
+ fTileProc = gTileProcs[fTileMode];
+ fRecs = (Rec*)(fOrigColors + colorCount);
+ if (colorCount > 2) {
+ Rec* recs = fRecs;
+ recs[0].fPos = 0;
+ for (int i = 1; i < colorCount; i++) {
+ recs[i].fPos = buffer.readInt();
+ recs[i].fScale = buffer.readUInt();
+ }
+ }
+ buffer.readMatrix(&fPtsToUnit);
+ this->initCommon();
+}
+
+SkGradientShaderBase::~SkGradientShaderBase() {
+ if (fCache16Storage) {
+ sk_free(fCache16Storage);
+ }
+ SkSafeUnref(fCache32PixelRef);
+ if (fOrigColors != fStorage) {
+ sk_free(fOrigColors);
+ }
+ SkSafeUnref(fMapper);
+}
+
+void SkGradientShaderBase::initCommon() {
+ fFlags = 0;
+ unsigned colorAlpha = 0xFF;
+ for (int i = 0; i < fColorCount; i++) {
+ colorAlpha &= SkColorGetA(fOrigColors[i]);
+ }
+ fColorsAreOpaque = colorAlpha == 0xFF;
+}
+
+void SkGradientShaderBase::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writeFlattenable(fMapper);
+ buffer.writeColorArray(fOrigColors, fColorCount);
+ buffer.writeUInt(pack_mode_flags(fTileMode, fGradFlags));
+ if (fColorCount > 2) {
+ Rec* recs = fRecs;
+ for (int i = 1; i < fColorCount; i++) {
+ buffer.writeInt(recs[i].fPos);
+ buffer.writeUInt(recs[i].fScale);
+ }
+ }
+ buffer.writeMatrix(fPtsToUnit);
+}
+
+bool SkGradientShaderBase::isOpaque() const {
+ return fColorsAreOpaque;
+}
+
+bool SkGradientShaderBase::setContext(const SkBitmap& device,
+ const SkPaint& paint,
+ const SkMatrix& matrix) {
+ if (!this->INHERITED::setContext(device, paint, matrix)) {
+ return false;
+ }
+
+ const SkMatrix& inverse = this->getTotalInverse();
+
+ if (!fDstToIndex.setConcat(fPtsToUnit, inverse)) {
+ // need to keep our set/end context calls balanced.
+ this->INHERITED::endContext();
+ return false;
+ }
+
+ fDstToIndexProc = fDstToIndex.getMapXYProc();
+ fDstToIndexClass = (uint8_t)SkShader::ComputeMatrixClass(fDstToIndex);
+
+ // now convert our colors in to PMColors
+ unsigned paintAlpha = this->getPaintAlpha();
+
+ fFlags = this->INHERITED::getFlags();
+ if (fColorsAreOpaque && paintAlpha == 0xFF) {
+ fFlags |= kOpaqueAlpha_Flag;
+ }
+ // we can do span16 as long as our individual colors are opaque,
+ // regardless of the paint's alpha
+ if (fColorsAreOpaque) {
+ fFlags |= kHasSpan16_Flag;
+ }
+
+ this->setCacheAlpha(paintAlpha);
+ return true;
+}
+
+void SkGradientShaderBase::setCacheAlpha(U8CPU alpha) const {
+ // if the new alpha differs from the previous time we were called, inval our cache
+ // this will trigger the cache to be rebuilt.
+ // we don't care about the first time, since the cache ptrs will already be NULL
+ if (fCacheAlpha != alpha) {
+ fCache16 = NULL; // inval the cache
+ fCache32 = NULL; // inval the cache
+ fCacheAlpha = alpha; // record the new alpha
+ // inform our subclasses
+ if (fCache32PixelRef) {
+ fCache32PixelRef->notifyPixelsChanged();
+ }
+ }
+}
+
+#define Fixed_To_Dot8(x) (((x) + 0x80) >> 8)
+
+/** We take the original colors, not our premultiplied PMColors, since we can
+ build a 16bit table as long as the original colors are opaque, even if the
+ paint specifies a non-opaque alpha.
+*/
+void SkGradientShaderBase::Build16bitCache(uint16_t cache[], SkColor c0, SkColor c1,
+ int count) {
+ SkASSERT(count > 1);
+ SkASSERT(SkColorGetA(c0) == 0xFF);
+ SkASSERT(SkColorGetA(c1) == 0xFF);
+
+ SkFixed r = SkColorGetR(c0);
+ SkFixed g = SkColorGetG(c0);
+ SkFixed b = SkColorGetB(c0);
+
+ SkFixed dr = SkIntToFixed(SkColorGetR(c1) - r) / (count - 1);
+ SkFixed dg = SkIntToFixed(SkColorGetG(c1) - g) / (count - 1);
+ SkFixed db = SkIntToFixed(SkColorGetB(c1) - b) / (count - 1);
+
+ r = SkIntToFixed(r) + 0x8000;
+ g = SkIntToFixed(g) + 0x8000;
+ b = SkIntToFixed(b) + 0x8000;
+
+ do {
+ unsigned rr = r >> 16;
+ unsigned gg = g >> 16;
+ unsigned bb = b >> 16;
+ cache[0] = SkPackRGB16(SkR32ToR16(rr), SkG32ToG16(gg), SkB32ToB16(bb));
+ cache[kCache16Count] = SkDitherPack888ToRGB16(rr, gg, bb);
+ cache += 1;
+ r += dr;
+ g += dg;
+ b += db;
+ } while (--count != 0);
+}
+
+/*
+ * r,g,b used to be SkFixed, but on gcc (4.2.1 mac and 4.6.3 goobuntu) in
+ * release builds, we saw a compiler error where the 0xFF parameter in
+ * SkPackARGB32() was being totally ignored whenever it was called with
+ * a non-zero add (e.g. 0x8000).
+ *
+ * We found two work-arounds:
+ * 1. change r,g,b to unsigned (or just one of them)
+ * 2. change SkPackARGB32 to + its (a << SK_A32_SHIFT) value instead
+ * of using |
+ *
+ * We chose #1 just because it was more localized.
+ * See http://code.google.com/p/skia/issues/detail?id=1113
+ *
+ * The type SkUFixed encapsulate this need for unsigned, but logically Fixed.
+ */
+typedef uint32_t SkUFixed;
+
+void SkGradientShaderBase::Build32bitCache(SkPMColor cache[], SkColor c0, SkColor c1,
+ int count, U8CPU paintAlpha, uint32_t gradFlags) {
+ SkASSERT(count > 1);
+
+ // need to apply paintAlpha to our two endpoints
+ uint32_t a0 = SkMulDiv255Round(SkColorGetA(c0), paintAlpha);
+ uint32_t a1 = SkMulDiv255Round(SkColorGetA(c1), paintAlpha);
+
+
+ const bool interpInPremul = SkToBool(gradFlags &
+ SkGradientShader::kInterpolateColorsInPremul_Flag);
+
+ uint32_t r0 = SkColorGetR(c0);
+ uint32_t g0 = SkColorGetG(c0);
+ uint32_t b0 = SkColorGetB(c0);
+
+ uint32_t r1 = SkColorGetR(c1);
+ uint32_t g1 = SkColorGetG(c1);
+ uint32_t b1 = SkColorGetB(c1);
+
+ if (interpInPremul) {
+ r0 = SkMulDiv255Round(r0, a0);
+ g0 = SkMulDiv255Round(g0, a0);
+ b0 = SkMulDiv255Round(b0, a0);
+
+ r1 = SkMulDiv255Round(r1, a1);
+ g1 = SkMulDiv255Round(g1, a1);
+ b1 = SkMulDiv255Round(b1, a1);
+ }
+
+ SkFixed da = SkIntToFixed(a1 - a0) / (count - 1);
+ SkFixed dr = SkIntToFixed(r1 - r0) / (count - 1);
+ SkFixed dg = SkIntToFixed(g1 - g0) / (count - 1);
+ SkFixed db = SkIntToFixed(b1 - b0) / (count - 1);
+
+ /* We pre-add 1/8 to avoid having to add this to our [0] value each time
+ in the loop. Without this, the bias for each would be
+ 0x2000 0xA000 0xE000 0x6000
+ With this trick, we can add 0 for the first (no-op) and just adjust the
+ others.
+ */
+ SkUFixed a = SkIntToFixed(a0) + 0x2000;
+ SkUFixed r = SkIntToFixed(r0) + 0x2000;
+ SkUFixed g = SkIntToFixed(g0) + 0x2000;
+ SkUFixed b = SkIntToFixed(b0) + 0x2000;
+
+ /*
+ * Our dither-cell (spatially) is
+ * 0 2
+ * 3 1
+ * Where
+ * [0] -> [-1/8 ... 1/8 ) values near 0
+ * [1] -> [ 1/8 ... 3/8 ) values near 1/4
+ * [2] -> [ 3/8 ... 5/8 ) values near 1/2
+ * [3] -> [ 5/8 ... 7/8 ) values near 3/4
+ */
+
+ if (0xFF == a0 && 0 == da) {
+ do {
+ cache[kCache32Count*0] = SkPackARGB32(0xFF, (r + 0 ) >> 16,
+ (g + 0 ) >> 16,
+ (b + 0 ) >> 16);
+ cache[kCache32Count*1] = SkPackARGB32(0xFF, (r + 0x8000) >> 16,
+ (g + 0x8000) >> 16,
+ (b + 0x8000) >> 16);
+ cache[kCache32Count*2] = SkPackARGB32(0xFF, (r + 0xC000) >> 16,
+ (g + 0xC000) >> 16,
+ (b + 0xC000) >> 16);
+ cache[kCache32Count*3] = SkPackARGB32(0xFF, (r + 0x4000) >> 16,
+ (g + 0x4000) >> 16,
+ (b + 0x4000) >> 16);
+ cache += 1;
+ r += dr;
+ g += dg;
+ b += db;
+ } while (--count != 0);
+ } else if (interpInPremul) {
+ do {
+ cache[kCache32Count*0] = SkPackARGB32((a + 0 ) >> 16,
+ (r + 0 ) >> 16,
+ (g + 0 ) >> 16,
+ (b + 0 ) >> 16);
+ cache[kCache32Count*1] = SkPackARGB32((a + 0x8000) >> 16,
+ (r + 0x8000) >> 16,
+ (g + 0x8000) >> 16,
+ (b + 0x8000) >> 16);
+ cache[kCache32Count*2] = SkPackARGB32((a + 0xC000) >> 16,
+ (r + 0xC000) >> 16,
+ (g + 0xC000) >> 16,
+ (b + 0xC000) >> 16);
+ cache[kCache32Count*3] = SkPackARGB32((a + 0x4000) >> 16,
+ (r + 0x4000) >> 16,
+ (g + 0x4000) >> 16,
+ (b + 0x4000) >> 16);
+ cache += 1;
+ a += da;
+ r += dr;
+ g += dg;
+ b += db;
+ } while (--count != 0);
+ } else { // interpolate in unpreml space
+ do {
+ cache[kCache32Count*0] = SkPremultiplyARGBInline((a + 0 ) >> 16,
+ (r + 0 ) >> 16,
+ (g + 0 ) >> 16,
+ (b + 0 ) >> 16);
+ cache[kCache32Count*1] = SkPremultiplyARGBInline((a + 0x8000) >> 16,
+ (r + 0x8000) >> 16,
+ (g + 0x8000) >> 16,
+ (b + 0x8000) >> 16);
+ cache[kCache32Count*2] = SkPremultiplyARGBInline((a + 0xC000) >> 16,
+ (r + 0xC000) >> 16,
+ (g + 0xC000) >> 16,
+ (b + 0xC000) >> 16);
+ cache[kCache32Count*3] = SkPremultiplyARGBInline((a + 0x4000) >> 16,
+ (r + 0x4000) >> 16,
+ (g + 0x4000) >> 16,
+ (b + 0x4000) >> 16);
+ cache += 1;
+ a += da;
+ r += dr;
+ g += dg;
+ b += db;
+ } while (--count != 0);
+ }
+}
+
+static inline int SkFixedToFFFF(SkFixed x) {
+ SkASSERT((unsigned)x <= SK_Fixed1);
+ return x - (x >> 16);
+}
+
+static inline U16CPU bitsTo16(unsigned x, const unsigned bits) {
+ SkASSERT(x < (1U << bits));
+ if (6 == bits) {
+ return (x << 10) | (x << 4) | (x >> 2);
+ }
+ if (8 == bits) {
+ return (x << 8) | x;
+ }
+ sk_throw();
+ return 0;
+}
+
+const uint16_t* SkGradientShaderBase::getCache16() const {
+ if (fCache16 == NULL) {
+ // double the count for dither entries
+ const int entryCount = kCache16Count * 2;
+ const size_t allocSize = sizeof(uint16_t) * entryCount;
+
+ if (fCache16Storage == NULL) { // set the storage and our working ptr
+ fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
+ }
+ fCache16 = fCache16Storage;
+ if (fColorCount == 2) {
+ Build16bitCache(fCache16, fOrigColors[0], fOrigColors[1],
+ kCache16Count);
+ } else {
+ Rec* rec = fRecs;
+ int prevIndex = 0;
+ for (int i = 1; i < fColorCount; i++) {
+ int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache16Shift;
+ SkASSERT(nextIndex < kCache16Count);
+
+ if (nextIndex > prevIndex)
+ Build16bitCache(fCache16 + prevIndex, fOrigColors[i-1], fOrigColors[i], nextIndex - prevIndex + 1);
+ prevIndex = nextIndex;
+ }
+ }
+
+ if (fMapper) {
+ fCache16Storage = (uint16_t*)sk_malloc_throw(allocSize);
+ uint16_t* linear = fCache16; // just computed linear data
+ uint16_t* mapped = fCache16Storage; // storage for mapped data
+ SkUnitMapper* map = fMapper;
+ for (int i = 0; i < kCache16Count; i++) {
+ int index = map->mapUnit16(bitsTo16(i, kCache16Bits)) >> kCache16Shift;
+ mapped[i] = linear[index];
+ mapped[i + kCache16Count] = linear[index + kCache16Count];
+ }
+ sk_free(fCache16);
+ fCache16 = fCache16Storage;
+ }
+ }
+ return fCache16;
+}
+
+const SkPMColor* SkGradientShaderBase::getCache32() const {
+ if (fCache32 == NULL) {
+ // double the count for dither entries
+ const int entryCount = kCache32Count * 4;
+ const size_t allocSize = sizeof(SkPMColor) * entryCount;
+
+ if (NULL == fCache32PixelRef) {
+ fCache32PixelRef = SkNEW_ARGS(SkMallocPixelRef,
+ (NULL, allocSize, NULL));
+ }
+ fCache32 = (SkPMColor*)fCache32PixelRef->getAddr();
+ if (fColorCount == 2) {
+ Build32bitCache(fCache32, fOrigColors[0], fOrigColors[1],
+ kCache32Count, fCacheAlpha, fGradFlags);
+ } else {
+ Rec* rec = fRecs;
+ int prevIndex = 0;
+ for (int i = 1; i < fColorCount; i++) {
+ int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache32Shift;
+ SkASSERT(nextIndex < kCache32Count);
+
+ if (nextIndex > prevIndex)
+ Build32bitCache(fCache32 + prevIndex, fOrigColors[i-1],
+ fOrigColors[i], nextIndex - prevIndex + 1,
+ fCacheAlpha, fGradFlags);
+ prevIndex = nextIndex;
+ }
+ }
+
+ if (fMapper) {
+ SkMallocPixelRef* newPR = SkNEW_ARGS(SkMallocPixelRef,
+ (NULL, allocSize, NULL));
+ SkPMColor* linear = fCache32; // just computed linear data
+ SkPMColor* mapped = (SkPMColor*)newPR->getAddr(); // storage for mapped data
+ SkUnitMapper* map = fMapper;
+ for (int i = 0; i < kCache32Count; i++) {
+ int index = map->mapUnit16((i << 8) | i) >> 8;
+ mapped[i + kCache32Count*0] = linear[index + kCache32Count*0];
+ mapped[i + kCache32Count*1] = linear[index + kCache32Count*1];
+ mapped[i + kCache32Count*2] = linear[index + kCache32Count*2];
+ mapped[i + kCache32Count*3] = linear[index + kCache32Count*3];
+ }
+ fCache32PixelRef->unref();
+ fCache32PixelRef = newPR;
+ fCache32 = (SkPMColor*)newPR->getAddr();
+ }
+ }
+ return fCache32;
+}
+
+/*
+ * Because our caller might rebuild the same (logically the same) gradient
+ * over and over, we'd like to return exactly the same "bitmap" if possible,
+ * allowing the client to utilize a cache of our bitmap (e.g. with a GPU).
+ * To do that, we maintain a private cache of built-bitmaps, based on our
+ * colors and positions. Note: we don't try to flatten the fMapper, so if one
+ * is present, we skip the cache for now.
+ */
+void SkGradientShaderBase::getGradientTableBitmap(SkBitmap* bitmap) const {
+ // our caller assumes no external alpha, so we ensure that our cache is
+ // built with 0xFF
+ this->setCacheAlpha(0xFF);
+
+ // don't have a way to put the mapper into our cache-key yet
+ if (fMapper) {
+ // force our cahce32pixelref to be built
+ (void)this->getCache32();
+ bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
+ bitmap->setPixelRef(fCache32PixelRef);
+ return;
+ }
+
+ // build our key: [numColors + colors[] + {positions[]} + flags ]
+ int count = 1 + fColorCount + 1;
+ if (fColorCount > 2) {
+ count += fColorCount - 1; // fRecs[].fPos
+ }
+
+ SkAutoSTMalloc<16, int32_t> storage(count);
+ int32_t* buffer = storage.get();
+
+ *buffer++ = fColorCount;
+ memcpy(buffer, fOrigColors, fColorCount * sizeof(SkColor));
+ buffer += fColorCount;
+ if (fColorCount > 2) {
+ for (int i = 1; i < fColorCount; i++) {
+ *buffer++ = fRecs[i].fPos;
+ }
+ }
+ *buffer++ = fGradFlags;
+ SkASSERT(buffer - storage.get() == count);
+
+ ///////////////////////////////////
+
+ SK_DECLARE_STATIC_MUTEX(gMutex);
+ static SkBitmapCache* gCache;
+ // each cache cost 1K of RAM, since each bitmap will be 1x256 at 32bpp
+ static const int MAX_NUM_CACHED_GRADIENT_BITMAPS = 32;
+ SkAutoMutexAcquire ama(gMutex);
+
+ if (NULL == gCache) {
+ gCache = SkNEW_ARGS(SkBitmapCache, (MAX_NUM_CACHED_GRADIENT_BITMAPS));
+ }
+ size_t size = count * sizeof(int32_t);
+
+ if (!gCache->find(storage.get(), size, bitmap)) {
+ // force our cahce32pixelref to be built
+ (void)this->getCache32();
+ bitmap->setConfig(SkBitmap::kARGB_8888_Config, kCache32Count, 1);
+ bitmap->setPixelRef(fCache32PixelRef);
+
+ gCache->add(storage.get(), size, *bitmap);
+ }
+}
+
+void SkGradientShaderBase::commonAsAGradient(GradientInfo* info) const {
+ if (info) {
+ if (info->fColorCount >= fColorCount) {
+ if (info->fColors) {
+ memcpy(info->fColors, fOrigColors, fColorCount * sizeof(SkColor));
+ }
+ if (info->fColorOffsets) {
+ if (fColorCount == 2) {
+ info->fColorOffsets[0] = 0;
+ info->fColorOffsets[1] = SK_Scalar1;
+ } else if (fColorCount > 2) {
+ for (int i = 0; i < fColorCount; ++i) {
+ info->fColorOffsets[i] = SkFixedToScalar(fRecs[i].fPos);
+ }
+ }
+ }
+ }
+ info->fColorCount = fColorCount;
+ info->fTileMode = fTileMode;
+ info->fGradientFlags = fGradFlags;
+ }
+}
+
+#ifdef SK_DEVELOPER
+void SkGradientShaderBase::toString(SkString* str) const {
+
+ str->appendf("%d colors: ", fColorCount);
+
+ for (int i = 0; i < fColorCount; ++i) {
+ str->appendHex(fOrigColors[i]);
+ if (i < fColorCount-1) {
+ str->append(", ");
+ }
+ }
+
+ if (fColorCount > 2) {
+ str->append(" points: (");
+ for (int i = 0; i < fColorCount; ++i) {
+ str->appendScalar(SkFixedToScalar(fRecs[i].fPos));
+ if (i < fColorCount-1) {
+ str->append(", ");
+ }
+ }
+ str->append(")");
+ }
+
+ static const char* gTileModeName[SkShader::kTileModeCount] = {
+ "clamp", "repeat", "mirror"
+ };
+
+ str->append(" ");
+ str->append(gTileModeName[fTileMode]);
+
+ // TODO: add "fMapper->toString(str);" when SkUnitMapper::toString is added
+
+ this->INHERITED::toString(str);
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkEmptyShader.h"
+
+// assumes colors is SkColor* and pos is SkScalar*
+#define EXPAND_1_COLOR(count) \
+ SkColor tmp[2]; \
+ do { \
+ if (1 == count) { \
+ tmp[0] = tmp[1] = colors[0]; \
+ colors = tmp; \
+ pos = NULL; \
+ count = 2; \
+ } \
+ } while (0)
+
+static void desc_init(SkGradientShaderBase::Descriptor* desc,
+ const SkColor colors[],
+ const SkScalar pos[], int colorCount,
+ SkShader::TileMode mode,
+ SkUnitMapper* mapper, uint32_t flags) {
+ desc->fColors = colors;
+ desc->fPos = pos;
+ desc->fCount = colorCount;
+ desc->fTileMode = mode;
+ desc->fMapper = mapper;
+ desc->fFlags = flags;
+}
+
+SkShader* SkGradientShader::CreateLinear(const SkPoint pts[2],
+ const SkColor colors[],
+ const SkScalar pos[], int colorCount,
+ SkShader::TileMode mode,
+ SkUnitMapper* mapper,
+ uint32_t flags) {
+ if (NULL == pts || NULL == colors || colorCount < 1) {
+ return NULL;
+ }
+ EXPAND_1_COLOR(colorCount);
+
+ SkGradientShaderBase::Descriptor desc;
+ desc_init(&desc, colors, pos, colorCount, mode, mapper, flags);
+ return SkNEW_ARGS(SkLinearGradient, (pts, desc));
+}
+
+SkShader* SkGradientShader::CreateRadial(const SkPoint& center, SkScalar radius,
+ const SkColor colors[],
+ const SkScalar pos[], int colorCount,
+ SkShader::TileMode mode,
+ SkUnitMapper* mapper,
+ uint32_t flags) {
+ if (radius <= 0 || NULL == colors || colorCount < 1) {
+ return NULL;
+ }
+ EXPAND_1_COLOR(colorCount);
+
+ SkGradientShaderBase::Descriptor desc;
+ desc_init(&desc, colors, pos, colorCount, mode, mapper, flags);
+ return SkNEW_ARGS(SkRadialGradient, (center, radius, desc));
+}
+
+SkShader* SkGradientShader::CreateTwoPointRadial(const SkPoint& start,
+ SkScalar startRadius,
+ const SkPoint& end,
+ SkScalar endRadius,
+ const SkColor colors[],
+ const SkScalar pos[],
+ int colorCount,
+ SkShader::TileMode mode,
+ SkUnitMapper* mapper,
+ uint32_t flags) {
+ if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
+ return NULL;
+ }
+ EXPAND_1_COLOR(colorCount);
+
+ SkGradientShaderBase::Descriptor desc;
+ desc_init(&desc, colors, pos, colorCount, mode, mapper, flags);
+ return SkNEW_ARGS(SkTwoPointRadialGradient,
+ (start, startRadius, end, endRadius, desc));
+}
+
+SkShader* SkGradientShader::CreateTwoPointConical(const SkPoint& start,
+ SkScalar startRadius,
+ const SkPoint& end,
+ SkScalar endRadius,
+ const SkColor colors[],
+ const SkScalar pos[],
+ int colorCount,
+ SkShader::TileMode mode,
+ SkUnitMapper* mapper,
+ uint32_t flags) {
+ if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) {
+ return NULL;
+ }
+ if (start == end && startRadius == endRadius) {
+ return SkNEW(SkEmptyShader);
+ }
+ EXPAND_1_COLOR(colorCount);
+
+ SkGradientShaderBase::Descriptor desc;
+ desc_init(&desc, colors, pos, colorCount, mode, mapper, flags);
+ return SkNEW_ARGS(SkTwoPointConicalGradient,
+ (start, startRadius, end, endRadius, desc));
+}
+
+SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy,
+ const SkColor colors[],
+ const SkScalar pos[],
+ int colorCount, SkUnitMapper* mapper,
+ uint32_t flags) {
+ if (NULL == colors || colorCount < 1) {
+ return NULL;
+ }
+ EXPAND_1_COLOR(colorCount);
+
+ SkGradientShaderBase::Descriptor desc;
+ desc_init(&desc, colors, pos, colorCount, SkShader::kClamp_TileMode, mapper, flags);
+ return SkNEW_ARGS(SkSweepGradient, (cx, cy, desc));
+}
+
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkGradientShader)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkLinearGradient)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkRadialGradient)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkSweepGradient)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkTwoPointRadialGradient)
+ SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkTwoPointConicalGradient)
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
+
+///////////////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+
+#include "effects/GrTextureStripAtlas.h"
+#include "GrTBackendEffectFactory.h"
+#include "SkGr.h"
+
+GrGLGradientEffect::GrGLGradientEffect(const GrBackendEffectFactory& factory)
+ : INHERITED(factory)
+ , fCachedYCoord(SK_ScalarMax)
+ , fFSYUni(GrGLUniformManager::kInvalidUniformHandle)
+ , fEffectMatrix(kCoordsType) {
+}
+
+GrGLGradientEffect::~GrGLGradientEffect() { }
+
+void GrGLGradientEffect::emitYCoordUniform(GrGLShaderBuilder* builder) {
+ fFSYUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "GradientYCoordFS");
+}
+
+void GrGLGradientEffect::setData(const GrGLUniformManager& uman,
+ const GrDrawEffect& drawEffect) {
+ const GrGradientEffect& e = drawEffect.castEffect<GrGradientEffect>();
+ const GrTexture* texture = e.texture(0);
+ fEffectMatrix.setData(uman, e.getMatrix(), drawEffect, texture);
+
+ SkScalar yCoord = e.getYCoord();
+ if (yCoord != fCachedYCoord) {
+ uman.set1f(fFSYUni, yCoord);
+ fCachedYCoord = yCoord;
+ }
+}
+
+GrGLEffect::EffectKey GrGLGradientEffect::GenMatrixKey(const GrDrawEffect& drawEffect) {
+ const GrGradientEffect& e = drawEffect.castEffect<GrGradientEffect>();
+ const GrTexture* texture = e.texture(0);
+ return GrGLEffectMatrix::GenKey(e.getMatrix(), drawEffect, kCoordsType, texture);
+}
+
+void GrGLGradientEffect::setupMatrix(GrGLShaderBuilder* builder,
+ EffectKey key,
+ const char** fsCoordName,
+ const char** vsVaryingName,
+ GrSLType* vsVaryingType) {
+ fEffectMatrix.emitCodeMakeFSCoords2D(builder,
+ key & kMatrixKeyMask,
+ fsCoordName,
+ vsVaryingName,
+ vsVaryingType);
+}
+
+void GrGLGradientEffect::emitColorLookup(GrGLShaderBuilder* builder,
+ const char* gradientTValue,
+ const char* outputColor,
+ const char* inputColor,
+ const GrGLShaderBuilder::TextureSampler& sampler) {
+
+ builder->fsCodeAppendf("\tvec2 coord = vec2(%s, %s);\n",
+ gradientTValue,
+ builder->getUniformVariable(fFSYUni).c_str());
+ builder->fsCodeAppendf("\t%s = ", outputColor);
+ builder->appendTextureLookupAndModulate(GrGLShaderBuilder::kFragment_ShaderType,
+ inputColor,
+ sampler,
+ "coord");
+ builder->fsCodeAppend(";\n");
+}
+
+/////////////////////////////////////////////////////////////////////
+
+GrGradientEffect::GrGradientEffect(GrContext* ctx,
+ const SkGradientShaderBase& shader,
+ const SkMatrix& matrix,
+ SkShader::TileMode tileMode) {
+ // TODO: check for simple cases where we don't need a texture:
+ //GradientInfo info;
+ //shader.asAGradient(&info);
+ //if (info.fColorCount == 2) { ...
+
+ fMatrix = matrix;
+
+ SkBitmap bitmap;
+ shader.getGradientTableBitmap(&bitmap);
+
+ fIsOpaque = shader.isOpaque();
+
+ GrTextureStripAtlas::Desc desc;
+ desc.fWidth = bitmap.width();
+ desc.fHeight = 32;
+ desc.fRowHeight = bitmap.height();
+ desc.fContext = ctx;
+ desc.fConfig = SkBitmapConfig2GrPixelConfig(bitmap.config());
+ fAtlas = GrTextureStripAtlas::GetAtlas(desc);
+ GrAssert(NULL != fAtlas);
+
+ // We always filter the gradient table. Each table is one row of a texture, so always y-clamp.
+ GrTextureParams params;
+ params.setFilterMode(GrTextureParams::kBilerp_FilterMode);
+ params.setTileModeX(tileMode);
+
+ fRow = fAtlas->lockRow(bitmap);
+ if (-1 != fRow) {
+ fYCoord = fAtlas->getYOffset(fRow) + SK_ScalarHalf *
+ fAtlas->getVerticalScaleFactor();
+ fTextureAccess.reset(fAtlas->getTexture(), params);
+ } else {
+ GrTexture* texture = GrLockAndRefCachedBitmapTexture(ctx, bitmap, &params);
+ fTextureAccess.reset(texture, params);
+ fYCoord = SK_ScalarHalf;
+
+ // Unlock immediately, this is not great, but we don't have a way of
+ // knowing when else to unlock it currently, so it may get purged from
+ // the cache, but it'll still be ref'd until it's no longer being used.
+ GrUnlockAndUnrefCachedBitmapTexture(texture);
+ }
+ this->addTextureAccess(&fTextureAccess);
+}
+
+GrGradientEffect::~GrGradientEffect() {
+ if (this->useAtlas()) {
+ fAtlas->unlockRow(fRow);
+ }
+}
+
+bool GrGradientEffect::onIsEqual(const GrEffect& effect) const {
+ const GrGradientEffect& s = CastEffect<GrGradientEffect>(effect);
+ return fTextureAccess.getTexture() == s.fTextureAccess.getTexture() &&
+ fTextureAccess.getParams().getTileModeX() ==
+ s.fTextureAccess.getParams().getTileModeX() &&
+ this->useAtlas() == s.useAtlas() &&
+ fYCoord == s.getYCoord() &&
+ fMatrix.cheapEqualTo(s.getMatrix());
+}
+
+void GrGradientEffect::getConstantColorComponents(GrColor* color, uint32_t* validFlags) const {
+ if (fIsOpaque && (kA_GrColorComponentFlag & *validFlags) && 0xff == GrColorUnpackA(*color)) {
+ *validFlags = kA_GrColorComponentFlag;
+ } else {
+ *validFlags = 0;
+ }
+}
+
+int GrGradientEffect::RandomGradientParams(SkMWCRandom* random,
+ SkColor colors[],
+ SkScalar** stops,
+ SkShader::TileMode* tm) {
+ int outColors = random->nextRangeU(1, kMaxRandomGradientColors);
+
+ // if one color, omit stops, otherwise randomly decide whether or not to
+ if (outColors == 1 || (outColors >= 2 && random->nextBool())) {
+ *stops = NULL;
+ }
+
+ SkScalar stop = 0.f;
+ for (int i = 0; i < outColors; ++i) {
+ colors[i] = random->nextU();
+ if (NULL != *stops) {
+ (*stops)[i] = stop;
+ stop = i < outColors - 1 ? stop + random->nextUScalar1() * (1.f - stop) : 1.f;
+ }
+ }
+ *tm = static_cast<SkShader::TileMode>(random->nextULessThan(SkShader::kTileModeCount));
+
+ return outColors;
+}
+
+#endif
diff --git a/effects/gradients/SkGradientShaderPriv.h b/effects/gradients/SkGradientShaderPriv.h
new file mode 100644
index 00000000..31cc9f26
--- /dev/null
+++ b/effects/gradients/SkGradientShaderPriv.h
@@ -0,0 +1,349 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkGradientShaderPriv_DEFINED
+#define SkGradientShaderPriv_DEFINED
+
+#include "SkGradientShader.h"
+#include "SkClampRange.h"
+#include "SkColorPriv.h"
+#include "SkFlattenableBuffers.h"
+#include "SkMallocPixelRef.h"
+#include "SkUnitMapper.h"
+#include "SkUtils.h"
+#include "SkTemplates.h"
+#include "SkBitmapCache.h"
+#include "SkShader.h"
+
+static inline void sk_memset32_dither(uint32_t dst[], uint32_t v0, uint32_t v1,
+ int count) {
+ if (count > 0) {
+ if (v0 == v1) {
+ sk_memset32(dst, v0, count);
+ } else {
+ int pairs = count >> 1;
+ for (int i = 0; i < pairs; i++) {
+ *dst++ = v0;
+ *dst++ = v1;
+ }
+ if (count & 1) {
+ *dst = v0;
+ }
+ }
+ }
+}
+
+// Clamp
+
+static inline SkFixed clamp_tileproc(SkFixed x) {
+ return SkClampMax(x, 0xFFFF);
+}
+
+// Repeat
+
+static inline SkFixed repeat_tileproc(SkFixed x) {
+ return x & 0xFFFF;
+}
+
+// Mirror
+
+// Visual Studio 2010 (MSC_VER=1600) optimizes bit-shift code incorrectly.
+// See http://code.google.com/p/skia/issues/detail?id=472
+#if defined(_MSC_VER) && (_MSC_VER >= 1600)
+#pragma optimize("", off)
+#endif
+
+static inline SkFixed mirror_tileproc(SkFixed x) {
+ int s = x << 15 >> 31;
+ return (x ^ s) & 0xFFFF;
+}
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1600)
+#pragma optimize("", on)
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+typedef SkFixed (*TileProc)(SkFixed);
+
+///////////////////////////////////////////////////////////////////////////////
+
+static const TileProc gTileProcs[] = {
+ clamp_tileproc,
+ repeat_tileproc,
+ mirror_tileproc
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkGradientShaderBase : public SkShader {
+public:
+ struct Descriptor {
+ Descriptor() {
+ sk_bzero(this, sizeof(*this));
+ fTileMode = SkShader::kClamp_TileMode;
+ }
+
+ const SkColor* fColors;
+ const SkScalar* fPos;
+ int fCount;
+ SkShader::TileMode fTileMode;
+ SkUnitMapper* fMapper;
+ uint32_t fFlags;
+ };
+
+public:
+ SkGradientShaderBase(const Descriptor& desc);
+ virtual ~SkGradientShaderBase();
+
+ virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&) SK_OVERRIDE;
+ virtual uint32_t getFlags() SK_OVERRIDE { return fFlags; }
+ virtual bool isOpaque() const SK_OVERRIDE;
+
+ void getGradientTableBitmap(SkBitmap*) const;
+
+ enum {
+ /// Seems like enough for visual accuracy. TODO: if pos[] deserves
+ /// it, use a larger cache.
+ kCache16Bits = 8,
+ kCache16Count = (1 << kCache16Bits),
+ kCache16Shift = 16 - kCache16Bits,
+ kSqrt16Shift = 8 - kCache16Bits,
+
+ /// Seems like enough for visual accuracy. TODO: if pos[] deserves
+ /// it, use a larger cache.
+ kCache32Bits = 8,
+ kCache32Count = (1 << kCache32Bits),
+ kCache32Shift = 16 - kCache32Bits,
+ kSqrt32Shift = 8 - kCache32Bits,
+
+ /// This value is used to *read* the dither cache; it may be 0
+ /// if dithering is disabled.
+ kDitherStride32 = kCache32Count,
+ kDitherStride16 = kCache16Count,
+ };
+
+
+protected:
+ SkGradientShaderBase(SkFlattenableReadBuffer& );
+ virtual void flatten(SkFlattenableWriteBuffer&) const SK_OVERRIDE;
+ SK_DEVELOPER_TO_STRING()
+
+ SkUnitMapper* fMapper;
+ SkMatrix fPtsToUnit; // set by subclass
+ SkMatrix fDstToIndex;
+ SkMatrix::MapXYProc fDstToIndexProc;
+ TileMode fTileMode;
+ TileProc fTileProc;
+ int fColorCount;
+ uint8_t fDstToIndexClass;
+ uint8_t fFlags;
+ uint8_t fGradFlags;
+
+ struct Rec {
+ SkFixed fPos; // 0...1
+ uint32_t fScale; // (1 << 24) / range
+ };
+ Rec* fRecs;
+
+ const uint16_t* getCache16() const;
+ const SkPMColor* getCache32() const;
+
+ void commonAsAGradient(GradientInfo*) const;
+
+private:
+ enum {
+ kColorStorageCount = 4, // more than this many colors, and we'll use sk_malloc for the space
+
+ kStorageSize = kColorStorageCount * (sizeof(SkColor) + sizeof(Rec))
+ };
+ SkColor fStorage[(kStorageSize + 3) >> 2];
+ SkColor* fOrigColors; // original colors, before modulation by paint in setContext
+ bool fColorsAreOpaque;
+
+ mutable uint16_t* fCache16; // working ptr. If this is NULL, we need to recompute the cache values
+ mutable SkPMColor* fCache32; // working ptr. If this is NULL, we need to recompute the cache values
+
+ mutable uint16_t* fCache16Storage; // storage for fCache16, allocated on demand
+ mutable SkMallocPixelRef* fCache32PixelRef;
+ mutable unsigned fCacheAlpha; // the alpha value we used when we computed the cache. larger than 8bits so we can store uninitialized value
+
+ static void Build16bitCache(uint16_t[], SkColor c0, SkColor c1, int count);
+ static void Build32bitCache(SkPMColor[], SkColor c0, SkColor c1, int count,
+ U8CPU alpha, uint32_t gradFlags);
+ void setCacheAlpha(U8CPU alpha) const;
+ void initCommon();
+
+ typedef SkShader INHERITED;
+};
+
+static inline int init_dither_toggle(int x, int y) {
+ x &= 1;
+ y = (y & 1) << 1;
+ return (x | y) * SkGradientShaderBase::kDitherStride32;
+}
+
+static inline int next_dither_toggle(int toggle) {
+ return toggle ^ SkGradientShaderBase::kDitherStride32;
+}
+
+static inline int init_dither_toggle16(int x, int y) {
+ return ((x ^ y) & 1) * SkGradientShaderBase::kDitherStride16;
+}
+
+static inline int next_dither_toggle16(int toggle) {
+ return toggle ^ SkGradientShaderBase::kDitherStride16;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLEffectMatrix.h"
+
+class GrEffectStage;
+class GrBackendEffectFactory;
+
+/*
+ * The interpretation of the texture matrix depends on the sample mode. The
+ * texture matrix is applied both when the texture coordinates are explicit
+ * and when vertex positions are used as texture coordinates. In the latter
+ * case the texture matrix is applied to the pre-view-matrix position
+ * values.
+ *
+ * Normal SampleMode
+ * The post-matrix texture coordinates are in normalize space with (0,0) at
+ * the top-left and (1,1) at the bottom right.
+ * RadialGradient
+ * The matrix specifies the radial gradient parameters.
+ * (0,0) in the post-matrix space is center of the radial gradient.
+ * Radial2Gradient
+ * Matrix transforms to space where first circle is centered at the
+ * origin. The second circle will be centered (x, 0) where x may be
+ * 0 and is provided by setRadial2Params. The post-matrix space is
+ * normalized such that 1 is the second radius - first radius.
+ * SweepGradient
+ * The angle from the origin of texture coordinates in post-matrix space
+ * determines the gradient value.
+ */
+
+ class GrTextureStripAtlas;
+
+// Base class for Gr gradient effects
+class GrGradientEffect : public GrEffect {
+public:
+
+ GrGradientEffect(GrContext* ctx,
+ const SkGradientShaderBase& shader,
+ const SkMatrix& matrix,
+ SkShader::TileMode tileMode);
+
+ virtual ~GrGradientEffect();
+
+ bool useAtlas() const { return SkToBool(-1 != fRow); }
+ SkScalar getYCoord() const { return fYCoord; };
+ const SkMatrix& getMatrix() const { return fMatrix;}
+
+ virtual void getConstantColorComponents(GrColor* color, uint32_t* validFlags) const SK_OVERRIDE;
+
+protected:
+
+ /** Populates a pair of arrays with colors and stop info to construct a random gradient.
+ The function decides whether stop values should be used or not. The return value indicates
+ the number of colors, which will be capped by kMaxRandomGradientColors. colors should be
+ sized to be at least kMaxRandomGradientColors. stops is a pointer to an array of at least
+ size kMaxRandomGradientColors. It may be updated to NULL, indicating that NULL should be
+ passed to the gradient factory rather than the array.
+ */
+ static const int kMaxRandomGradientColors = 4;
+ static int RandomGradientParams(SkMWCRandom* r,
+ SkColor colors[kMaxRandomGradientColors],
+ SkScalar** stops,
+ SkShader::TileMode* tm);
+
+ virtual bool onIsEqual(const GrEffect& effect) const SK_OVERRIDE;
+
+private:
+
+ GrTextureAccess fTextureAccess;
+ SkScalar fYCoord;
+ GrTextureStripAtlas* fAtlas;
+ int fRow;
+ SkMatrix fMatrix;
+ bool fIsOpaque;
+
+ typedef GrEffect INHERITED;
+
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Base class for GL gradient effects
+class GrGLGradientEffect : public GrGLEffect {
+public:
+ GrGLGradientEffect(const GrBackendEffectFactory& factory);
+ virtual ~GrGLGradientEffect();
+
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+protected:
+ /**
+ * Subclasses must reserve the lower kMatrixKeyBitCnt of their key for use by
+ * GrGLGradientEffect.
+ */
+ enum {
+ kMatrixKeyBitCnt = GrGLEffectMatrix::kKeyBits,
+ kMatrixKeyMask = (1 << kMatrixKeyBitCnt) - 1,
+ };
+
+ /**
+ * Subclasses must call this. It will return a value restricted to the lower kMatrixKeyBitCnt
+ * bits.
+ */
+ static EffectKey GenMatrixKey(const GrDrawEffect&);
+
+ /**
+ * Inserts code to implement the GrGradientEffect's matrix. This should be called before a
+ * subclass emits its own code. The name of the 2D coords is output via fsCoordName and already
+ * incorporates any perspective division. The caller can also optionally retrieve the name of
+ * the varying inserted in the VS and its type, which may be either vec2f or vec3f depending
+ * upon whether the matrix has perspective or not. It is not necessary to mask the key before
+ * calling.
+ */
+ void setupMatrix(GrGLShaderBuilder* builder,
+ EffectKey key,
+ const char** fsCoordName,
+ const char** vsVaryingName = NULL,
+ GrSLType* vsVaryingType = NULL);
+
+ // Emits the uniform used as the y-coord to texture samples in derived classes. Subclasses
+ // should call this method from their emitCode().
+ void emitYCoordUniform(GrGLShaderBuilder* builder);
+
+ // emit code that gets a fragment's color from an expression for t; for now this always uses the
+ // texture, but for simpler cases we'll be able to lerp. Subclasses should call this method from
+ // their emitCode().
+ void emitColorLookup(GrGLShaderBuilder* builder,
+ const char* gradientTValue,
+ const char* outputColor,
+ const char* inputColor,
+ const GrGLShaderBuilder::TextureSampler&);
+
+private:
+ static const GrEffect::CoordsType kCoordsType = GrEffect::kLocal_CoordsType;
+
+ SkScalar fCachedYCoord;
+ GrGLUniformManager::UniformHandle fFSYUni;
+ GrGLEffectMatrix fEffectMatrix;
+
+ typedef GrGLEffect INHERITED;
+};
+
+#endif
+
+#endif
diff --git a/effects/gradients/SkLinearGradient.cpp b/effects/gradients/SkLinearGradient.cpp
new file mode 100644
index 00000000..2f56cb49
--- /dev/null
+++ b/effects/gradients/SkLinearGradient.cpp
@@ -0,0 +1,568 @@
+
+/*
+ * 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 "SkLinearGradient.h"
+
+static inline int repeat_bits(int x, const int bits) {
+ return x & ((1 << bits) - 1);
+}
+
+static inline int repeat_8bits(int x) {
+ return x & 0xFF;
+}
+
+// Visual Studio 2010 (MSC_VER=1600) optimizes bit-shift code incorrectly.
+// See http://code.google.com/p/skia/issues/detail?id=472
+#if defined(_MSC_VER) && (_MSC_VER >= 1600)
+#pragma optimize("", off)
+#endif
+
+static inline int mirror_bits(int x, const int bits) {
+ if (x & (1 << bits)) {
+ x = ~x;
+ }
+ return x & ((1 << bits) - 1);
+}
+
+static inline int mirror_8bits(int x) {
+ if (x & 256) {
+ x = ~x;
+ }
+ return x & 255;
+}
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1600)
+#pragma optimize("", on)
+#endif
+
+static void pts_to_unit_matrix(const SkPoint pts[2], SkMatrix* matrix) {
+ SkVector vec = pts[1] - pts[0];
+ SkScalar mag = vec.length();
+ SkScalar inv = mag ? SkScalarInvert(mag) : 0;
+
+ vec.scale(inv);
+ matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
+ matrix->postTranslate(-pts[0].fX, -pts[0].fY);
+ matrix->postScale(inv, inv);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkLinearGradient::SkLinearGradient(const SkPoint pts[2], const Descriptor& desc)
+ : SkGradientShaderBase(desc)
+ , fStart(pts[0])
+ , fEnd(pts[1]) {
+ pts_to_unit_matrix(pts, &fPtsToUnit);
+}
+
+SkLinearGradient::SkLinearGradient(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer)
+ , fStart(buffer.readPoint())
+ , fEnd(buffer.readPoint()) {
+}
+
+void SkLinearGradient::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writePoint(fStart);
+ buffer.writePoint(fEnd);
+}
+
+bool SkLinearGradient::setContext(const SkBitmap& device, const SkPaint& paint,
+ const SkMatrix& matrix) {
+ if (!this->INHERITED::setContext(device, paint, matrix)) {
+ return false;
+ }
+
+ 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) && !paint.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;
+ }
+ }
+ return true;
+}
+
+#define NO_CHECK_ITER \
+ do { \
+ unsigned fi = 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, SkFixed dx, SkFixed 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, SkFixed dx, SkFixed 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(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, SkFixed dx, SkFixed 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);
+
+ 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, SkFixed dx, SkFixed fx,
+ SkPMColor* SK_RESTRICT dstC,
+ const SkPMColor* SK_RESTRICT cache,
+ int toggle, int count) {
+ do {
+ unsigned fi = mirror_8bits(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, SkFixed dx, SkFixed fx,
+ SkPMColor* SK_RESTRICT dstC,
+ const SkPMColor* SK_RESTRICT cache,
+ int toggle, int count) {
+ do {
+ unsigned fi = repeat_8bits(fx >> 8);
+ SkASSERT(fi <= 0xFF);
+ fx += dx;
+ *dstC++ = cache[toggle + fi];
+ toggle = next_dither_toggle(toggle);
+ } while (--count != 0);
+}
+
+}
+
+void SkLinearGradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
+ int count) {
+ SkASSERT(count > 0);
+
+ SkPoint srcPt;
+ SkMatrix::MapXYProc dstProc = fDstToIndexProc;
+ TileProc proc = fTileProc;
+ const SkPMColor* SK_RESTRICT cache = this->getCache32();
+ int toggle = init_dither_toggle(x, y);
+
+ if (fDstToIndexClass != kPerspective_MatrixClass) {
+ dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+ SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
+
+ if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
+ SkFixed dxStorage[1];
+ (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
+ dx = dxStorage[0];
+ } else {
+ SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
+ dx = SkScalarToFixed(fDstToIndex.getScaleX());
+ }
+
+ LinearShadeProc shadeProc = shadeSpan_linear_repeat;
+ if (0 == dx) {
+ shadeProc = shadeSpan_linear_vertical_lerp;
+ } else if (SkShader::kClamp_TileMode == fTileMode) {
+ shadeProc = shadeSpan_linear_clamp;
+ } else if (SkShader::kMirror_TileMode == fTileMode) {
+ shadeProc = shadeSpan_linear_mirror;
+ } else {
+ SkASSERT(SkShader::kRepeat_TileMode == 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::BitmapType SkLinearGradient::asABitmap(SkBitmap* bitmap,
+ SkMatrix* matrix,
+ TileMode xy[]) const {
+ if (bitmap) {
+ this->getGradientTableBitmap(bitmap);
+ }
+ if (matrix) {
+ matrix->preConcat(fPtsToUnit);
+ }
+ if (xy) {
+ xy[0] = fTileMode;
+ xy[1] = kClamp_TileMode;
+ }
+ return kLinear_BitmapType;
+}
+
+SkShader::GradientType SkLinearGradient::asAGradient(GradientInfo* info) const {
+ if (info) {
+ commonAsAGradient(info);
+ info->fPoint[0] = fStart;
+ info->fPoint[1] = fEnd;
+ }
+ return kLinear_GradientType;
+}
+
+static void dither_memset16(uint16_t dst[], uint16_t value, uint16_t other,
+ int count) {
+ if (reinterpret_cast<uintptr_t>(dst) & 2) {
+ *dst++ = value;
+ count -= 1;
+ SkTSwap(value, other);
+ }
+
+ sk_memset32((uint32_t*)dst, (value << 16) | other, count >> 1);
+
+ if (count & 1) {
+ dst[count - 1] = value;
+ }
+}
+
+#define NO_CHECK_ITER_16 \
+ do { \
+ unsigned fi = fx >> SkGradientShaderBase::kCache16Shift; \
+ SkASSERT(fi < SkGradientShaderBase::kCache16Count); \
+ fx += dx; \
+ *dstC++ = cache[toggle + fi]; \
+ toggle = next_dither_toggle16(toggle); \
+ } while (0)
+
+namespace {
+
+typedef void (*LinearShade16Proc)(TileProc proc, SkFixed dx, SkFixed fx,
+ uint16_t* dstC, const uint16_t* cache,
+ int toggle, int count);
+
+void shadeSpan16_linear_vertical(TileProc proc, SkFixed dx, SkFixed fx,
+ uint16_t* SK_RESTRICT dstC,
+ const uint16_t* SK_RESTRICT cache,
+ int toggle, int count) {
+ // we're a vertical gradient, so no change in a span
+ unsigned fi = proc(fx) >> SkGradientShaderBase::kCache16Shift;
+ SkASSERT(fi < SkGradientShaderBase::kCache16Count);
+ dither_memset16(dstC, cache[toggle + fi],
+ cache[next_dither_toggle16(toggle) + fi], count);
+}
+
+void shadeSpan16_linear_clamp(TileProc proc, SkFixed dx, SkFixed fx,
+ uint16_t* SK_RESTRICT dstC,
+ const uint16_t* SK_RESTRICT cache,
+ int toggle, int count) {
+ SkClampRange range;
+ range.init(fx, dx, count, 0, SkGradientShaderBase::kCache32Count - 1);
+
+ if ((count = range.fCount0) > 0) {
+ dither_memset16(dstC,
+ cache[toggle + range.fV0],
+ cache[next_dither_toggle16(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_16; NO_CHECK_ITER_16;
+ NO_CHECK_ITER_16; NO_CHECK_ITER_16;
+ NO_CHECK_ITER_16; NO_CHECK_ITER_16;
+ NO_CHECK_ITER_16; NO_CHECK_ITER_16;
+ }
+ if ((count &= 7) > 0) {
+ do {
+ NO_CHECK_ITER_16;
+ } while (--count != 0);
+ }
+ }
+ if ((count = range.fCount2) > 0) {
+ dither_memset16(dstC,
+ cache[toggle + range.fV1],
+ cache[next_dither_toggle16(toggle) + range.fV1],
+ count);
+ }
+}
+
+void shadeSpan16_linear_mirror(TileProc proc, SkFixed dx, SkFixed fx,
+ uint16_t* SK_RESTRICT dstC,
+ const uint16_t* SK_RESTRICT cache,
+ int toggle, int count) {
+ do {
+ unsigned fi = mirror_bits(fx >> SkGradientShaderBase::kCache16Shift,
+ SkGradientShaderBase::kCache16Bits);
+ SkASSERT(fi < SkGradientShaderBase::kCache16Count);
+ fx += dx;
+ *dstC++ = cache[toggle + fi];
+ toggle = next_dither_toggle16(toggle);
+ } while (--count != 0);
+}
+
+void shadeSpan16_linear_repeat(TileProc proc, SkFixed dx, SkFixed fx,
+ uint16_t* SK_RESTRICT dstC,
+ const uint16_t* SK_RESTRICT cache,
+ int toggle, int count) {
+ do {
+ unsigned fi = repeat_bits(fx >> SkGradientShaderBase::kCache16Shift,
+ SkGradientShaderBase::kCache16Bits);
+ SkASSERT(fi < SkGradientShaderBase::kCache16Count);
+ fx += dx;
+ *dstC++ = cache[toggle + fi];
+ toggle = next_dither_toggle16(toggle);
+ } while (--count != 0);
+}
+}
+
+void SkLinearGradient::shadeSpan16(int x, int y,
+ uint16_t* SK_RESTRICT dstC, int count) {
+ SkASSERT(count > 0);
+
+ SkPoint srcPt;
+ SkMatrix::MapXYProc dstProc = fDstToIndexProc;
+ TileProc proc = fTileProc;
+ const uint16_t* SK_RESTRICT cache = this->getCache16();
+ int toggle = init_dither_toggle16(x, y);
+
+ if (fDstToIndexClass != kPerspective_MatrixClass) {
+ dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+ SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
+
+ if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
+ SkFixed dxStorage[1];
+ (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), dxStorage, NULL);
+ dx = dxStorage[0];
+ } else {
+ SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
+ dx = SkScalarToFixed(fDstToIndex.getScaleX());
+ }
+
+ LinearShade16Proc shadeProc = shadeSpan16_linear_repeat;
+ if (SkFixedNearlyZero(dx)) {
+ shadeProc = shadeSpan16_linear_vertical;
+ } else if (SkShader::kClamp_TileMode == fTileMode) {
+ shadeProc = shadeSpan16_linear_clamp;
+ } else if (SkShader::kMirror_TileMode == fTileMode) {
+ shadeProc = shadeSpan16_linear_mirror;
+ } else {
+ SkASSERT(SkShader::kRepeat_TileMode == 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);
+
+ int index = fi >> kCache16Shift;
+ *dstC++ = cache[toggle + index];
+ toggle = next_dither_toggle16(toggle);
+
+ dstX += SK_Scalar1;
+ } while (--count != 0);
+ }
+}
+
+#if SK_SUPPORT_GPU
+
+#include "GrTBackendEffectFactory.h"
+
+/////////////////////////////////////////////////////////////////////
+
+class GrGLLinearGradient : public GrGLGradientEffect {
+public:
+
+ GrGLLinearGradient(const GrBackendEffectFactory& factory, const GrDrawEffect&)
+ : INHERITED (factory) { }
+
+ virtual ~GrGLLinearGradient() { }
+
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ static EffectKey GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
+ return GenMatrixKey(drawEffect);
+ }
+
+private:
+
+ typedef GrGLGradientEffect INHERITED;
+};
+
+/////////////////////////////////////////////////////////////////////
+
+class GrLinearGradient : public GrGradientEffect {
+public:
+
+ static GrEffectRef* Create(GrContext* ctx,
+ const SkLinearGradient& shader,
+ const SkMatrix& matrix,
+ SkShader::TileMode tm) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrLinearGradient, (ctx, shader, matrix, tm)));
+ return CreateEffectRef(effect);
+ }
+
+ virtual ~GrLinearGradient() { }
+
+ static const char* Name() { return "Linear Gradient"; }
+ const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<GrLinearGradient>::getInstance();
+ }
+
+ typedef GrGLLinearGradient GLEffect;
+
+private:
+ GrLinearGradient(GrContext* ctx,
+ const SkLinearGradient& shader,
+ const SkMatrix& matrix,
+ SkShader::TileMode tm)
+ : INHERITED(ctx, shader, matrix, tm) { }
+ GR_DECLARE_EFFECT_TEST;
+
+ typedef GrGradientEffect INHERITED;
+};
+
+/////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_EFFECT_TEST(GrLinearGradient);
+
+GrEffectRef* GrLinearGradient::TestCreate(SkMWCRandom* random,
+ GrContext* context,
+ const GrDrawTargetCaps&,
+ GrTexture**) {
+ SkPoint points[] = {{random->nextUScalar1(), random->nextUScalar1()},
+ {random->nextUScalar1(), random->nextUScalar1()}};
+
+ SkColor colors[kMaxRandomGradientColors];
+ SkScalar stopsArray[kMaxRandomGradientColors];
+ SkScalar* stops = stopsArray;
+ SkShader::TileMode tm;
+ int colorCount = RandomGradientParams(random, colors, &stops, &tm);
+ SkAutoTUnref<SkShader> shader(SkGradientShader::CreateLinear(points,
+ colors, stops, colorCount,
+ tm));
+ SkPaint paint;
+ return shader->asNewEffect(context, paint);
+}
+
+/////////////////////////////////////////////////////////////////////
+
+void GrGLLinearGradient::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect&,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ this->emitYCoordUniform(builder);
+ const char* coords;
+ this->setupMatrix(builder, key, &coords);
+ SkString t;
+ t.append(coords);
+ t.append(".x");
+ this->emitColorLookup(builder, t.c_str(), outputColor, inputColor, samplers[0]);
+}
+
+/////////////////////////////////////////////////////////////////////
+
+GrEffectRef* SkLinearGradient::asNewEffect(GrContext* context, const SkPaint&) const {
+ SkASSERT(NULL != context);
+ SkMatrix matrix;
+ if (!this->getLocalMatrix().invert(&matrix)) {
+ return NULL;
+ }
+ matrix.postConcat(fPtsToUnit);
+ return GrLinearGradient::Create(context, *this, matrix, fTileMode);
+}
+
+#else
+
+GrEffectRef* SkLinearGradient::asNewEffect(GrContext*, const SkPaint&) const {
+ SkDEBUGFAIL("Should not call in GPU-less build");
+ return NULL;
+}
+
+#endif
+
+#ifdef SK_DEVELOPER
+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
diff --git a/effects/gradients/SkLinearGradient.h b/effects/gradients/SkLinearGradient.h
new file mode 100644
index 00000000..24c6caba
--- /dev/null
+++ b/effects/gradients/SkLinearGradient.h
@@ -0,0 +1,38 @@
+
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkLinearGradient_DEFINED
+#define SkLinearGradient_DEFINED
+
+#include "SkGradientShaderPriv.h"
+
+class SkLinearGradient : public SkGradientShaderBase {
+public:
+ SkLinearGradient(const SkPoint pts[2], const Descriptor&);
+
+ virtual bool setContext(const SkBitmap&, const SkPaint&, const SkMatrix&) SK_OVERRIDE;
+ virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
+ virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) SK_OVERRIDE;
+ virtual BitmapType asABitmap(SkBitmap*, SkMatrix*, TileMode*) const SK_OVERRIDE;
+ virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE;
+ virtual GrEffectRef* asNewEffect(GrContext* context, const SkPaint&) const SK_OVERRIDE;
+
+ SK_DEVELOPER_TO_STRING()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLinearGradient)
+
+protected:
+ SkLinearGradient(SkFlattenableReadBuffer& buffer);
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE;
+
+private:
+ typedef SkGradientShaderBase INHERITED;
+ const SkPoint fStart;
+ const SkPoint fEnd;
+};
+
+#endif
diff --git a/effects/gradients/SkRadialGradient.cpp b/effects/gradients/SkRadialGradient.cpp
new file mode 100644
index 00000000..ca659699
--- /dev/null
+++ b/effects/gradients/SkRadialGradient.cpp
@@ -0,0 +1,608 @@
+
+/*
+ * 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 "SkRadialGradient.h"
+#include "SkRadialGradient_Table.h"
+
+#define kSQRT_TABLE_BITS 11
+#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
+
+#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
+
+#include <stdio.h>
+
+void SkRadialGradient_BuildTable() {
+ // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
+
+ FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
+ SkASSERT(file);
+ ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
+
+ for (int i = 0; i < kSQRT_TABLE_SIZE; i++) {
+ if ((i & 15) == 0) {
+ ::fprintf(file, "\t");
+ }
+
+ uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
+
+ ::fprintf(file, "0x%02X", value);
+ if (i < kSQRT_TABLE_SIZE-1) {
+ ::fprintf(file, ", ");
+ }
+ if ((i & 15) == 15) {
+ ::fprintf(file, "\n");
+ }
+ }
+ ::fprintf(file, "};\n");
+ ::fclose(file);
+}
+
+#endif
+
+namespace {
+
+void rad_to_unit_matrix(const SkPoint& center, SkScalar radius,
+ SkMatrix* matrix) {
+ SkScalar inv = SkScalarInvert(radius);
+
+ matrix->setTranslate(-center.fX, -center.fY);
+ matrix->postScale(inv, inv);
+}
+
+typedef void (* RadialShade16Proc)(SkScalar sfx, SkScalar sdx,
+ SkScalar sfy, SkScalar sdy,
+ uint16_t* dstC, const uint16_t* cache,
+ int toggle, int count);
+
+void shadeSpan16_radial_clamp(SkScalar sfx, SkScalar sdx,
+ SkScalar sfy, SkScalar sdy,
+ uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
+ int toggle, int count) {
+ const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
+
+ /* knock these down so we can pin against +- 0x7FFF, which is an
+ immediate load, rather than 0xFFFF which is slower. This is a
+ compromise, since it reduces our precision, but that appears
+ to be visually OK. If we decide this is OK for all of our cases,
+ we could (it seems) put this scale-down into fDstToIndex,
+ to avoid having to do these extra shifts each time.
+ */
+ SkFixed fx = SkScalarToFixed(sfx) >> 1;
+ SkFixed dx = SkScalarToFixed(sdx) >> 1;
+ SkFixed fy = SkScalarToFixed(sfy) >> 1;
+ SkFixed dy = SkScalarToFixed(sdy) >> 1;
+ // might perform this check for the other modes,
+ // but the win will be a smaller % of the total
+ if (dy == 0) {
+ fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
+ fy *= fy;
+ do {
+ unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
+ unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
+ fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
+ fx += dx;
+ *dstC++ = cache[toggle +
+ (sqrt_table[fi] >> SkGradientShaderBase::kSqrt16Shift)];
+ toggle = next_dither_toggle16(toggle);
+ } while (--count != 0);
+ } else {
+ do {
+ unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
+ unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
+ fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
+ fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
+ fx += dx;
+ fy += dy;
+ *dstC++ = cache[toggle +
+ (sqrt_table[fi] >> SkGradientShaderBase::kSqrt16Shift)];
+ toggle = next_dither_toggle16(toggle);
+ } while (--count != 0);
+ }
+}
+
+void shadeSpan16_radial_mirror(SkScalar sfx, SkScalar sdx,
+ SkScalar sfy, SkScalar sdy,
+ uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
+ int toggle, int count) {
+ do {
+#ifdef SK_SCALAR_IS_FLOAT
+ float fdist = sk_float_sqrt(sfx*sfx + sfy*sfy);
+ SkFixed dist = SkFloatToFixed(fdist);
+#else
+ SkFixed magnitudeSquared = SkFixedSquare(sfx) +
+ SkFixedSquare(sfy);
+ if (magnitudeSquared < 0) // Overflow.
+ magnitudeSquared = SK_FixedMax;
+ SkFixed dist = SkFixedSqrt(magnitudeSquared);
+#endif
+ unsigned fi = mirror_tileproc(dist);
+ SkASSERT(fi <= 0xFFFF);
+ *dstC++ = cache[toggle + (fi >> SkGradientShaderBase::kCache16Shift)];
+ toggle = next_dither_toggle16(toggle);
+ sfx += sdx;
+ sfy += sdy;
+ } while (--count != 0);
+}
+
+void shadeSpan16_radial_repeat(SkScalar sfx, SkScalar sdx,
+ SkScalar sfy, SkScalar sdy,
+ uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
+ int toggle, int count) {
+ SkFixed fx = SkScalarToFixed(sfx);
+ SkFixed dx = SkScalarToFixed(sdx);
+ SkFixed fy = SkScalarToFixed(sfy);
+ SkFixed dy = SkScalarToFixed(sdy);
+ do {
+ SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
+ unsigned fi = repeat_tileproc(dist);
+ SkASSERT(fi <= 0xFFFF);
+ fx += dx;
+ fy += dy;
+ *dstC++ = cache[toggle + (fi >> SkGradientShaderBase::kCache16Shift)];
+ toggle = next_dither_toggle16(toggle);
+ } while (--count != 0);
+}
+
+}
+
+/////////////////////////////////////////////////////////////////////
+
+SkRadialGradient::SkRadialGradient(const SkPoint& center, SkScalar radius,
+ const Descriptor& desc)
+ : SkGradientShaderBase(desc),
+ fCenter(center),
+ fRadius(radius)
+{
+ // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
+ SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
+
+ rad_to_unit_matrix(center, radius, &fPtsToUnit);
+}
+
+void SkRadialGradient::shadeSpan16(int x, int y, uint16_t* dstCParam,
+ int count) {
+ SkASSERT(count > 0);
+
+ uint16_t* SK_RESTRICT dstC = dstCParam;
+
+ SkPoint srcPt;
+ SkMatrix::MapXYProc dstProc = fDstToIndexProc;
+ TileProc proc = fTileProc;
+ const uint16_t* SK_RESTRICT cache = this->getCache16();
+ int toggle = init_dither_toggle16(x, y);
+
+ if (fDstToIndexClass != kPerspective_MatrixClass) {
+ dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+
+ SkScalar sdx = fDstToIndex.getScaleX();
+ SkScalar sdy = fDstToIndex.getSkewY();
+
+ if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
+ SkFixed storage[2];
+ (void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
+ &storage[0], &storage[1]);
+ sdx = SkFixedToScalar(storage[0]);
+ sdy = SkFixedToScalar(storage[1]);
+ } else {
+ SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
+ }
+
+ RadialShade16Proc shadeProc = shadeSpan16_radial_repeat;
+ if (SkShader::kClamp_TileMode == fTileMode) {
+ shadeProc = shadeSpan16_radial_clamp;
+ } else if (SkShader::kMirror_TileMode == fTileMode) {
+ shadeProc = shadeSpan16_radial_mirror;
+ } else {
+ SkASSERT(SkShader::kRepeat_TileMode == fTileMode);
+ }
+ (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC,
+ cache, toggle, count);
+ } else { // perspective case
+ SkScalar dstX = SkIntToScalar(x);
+ SkScalar dstY = SkIntToScalar(y);
+ do {
+ dstProc(fDstToIndex, dstX, dstY, &srcPt);
+ unsigned fi = proc(SkScalarToFixed(srcPt.length()));
+ SkASSERT(fi <= 0xFFFF);
+
+ int index = fi >> (16 - kCache16Bits);
+ *dstC++ = cache[toggle + index];
+ toggle = next_dither_toggle16(toggle);
+
+ dstX += SK_Scalar1;
+ } while (--count != 0);
+ }
+}
+
+SkShader::BitmapType SkRadialGradient::asABitmap(SkBitmap* bitmap,
+ SkMatrix* matrix, SkShader::TileMode* xy) const {
+ if (bitmap) {
+ this->getGradientTableBitmap(bitmap);
+ }
+ if (matrix) {
+ matrix->setScale(SkIntToScalar(kCache32Count),
+ SkIntToScalar(kCache32Count));
+ matrix->preConcat(fPtsToUnit);
+ }
+ if (xy) {
+ xy[0] = fTileMode;
+ xy[1] = kClamp_TileMode;
+ }
+ return kRadial_BitmapType;
+}
+
+SkShader::GradientType SkRadialGradient::asAGradient(GradientInfo* info) const {
+ if (info) {
+ commonAsAGradient(info);
+ info->fPoint[0] = fCenter;
+ info->fRadius[0] = fRadius;
+ }
+ return kRadial_GradientType;
+}
+
+SkRadialGradient::SkRadialGradient(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer),
+ fCenter(buffer.readPoint()),
+ fRadius(buffer.readScalar()) {
+}
+
+void SkRadialGradient::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writePoint(fCenter);
+ buffer.writeScalar(fRadius);
+}
+
+namespace {
+
+inline bool radial_completely_pinned(int fx, int dx, int fy, int dy) {
+ // fast, overly-conservative test: checks unit square instead
+ // of unit circle
+ bool xClamped = (fx >= SK_FixedHalf && dx >= 0) ||
+ (fx <= -SK_FixedHalf && dx <= 0);
+ bool yClamped = (fy >= SK_FixedHalf && dy >= 0) ||
+ (fy <= -SK_FixedHalf && dy <= 0);
+
+ return xClamped || yClamped;
+}
+
+// Return true if (fx * fy) is always inside the unit circle
+// SkPin32 is expensive, but so are all the SkFixedMul in this test,
+// so it shouldn't be run if count is small.
+inline bool no_need_for_radial_pin(int fx, int dx,
+ int fy, int dy, int count) {
+ SkASSERT(count > 0);
+ if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
+ return false;
+ }
+ if (fx*fx + fy*fy > 0x7FFF*0x7FFF) {
+ return false;
+ }
+ fx += (count - 1) * dx;
+ fy += (count - 1) * dy;
+ if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
+ return false;
+ }
+ return fx*fx + fy*fy <= 0x7FFF*0x7FFF;
+}
+
+#define UNPINNED_RADIAL_STEP \
+ fi = (fx * fx + fy * fy) >> (14 + 16 - kSQRT_TABLE_BITS); \
+ *dstC++ = cache[toggle + \
+ (sqrt_table[fi] >> SkGradientShaderBase::kSqrt32Shift)]; \
+ toggle = next_dither_toggle(toggle); \
+ fx += dx; \
+ fy += dy;
+
+typedef void (* RadialShadeProc)(SkScalar sfx, SkScalar sdx,
+ SkScalar sfy, SkScalar sdy,
+ SkPMColor* dstC, const SkPMColor* cache,
+ int count, int toggle);
+
+// On Linux, this is faster with SkPMColor[] params than SkPMColor* SK_RESTRICT
+void shadeSpan_radial_clamp(SkScalar sfx, SkScalar sdx,
+ SkScalar sfy, SkScalar sdy,
+ SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
+ int count, int toggle) {
+ // Floating point seems to be slower than fixed point,
+ // even when we have float hardware.
+ const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
+ SkFixed fx = SkScalarToFixed(sfx) >> 1;
+ SkFixed dx = SkScalarToFixed(sdx) >> 1;
+ SkFixed fy = SkScalarToFixed(sfy) >> 1;
+ SkFixed dy = SkScalarToFixed(sdy) >> 1;
+ if ((count > 4) && radial_completely_pinned(fx, dx, fy, dy)) {
+ unsigned fi = SkGradientShaderBase::kCache32Count - 1;
+ sk_memset32_dither(dstC,
+ cache[toggle + fi],
+ cache[next_dither_toggle(toggle) + fi],
+ count);
+ } else if ((count > 4) &&
+ no_need_for_radial_pin(fx, dx, fy, dy, count)) {
+ unsigned fi;
+ // 4x unroll appears to be no faster than 2x unroll on Linux
+ while (count > 1) {
+ UNPINNED_RADIAL_STEP;
+ UNPINNED_RADIAL_STEP;
+ count -= 2;
+ }
+ if (count) {
+ UNPINNED_RADIAL_STEP;
+ }
+ } else {
+ // Specializing for dy == 0 gains us 25% on Skia benchmarks
+ if (dy == 0) {
+ unsigned yy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
+ yy *= yy;
+ do {
+ unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
+ unsigned fi = (xx * xx + yy) >> (14 + 16 - kSQRT_TABLE_BITS);
+ fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
+ *dstC++ = cache[toggle + (sqrt_table[fi] >>
+ SkGradientShaderBase::kSqrt32Shift)];
+ toggle = next_dither_toggle(toggle);
+ fx += dx;
+ } while (--count != 0);
+ } else {
+ do {
+ unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
+ unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
+ fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
+ fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
+ *dstC++ = cache[toggle + (sqrt_table[fi] >>
+ SkGradientShaderBase::kSqrt32Shift)];
+ toggle = next_dither_toggle(toggle);
+ fx += dx;
+ fy += dy;
+ } while (--count != 0);
+ }
+ }
+}
+
+// Unrolling this loop doesn't seem to help (when float); we're stalling to
+// get the results of the sqrt (?), and don't have enough extra registers to
+// have many in flight.
+void shadeSpan_radial_mirror(SkScalar sfx, SkScalar sdx,
+ SkScalar sfy, SkScalar sdy,
+ SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
+ int count, int toggle) {
+ do {
+#ifdef SK_SCALAR_IS_FLOAT
+ float fdist = sk_float_sqrt(sfx*sfx + sfy*sfy);
+ SkFixed dist = SkFloatToFixed(fdist);
+#else
+ SkFixed magnitudeSquared = SkFixedSquare(sfx) +
+ SkFixedSquare(sfy);
+ if (magnitudeSquared < 0) // Overflow.
+ magnitudeSquared = SK_FixedMax;
+ SkFixed dist = SkFixedSqrt(magnitudeSquared);
+#endif
+ unsigned fi = mirror_tileproc(dist);
+ SkASSERT(fi <= 0xFFFF);
+ *dstC++ = cache[toggle + (fi >> SkGradientShaderBase::kCache32Shift)];
+ toggle = next_dither_toggle(toggle);
+ sfx += sdx;
+ sfy += sdy;
+ } while (--count != 0);
+}
+
+void shadeSpan_radial_repeat(SkScalar sfx, SkScalar sdx,
+ SkScalar sfy, SkScalar sdy,
+ SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
+ int count, int toggle) {
+ SkFixed fx = SkScalarToFixed(sfx);
+ SkFixed dx = SkScalarToFixed(sdx);
+ SkFixed fy = SkScalarToFixed(sfy);
+ SkFixed dy = SkScalarToFixed(sdy);
+ do {
+ SkFixed magnitudeSquared = SkFixedSquare(fx) +
+ SkFixedSquare(fy);
+ if (magnitudeSquared < 0) // Overflow.
+ magnitudeSquared = SK_FixedMax;
+ SkFixed dist = SkFixedSqrt(magnitudeSquared);
+ unsigned fi = repeat_tileproc(dist);
+ SkASSERT(fi <= 0xFFFF);
+ *dstC++ = cache[toggle + (fi >> SkGradientShaderBase::kCache32Shift)];
+ toggle = next_dither_toggle(toggle);
+ fx += dx;
+ fy += dy;
+ } while (--count != 0);
+}
+}
+
+void SkRadialGradient::shadeSpan(int x, int y,
+ SkPMColor* SK_RESTRICT dstC, int count) {
+ SkASSERT(count > 0);
+
+ SkPoint srcPt;
+ SkMatrix::MapXYProc dstProc = fDstToIndexProc;
+ TileProc proc = fTileProc;
+ const SkPMColor* SK_RESTRICT cache = this->getCache32();
+ int toggle = init_dither_toggle(x, y);
+
+ if (fDstToIndexClass != kPerspective_MatrixClass) {
+ dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+ SkScalar sdx = fDstToIndex.getScaleX();
+ SkScalar sdy = fDstToIndex.getSkewY();
+
+ if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
+ SkFixed storage[2];
+ (void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
+ &storage[0], &storage[1]);
+ sdx = SkFixedToScalar(storage[0]);
+ sdy = SkFixedToScalar(storage[1]);
+ } else {
+ SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
+ }
+
+ RadialShadeProc shadeProc = shadeSpan_radial_repeat;
+ if (SkShader::kClamp_TileMode == fTileMode) {
+ shadeProc = shadeSpan_radial_clamp;
+ } else if (SkShader::kMirror_TileMode == fTileMode) {
+ shadeProc = shadeSpan_radial_mirror;
+ } else {
+ SkASSERT(SkShader::kRepeat_TileMode == fTileMode);
+ }
+ (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC, cache, count, toggle);
+ } else { // perspective case
+ SkScalar dstX = SkIntToScalar(x);
+ SkScalar dstY = SkIntToScalar(y);
+ do {
+ dstProc(fDstToIndex, dstX, dstY, &srcPt);
+ unsigned fi = proc(SkScalarToFixed(srcPt.length()));
+ SkASSERT(fi <= 0xFFFF);
+ *dstC++ = cache[fi >> SkGradientShaderBase::kCache32Shift];
+ dstX += SK_Scalar1;
+ } while (--count != 0);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+
+#include "GrTBackendEffectFactory.h"
+
+class GrGLRadialGradient : public GrGLGradientEffect {
+public:
+
+ GrGLRadialGradient(const GrBackendEffectFactory& factory,
+ const GrDrawEffect&) : INHERITED (factory) { }
+ virtual ~GrGLRadialGradient() { }
+
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ static EffectKey GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
+ return GenMatrixKey(drawEffect);
+ }
+
+private:
+
+ typedef GrGLGradientEffect INHERITED;
+
+};
+
+/////////////////////////////////////////////////////////////////////
+
+class GrRadialGradient : public GrGradientEffect {
+public:
+ static GrEffectRef* Create(GrContext* ctx,
+ const SkRadialGradient& shader,
+ const SkMatrix& matrix,
+ SkShader::TileMode tm) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrRadialGradient, (ctx, shader, matrix, tm)));
+ return CreateEffectRef(effect);
+ }
+
+ virtual ~GrRadialGradient() { }
+
+ static const char* Name() { return "Radial Gradient"; }
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<GrRadialGradient>::getInstance();
+ }
+
+ typedef GrGLRadialGradient GLEffect;
+
+private:
+ GrRadialGradient(GrContext* ctx,
+ const SkRadialGradient& shader,
+ const SkMatrix& matrix,
+ SkShader::TileMode tm)
+ : INHERITED(ctx, shader, matrix, tm) {
+ }
+
+ GR_DECLARE_EFFECT_TEST;
+
+ typedef GrGradientEffect INHERITED;
+};
+
+/////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_EFFECT_TEST(GrRadialGradient);
+
+GrEffectRef* GrRadialGradient::TestCreate(SkMWCRandom* random,
+ GrContext* context,
+ const GrDrawTargetCaps&,
+ GrTexture**) {
+ SkPoint center = {random->nextUScalar1(), random->nextUScalar1()};
+ SkScalar radius = random->nextUScalar1();
+
+ SkColor colors[kMaxRandomGradientColors];
+ SkScalar stopsArray[kMaxRandomGradientColors];
+ SkScalar* stops = stopsArray;
+ SkShader::TileMode tm;
+ int colorCount = RandomGradientParams(random, colors, &stops, &tm);
+ SkAutoTUnref<SkShader> shader(SkGradientShader::CreateRadial(center, radius,
+ colors, stops, colorCount,
+ tm));
+ SkPaint paint;
+ return shader->asNewEffect(context, paint);
+}
+
+/////////////////////////////////////////////////////////////////////
+
+void GrGLRadialGradient::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect&,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ this->emitYCoordUniform(builder);
+ const char* coords;
+ this->setupMatrix(builder, key, &coords);
+ SkString t("length(");
+ t.append(coords);
+ t.append(")");
+ this->emitColorLookup(builder, t.c_str(), outputColor, inputColor, samplers[0]);
+}
+
+/////////////////////////////////////////////////////////////////////
+
+GrEffectRef* SkRadialGradient::asNewEffect(GrContext* context, const SkPaint&) const {
+ SkASSERT(NULL != context);
+
+ SkMatrix matrix;
+ if (!this->getLocalMatrix().invert(&matrix)) {
+ return NULL;
+ }
+ matrix.postConcat(fPtsToUnit);
+ return GrRadialGradient::Create(context, *this, matrix, fTileMode);
+}
+
+#else
+
+GrEffectRef* SkRadialGradient::asNewEffect(GrContext*, const SkPaint&) const {
+ SkDEBUGFAIL("Should not call in GPU-less build");
+ return NULL;
+}
+
+#endif
+
+#ifdef SK_DEVELOPER
+void SkRadialGradient::toString(SkString* str) const {
+ str->append("SkRadialGradient: (");
+
+ str->append("center: (");
+ str->appendScalar(fCenter.fX);
+ str->append(", ");
+ str->appendScalar(fCenter.fY);
+ str->append(") radius: ");
+ str->appendScalar(fRadius);
+ str->append(" ");
+
+ this->INHERITED::toString(str);
+
+ str->append(")");
+}
+#endif
diff --git a/effects/gradients/SkRadialGradient.h b/effects/gradients/SkRadialGradient.h
new file mode 100644
index 00000000..fa0a969c
--- /dev/null
+++ b/effects/gradients/SkRadialGradient.h
@@ -0,0 +1,40 @@
+
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkRadialGradient_DEFINED
+#define SkRadialGradient_DEFINED
+
+#include "SkGradientShaderPriv.h"
+
+class SkRadialGradient : public SkGradientShaderBase {
+public:
+ SkRadialGradient(const SkPoint& center, SkScalar radius, const Descriptor&);
+ virtual void shadeSpan(int x, int y, SkPMColor* dstC, int count)
+ SK_OVERRIDE;
+ virtual void shadeSpan16(int x, int y, uint16_t* dstCParam,
+ int count) SK_OVERRIDE;
+ virtual BitmapType asABitmap(SkBitmap* bitmap,
+ SkMatrix* matrix,
+ TileMode* xy) const SK_OVERRIDE;
+ virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE;
+ virtual GrEffectRef* asNewEffect(GrContext* context, const SkPaint&) const SK_OVERRIDE;
+
+ SK_DEVELOPER_TO_STRING()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkRadialGradient)
+
+protected:
+ SkRadialGradient(SkFlattenableReadBuffer& buffer);
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE;
+
+private:
+ typedef SkGradientShaderBase INHERITED;
+ const SkPoint fCenter;
+ const SkScalar fRadius;
+};
+
+#endif
diff --git a/effects/gradients/SkRadialGradient_Table.h b/effects/gradients/SkRadialGradient_Table.h
new file mode 100644
index 00000000..9a8a5f81
--- /dev/null
+++ b/effects/gradients/SkRadialGradient_Table.h
@@ -0,0 +1,139 @@
+
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+static const uint8_t gSqrt8Table[] = {
+ 0x00, 0x05, 0x08, 0x09, 0x0B, 0x0C, 0x0D, 0x0E, 0x10, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x15,
+ 0x16, 0x17, 0x18, 0x18, 0x19, 0x19, 0x1A, 0x1B, 0x1B, 0x1C, 0x1C, 0x1D, 0x1D, 0x1E, 0x1E, 0x1F,
+ 0x20, 0x20, 0x20, 0x21, 0x21, 0x22, 0x22, 0x23, 0x23, 0x24, 0x24, 0x25, 0x25, 0x25, 0x26, 0x26,
+ 0x27, 0x27, 0x28, 0x28, 0x28, 0x29, 0x29, 0x29, 0x2A, 0x2A, 0x2B, 0x2B, 0x2B, 0x2C, 0x2C, 0x2C,
+ 0x2D, 0x2D, 0x2D, 0x2E, 0x2E, 0x2E, 0x2F, 0x2F, 0x30, 0x30, 0x30, 0x30, 0x31, 0x31, 0x31, 0x32,
+ 0x32, 0x32, 0x33, 0x33, 0x33, 0x34, 0x34, 0x34, 0x35, 0x35, 0x35, 0x35, 0x36, 0x36, 0x36, 0x37,
+ 0x37, 0x37, 0x38, 0x38, 0x38, 0x38, 0x39, 0x39, 0x39, 0x39, 0x3A, 0x3A, 0x3A, 0x3B, 0x3B, 0x3B,
+ 0x3B, 0x3C, 0x3C, 0x3C, 0x3C, 0x3D, 0x3D, 0x3D, 0x3D, 0x3E, 0x3E, 0x3E, 0x3E, 0x3F, 0x3F, 0x3F,
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x41, 0x41, 0x41, 0x41, 0x42, 0x42, 0x42, 0x42, 0x43, 0x43, 0x43,
+ 0x43, 0x44, 0x44, 0x44, 0x44, 0x45, 0x45, 0x45, 0x45, 0x45, 0x46, 0x46, 0x46, 0x46, 0x47, 0x47,
+ 0x47, 0x47, 0x48, 0x48, 0x48, 0x48, 0x48, 0x49, 0x49, 0x49, 0x49, 0x49, 0x4A, 0x4A, 0x4A, 0x4A,
+ 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4C, 0x4C, 0x4C, 0x4C, 0x4C, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4E,
+ 0x4E, 0x4E, 0x4E, 0x4E, 0x4F, 0x4F, 0x4F, 0x4F, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x51, 0x51,
+ 0x51, 0x51, 0x51, 0x52, 0x52, 0x52, 0x52, 0x52, 0x53, 0x53, 0x53, 0x53, 0x53, 0x54, 0x54, 0x54,
+ 0x54, 0x54, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x56, 0x56, 0x56, 0x56, 0x56, 0x57, 0x57, 0x57,
+ 0x57, 0x57, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x5A, 0x5A,
+ 0x5A, 0x5A, 0x5A, 0x5B, 0x5B, 0x5B, 0x5B, 0x5B, 0x5B, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5D,
+ 0x5D, 0x5D, 0x5D, 0x5D, 0x5D, 0x5E, 0x5E, 0x5E, 0x5E, 0x5E, 0x5E, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F,
+ 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x62, 0x62, 0x62,
+ 0x62, 0x62, 0x62, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x65,
+ 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x67, 0x67, 0x67, 0x67,
+ 0x67, 0x67, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
+ 0x6A, 0x6A, 0x6A, 0x6A, 0x6A, 0x6A, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6C, 0x6C, 0x6C,
+ 0x6C, 0x6C, 0x6C, 0x6C, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6E, 0x6E, 0x6E, 0x6E, 0x6E,
+ 0x6E, 0x6E, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70,
+ 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x73, 0x73,
+ 0x73, 0x73, 0x73, 0x73, 0x73, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x75, 0x75, 0x75, 0x75,
+ 0x75, 0x75, 0x75, 0x75, 0x76, 0x76, 0x76, 0x76, 0x76, 0x76, 0x76, 0x77, 0x77, 0x77, 0x77, 0x77,
+ 0x77, 0x77, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79,
+ 0x79, 0x79, 0x7A, 0x7A, 0x7A, 0x7A, 0x7A, 0x7A, 0x7A, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B,
+ 0x7B, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7D, 0x7D, 0x7D, 0x7D, 0x7D, 0x7D, 0x7D,
+ 0x7D, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8B, 0x8B, 0x8B, 0x8B,
+ 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8D, 0x8D,
+ 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E,
+ 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
+ 0x90, 0x90, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x92, 0x92, 0x92, 0x92, 0x92,
+ 0x92, 0x92, 0x92, 0x92, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x94, 0x94, 0x94,
+ 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95,
+ 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97,
+ 0x97, 0x97, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x99, 0x99, 0x99, 0x99,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9B,
+ 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C,
+ 0x9C, 0x9C, 0x9C, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9E, 0x9E, 0x9E,
+ 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F,
+ 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1,
+ 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA3,
+ 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4,
+ 0xA4, 0xA4, 0xA4, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA6, 0xA6,
+ 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7,
+ 0xA7, 0xA7, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA9, 0xA9, 0xA9,
+ 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+ 0xAA, 0xAA, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAC, 0xAC, 0xAC,
+ 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD,
+ 0xAD, 0xAD, 0xAD, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAF, 0xAF,
+ 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0,
+ 0xB0, 0xB0, 0xB0, 0xB0, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB2,
+ 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3,
+ 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4,
+ 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6,
+ 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7,
+ 0xB7, 0xB7, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB9, 0xB9,
+ 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA,
+ 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+ 0xBB, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBD, 0xBD, 0xBD,
+ 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE,
+ 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF,
+ 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC1, 0xC1, 0xC1,
+ 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2,
+ 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,
+ 0xC3, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC5, 0xC5, 0xC5,
+ 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6,
+ 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7,
+ 0xC7, 0xC7, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC9,
+ 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xCA, 0xCA, 0xCA, 0xCA,
+ 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, 0xCB,
+ 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC,
+ 0xCC, 0xCC, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCE,
+ 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCF, 0xCF, 0xCF, 0xCF,
+ 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xD0, 0xD0, 0xD0, 0xD0, 0xD0, 0xD0, 0xD0, 0xD0,
+ 0xD0, 0xD0, 0xD0, 0xD0, 0xD0, 0xD0, 0xD1, 0xD1, 0xD1, 0xD1, 0xD1, 0xD1, 0xD1, 0xD1, 0xD1, 0xD1,
+ 0xD1, 0xD1, 0xD1, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2,
+ 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD4, 0xD4, 0xD4,
+ 0xD4, 0xD4, 0xD4, 0xD4, 0xD4, 0xD4, 0xD4, 0xD4, 0xD4, 0xD4, 0xD5, 0xD5, 0xD5, 0xD5, 0xD5, 0xD5,
+ 0xD5, 0xD5, 0xD5, 0xD5, 0xD5, 0xD5, 0xD5, 0xD5, 0xD6, 0xD6, 0xD6, 0xD6, 0xD6, 0xD6, 0xD6, 0xD6,
+ 0xD6, 0xD6, 0xD6, 0xD6, 0xD6, 0xD7, 0xD7, 0xD7, 0xD7, 0xD7, 0xD7, 0xD7, 0xD7, 0xD7, 0xD7, 0xD7,
+ 0xD7, 0xD7, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8,
+ 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xDA, 0xDA,
+ 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB,
+ 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDC, 0xDC, 0xDC, 0xDC, 0xDC, 0xDC, 0xDC,
+ 0xDC, 0xDC, 0xDC, 0xDC, 0xDC, 0xDC, 0xDC, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
+ 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE,
+ 0xDE, 0xDE, 0xDE, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF,
+ 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE1,
+ 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE2, 0xE2, 0xE2,
+ 0xE2, 0xE2, 0xE2, 0xE2, 0xE2, 0xE2, 0xE2, 0xE2, 0xE2, 0xE2, 0xE2, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3,
+ 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4,
+ 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5,
+ 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6,
+ 0xE6, 0xE6, 0xE6, 0xE6, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7,
+ 0xE7, 0xE7, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8,
+ 0xE8, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9,
+ 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEB, 0xEB,
+ 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, 0xEC, 0xEC, 0xEC,
+ 0xEC, 0xEC, 0xEC, 0xEC, 0xEC, 0xEC, 0xEC, 0xEC, 0xEC, 0xEC, 0xEC, 0xEC, 0xED, 0xED, 0xED, 0xED,
+ 0xED, 0xED, 0xED, 0xED, 0xED, 0xED, 0xED, 0xED, 0xED, 0xED, 0xED, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE,
+ 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, 0xEF,
+ 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
+ 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1,
+ 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2,
+ 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3,
+ 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF4, 0xF4, 0xF4, 0xF4, 0xF4, 0xF4, 0xF4, 0xF4, 0xF4, 0xF4, 0xF4,
+ 0xF4, 0xF4, 0xF4, 0xF4, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5,
+ 0xF5, 0xF5, 0xF5, 0xF5, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6,
+ 0xF6, 0xF6, 0xF6, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7,
+ 0xF7, 0xF7, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8,
+ 0xF8, 0xF8, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9,
+ 0xF9, 0xF9, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA,
+ 0xFA, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB,
+ 0xFB, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC,
+ 0xFC, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD,
+ 0xFD, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE,
+ 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
+};
diff --git a/effects/gradients/SkSweepGradient.cpp b/effects/gradients/SkSweepGradient.cpp
new file mode 100644
index 00000000..f975a188
--- /dev/null
+++ b/effects/gradients/SkSweepGradient.cpp
@@ -0,0 +1,517 @@
+
+/*
+ * 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 "SkSweepGradient.h"
+
+SkSweepGradient::SkSweepGradient(SkScalar cx, SkScalar cy,
+ const Descriptor& desc)
+ : SkGradientShaderBase(desc)
+ , fCenter(SkPoint::Make(cx, cy))
+{
+ fPtsToUnit.setTranslate(-cx, -cy);
+
+ // overwrite the tilemode to a canonical value (since sweep ignores it)
+ fTileMode = SkShader::kClamp_TileMode;
+}
+
+SkShader::BitmapType SkSweepGradient::asABitmap(SkBitmap* bitmap,
+ SkMatrix* matrix, SkShader::TileMode* xy) const {
+ if (bitmap) {
+ this->getGradientTableBitmap(bitmap);
+ }
+ if (matrix) {
+ *matrix = fPtsToUnit;
+ }
+ if (xy) {
+ xy[0] = fTileMode;
+ xy[1] = kClamp_TileMode;
+ }
+ return kSweep_BitmapType;
+}
+
+SkShader::GradientType SkSweepGradient::asAGradient(GradientInfo* info) const {
+ if (info) {
+ commonAsAGradient(info);
+ info->fPoint[0] = fCenter;
+ }
+ return kSweep_GradientType;
+}
+
+SkSweepGradient::SkSweepGradient(SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer),
+ fCenter(buffer.readPoint()) {
+}
+
+void SkSweepGradient::flatten(SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writePoint(fCenter);
+}
+
+#ifndef SK_SCALAR_IS_FLOAT
+#ifdef COMPUTE_SWEEP_TABLE
+#define PI 3.14159265
+static bool gSweepTableReady;
+static uint8_t gSweepTable[65];
+
+/* Our table stores precomputed values for atan: [0...1] -> [0..PI/4]
+ We scale the results to [0..32]
+*/
+static const uint8_t* build_sweep_table() {
+ if (!gSweepTableReady) {
+ const int N = 65;
+ const double DENOM = N - 1;
+
+ for (int i = 0; i < N; i++)
+ {
+ double arg = i / DENOM;
+ double v = atan(arg);
+ int iv = (int)round(v * DENOM * 2 / PI);
+// printf("[%d] atan(%g) = %g %d\n", i, arg, v, iv);
+ printf("%d, ", iv);
+ gSweepTable[i] = iv;
+ }
+ gSweepTableReady = true;
+ }
+ return gSweepTable;
+}
+#else
+static const uint8_t gSweepTable[] = {
+ 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9,
+ 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18,
+ 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26,
+ 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32,
+ 32
+};
+static const uint8_t* build_sweep_table() { return gSweepTable; }
+#endif
+#endif
+
+// divide numer/denom, with a bias of 6bits. Assumes numer <= denom
+// and denom != 0. Since our table is 6bits big (+1), this is a nice fit.
+// Same as (but faster than) SkFixedDiv(numer, denom) >> 10
+
+//unsigned div_64(int numer, int denom);
+#ifndef SK_SCALAR_IS_FLOAT
+static unsigned div_64(int numer, int denom) {
+ SkASSERT(numer <= denom);
+ SkASSERT(numer > 0);
+ SkASSERT(denom > 0);
+
+ int nbits = SkCLZ(numer);
+ int dbits = SkCLZ(denom);
+ int bits = 6 - nbits + dbits;
+ SkASSERT(bits <= 6);
+
+ if (bits < 0) { // detect underflow
+ return 0;
+ }
+
+ denom <<= dbits - 1;
+ numer <<= nbits - 1;
+
+ unsigned result = 0;
+
+ // do the first one
+ if ((numer -= denom) >= 0) {
+ result = 1;
+ } else {
+ numer += denom;
+ }
+
+ // Now fall into our switch statement if there are more bits to compute
+ if (bits > 0) {
+ // make room for the rest of the answer bits
+ result <<= bits;
+ switch (bits) {
+ case 6:
+ if ((numer = (numer << 1) - denom) >= 0)
+ result |= 32;
+ else
+ numer += denom;
+ case 5:
+ if ((numer = (numer << 1) - denom) >= 0)
+ result |= 16;
+ else
+ numer += denom;
+ case 4:
+ if ((numer = (numer << 1) - denom) >= 0)
+ result |= 8;
+ else
+ numer += denom;
+ case 3:
+ if ((numer = (numer << 1) - denom) >= 0)
+ result |= 4;
+ else
+ numer += denom;
+ case 2:
+ if ((numer = (numer << 1) - denom) >= 0)
+ result |= 2;
+ else
+ numer += denom;
+ case 1:
+ default: // not strictly need, but makes GCC make better ARM code
+ if ((numer = (numer << 1) - denom) >= 0)
+ result |= 1;
+ else
+ numer += denom;
+ }
+ }
+ return result;
+}
+#endif
+
+// Given x,y in the first quadrant, return 0..63 for the angle [0..90]
+#ifndef SK_SCALAR_IS_FLOAT
+static unsigned atan_0_90(SkFixed y, SkFixed x) {
+#ifdef SK_DEBUG
+ {
+ static bool gOnce;
+ if (!gOnce) {
+ gOnce = true;
+ SkASSERT(div_64(55, 55) == 64);
+ SkASSERT(div_64(128, 256) == 32);
+ SkASSERT(div_64(2326528, 4685824) == 31);
+ SkASSERT(div_64(753664, 5210112) == 9);
+ SkASSERT(div_64(229376, 4882432) == 3);
+ SkASSERT(div_64(2, 64) == 2);
+ SkASSERT(div_64(1, 64) == 1);
+ // test that we handle underflow correctly
+ SkASSERT(div_64(12345, 0x54321234) == 0);
+ }
+ }
+#endif
+
+ SkASSERT(y > 0 && x > 0);
+ const uint8_t* table = build_sweep_table();
+
+ unsigned result;
+ bool swap = (x < y);
+ if (swap) {
+ // first part of the atan(v) = PI/2 - atan(1/v) identity
+ // since our div_64 and table want v <= 1, where v = y/x
+ SkTSwap<SkFixed>(x, y);
+ }
+
+ result = div_64(y, x);
+
+#ifdef SK_DEBUG
+ {
+ unsigned result2 = SkDivBits(y, x, 6);
+ SkASSERT(result2 == result ||
+ (result == 1 && result2 == 0));
+ }
+#endif
+
+ SkASSERT(result < SK_ARRAY_COUNT(gSweepTable));
+ result = table[result];
+
+ if (swap) {
+ // complete the atan(v) = PI/2 - atan(1/v) identity
+ result = 64 - result;
+ // pin to 63
+ result -= result >> 6;
+ }
+
+ SkASSERT(result <= 63);
+ return result;
+}
+#endif
+
+// returns angle in a circle [0..2PI) -> [0..255]
+#ifdef SK_SCALAR_IS_FLOAT
+static unsigned SkATan2_255(float y, float x) {
+ // static const float g255Over2PI = 255 / (2 * SK_ScalarPI);
+ static const float g255Over2PI = 40.584510488433314f;
+
+ float result = sk_float_atan2(y, x);
+ if (result < 0) {
+ result += 2 * SK_ScalarPI;
+ }
+ SkASSERT(result >= 0);
+ // since our value is always >= 0, we can cast to int, which is faster than
+ // calling floorf()
+ int ir = (int)(result * g255Over2PI);
+ SkASSERT(ir >= 0 && ir <= 255);
+ return ir;
+}
+#else
+static unsigned SkATan2_255(SkFixed y, SkFixed x) {
+ if (x == 0) {
+ if (y == 0) {
+ return 0;
+ }
+ return y < 0 ? 192 : 64;
+ }
+ if (y == 0) {
+ return x < 0 ? 128 : 0;
+ }
+
+ /* Find the right quadrant for x,y
+ Since atan_0_90 only handles the first quadrant, we rotate x,y
+ appropriately before calling it, and then add the right amount
+ to account for the real quadrant.
+ quadrant 0 : add 0 | x > 0 && y > 0
+ quadrant 1 : add 64 (90 degrees) | x < 0 && y > 0
+ quadrant 2 : add 128 (180 degrees) | x < 0 && y < 0
+ quadrant 3 : add 192 (270 degrees) | x > 0 && y < 0
+
+ map x<0 to (1 << 6)
+ map y<0 to (3 << 6)
+ add = map_x ^ map_y
+ */
+ int xsign = x >> 31;
+ int ysign = y >> 31;
+ int add = ((-xsign) ^ (ysign & 3)) << 6;
+
+#ifdef SK_DEBUG
+ if (0 == add)
+ SkASSERT(x > 0 && y > 0);
+ else if (64 == add)
+ SkASSERT(x < 0 && y > 0);
+ else if (128 == add)
+ SkASSERT(x < 0 && y < 0);
+ else if (192 == add)
+ SkASSERT(x > 0 && y < 0);
+ else
+ SkDEBUGFAIL("bad value for add");
+#endif
+
+ /* This ^ trick makes x, y positive, and the swap<> handles quadrants
+ where we need to rotate x,y by 90 or -90
+ */
+ x = (x ^ xsign) - xsign;
+ y = (y ^ ysign) - ysign;
+ if (add & 64) { // quads 1 or 3 need to swap x,y
+ SkTSwap<SkFixed>(x, y);
+ }
+
+ unsigned result = add + atan_0_90(y, x);
+ SkASSERT(result < 256);
+ return result;
+}
+#endif
+
+void SkSweepGradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC,
+ int count) {
+ SkMatrix::MapXYProc proc = fDstToIndexProc;
+ const SkMatrix& matrix = fDstToIndex;
+ const SkPMColor* SK_RESTRICT cache = this->getCache32();
+ int toggle = init_dither_toggle(x, y);
+ SkPoint srcPt;
+
+ if (fDstToIndexClass != kPerspective_MatrixClass) {
+ proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+ SkScalar dx, fx = srcPt.fX;
+ SkScalar dy, fy = srcPt.fY;
+
+ if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
+ SkFixed storage[2];
+ (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
+ &storage[0], &storage[1]);
+ dx = SkFixedToScalar(storage[0]);
+ dy = SkFixedToScalar(storage[1]);
+ } else {
+ SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
+ dx = matrix.getScaleX();
+ dy = matrix.getSkewY();
+ }
+
+ for (; count > 0; --count) {
+ *dstC++ = cache[toggle + SkATan2_255(fy, fx)];
+ fx += dx;
+ fy += dy;
+ toggle = next_dither_toggle(toggle);
+ }
+ } else { // perspective case
+ for (int stop = x + count; x < stop; x++) {
+ proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+ *dstC++ = cache[toggle + SkATan2_255(srcPt.fY, srcPt.fX)];
+ toggle = next_dither_toggle(toggle);
+ }
+ }
+}
+
+void SkSweepGradient::shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC,
+ int count) {
+ SkMatrix::MapXYProc proc = fDstToIndexProc;
+ const SkMatrix& matrix = fDstToIndex;
+ const uint16_t* SK_RESTRICT cache = this->getCache16();
+ int toggle = init_dither_toggle16(x, y);
+ SkPoint srcPt;
+
+ if (fDstToIndexClass != kPerspective_MatrixClass) {
+ proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+ SkScalar dx, fx = srcPt.fX;
+ SkScalar dy, fy = srcPt.fY;
+
+ if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
+ SkFixed storage[2];
+ (void)matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf,
+ &storage[0], &storage[1]);
+ dx = SkFixedToScalar(storage[0]);
+ dy = SkFixedToScalar(storage[1]);
+ } else {
+ SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
+ dx = matrix.getScaleX();
+ dy = matrix.getSkewY();
+ }
+
+ for (; count > 0; --count) {
+ int index = SkATan2_255(fy, fx) >> (8 - kCache16Bits);
+ *dstC++ = cache[toggle + index];
+ toggle = next_dither_toggle16(toggle);
+ fx += dx;
+ fy += dy;
+ }
+ } else { // perspective case
+ for (int stop = x + count; x < stop; x++) {
+ proc(matrix, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+
+ int index = SkATan2_255(srcPt.fY, srcPt.fX);
+ index >>= (8 - kCache16Bits);
+ *dstC++ = cache[toggle + index];
+ toggle = next_dither_toggle16(toggle);
+ }
+ }
+}
+
+/////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+
+#include "GrTBackendEffectFactory.h"
+
+class GrGLSweepGradient : public GrGLGradientEffect {
+public:
+
+ GrGLSweepGradient(const GrBackendEffectFactory& factory,
+ const GrDrawEffect&) : INHERITED (factory) { }
+ virtual ~GrGLSweepGradient() { }
+
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+
+ static EffectKey GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
+ return GenMatrixKey(drawEffect);
+ }
+
+private:
+
+ typedef GrGLGradientEffect INHERITED;
+
+};
+
+/////////////////////////////////////////////////////////////////////
+
+class GrSweepGradient : public GrGradientEffect {
+public:
+ static GrEffectRef* Create(GrContext* ctx,
+ const SkSweepGradient& shader,
+ const SkMatrix& matrix) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrSweepGradient, (ctx, shader, matrix)));
+ return CreateEffectRef(effect);
+ }
+ virtual ~GrSweepGradient() { }
+
+ static const char* Name() { return "Sweep Gradient"; }
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<GrSweepGradient>::getInstance();
+ }
+
+ typedef GrGLSweepGradient GLEffect;
+
+private:
+ GrSweepGradient(GrContext* ctx,
+ const SkSweepGradient& shader,
+ const SkMatrix& matrix)
+ : INHERITED(ctx, shader, matrix, SkShader::kClamp_TileMode) { }
+ GR_DECLARE_EFFECT_TEST;
+
+ typedef GrGradientEffect INHERITED;
+};
+
+/////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_EFFECT_TEST(GrSweepGradient);
+
+GrEffectRef* GrSweepGradient::TestCreate(SkMWCRandom* random,
+ GrContext* context,
+ const GrDrawTargetCaps&,
+ GrTexture**) {
+ SkPoint center = {random->nextUScalar1(), random->nextUScalar1()};
+
+ SkColor colors[kMaxRandomGradientColors];
+ SkScalar stopsArray[kMaxRandomGradientColors];
+ SkScalar* stops = stopsArray;
+ SkShader::TileMode tmIgnored;
+ int colorCount = RandomGradientParams(random, colors, &stops, &tmIgnored);
+ SkAutoTUnref<SkShader> shader(SkGradientShader::CreateSweep(center.fX, center.fY,
+ colors, stops, colorCount));
+ SkPaint paint;
+ return shader->asNewEffect(context, paint);
+}
+
+/////////////////////////////////////////////////////////////////////
+
+void GrGLSweepGradient::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect&,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ this->emitYCoordUniform(builder);
+ const char* coords;
+ this->setupMatrix(builder, key, &coords);
+ SkString t;
+ t.printf("atan(- %s.y, - %s.x) * 0.1591549430918 + 0.5", coords, coords);
+ this->emitColorLookup(builder, t.c_str(), outputColor, inputColor, samplers[0]);
+}
+
+/////////////////////////////////////////////////////////////////////
+
+GrEffectRef* SkSweepGradient::asNewEffect(GrContext* context, const SkPaint&) const {
+ SkMatrix matrix;
+ if (!this->getLocalMatrix().invert(&matrix)) {
+ return NULL;
+ }
+ matrix.postConcat(fPtsToUnit);
+ return GrSweepGradient::Create(context, *this, matrix);
+}
+
+#else
+
+GrEffectRef* SkSweepGradient::asNewEffect(GrContext*, const SkPaint&) const {
+ SkDEBUGFAIL("Should not call in GPU-less build");
+ return NULL;
+}
+
+#endif
+
+#ifdef SK_DEVELOPER
+void SkSweepGradient::toString(SkString* str) const {
+ str->append("SkSweepGradient: (");
+
+ str->append("center: (");
+ str->appendScalar(fCenter.fX);
+ str->append(", ");
+ str->appendScalar(fCenter.fY);
+ str->append(") ");
+
+ this->INHERITED::toString(str);
+
+ str->append(")");
+}
+#endif
diff --git a/effects/gradients/SkSweepGradient.h b/effects/gradients/SkSweepGradient.h
new file mode 100644
index 00000000..8b685bc2
--- /dev/null
+++ b/effects/gradients/SkSweepGradient.h
@@ -0,0 +1,40 @@
+
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkSweepGradient_DEFINED
+#define SkSweepGradient_DEFINED
+
+#include "SkGradientShaderPriv.h"
+
+class SkSweepGradient : public SkGradientShaderBase {
+public:
+ SkSweepGradient(SkScalar cx, SkScalar cy, const Descriptor&);
+ virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count) SK_OVERRIDE;
+ virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count) SK_OVERRIDE;
+
+ virtual BitmapType asABitmap(SkBitmap* bitmap,
+ SkMatrix* matrix,
+ TileMode* xy) const SK_OVERRIDE;
+
+ virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE;
+
+ virtual GrEffectRef* asNewEffect(GrContext* context, const SkPaint&) const SK_OVERRIDE;
+
+ SK_DEVELOPER_TO_STRING()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkSweepGradient)
+
+protected:
+ SkSweepGradient(SkFlattenableReadBuffer& buffer);
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE;
+
+private:
+ typedef SkGradientShaderBase INHERITED;
+ const SkPoint fCenter;
+};
+
+#endif
diff --git a/effects/gradients/SkTwoPointConicalGradient.cpp b/effects/gradients/SkTwoPointConicalGradient.cpp
new file mode 100644
index 00000000..37b49f0d
--- /dev/null
+++ b/effects/gradients/SkTwoPointConicalGradient.cpp
@@ -0,0 +1,765 @@
+
+/*
+ * 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 "SkTwoPointConicalGradient.h"
+
+static int valid_divide(float numer, float denom, float* ratio) {
+ SkASSERT(ratio);
+ if (0 == denom) {
+ return 0;
+ }
+ *ratio = numer / denom;
+ return 1;
+}
+
+// Return the number of distinct real roots, and write them into roots[] in
+// ascending order
+static int find_quad_roots(float A, float B, float C, float roots[2]) {
+ SkASSERT(roots);
+
+ if (A == 0) {
+ return valid_divide(-C, B, roots);
+ }
+
+ float R = B*B - 4*A*C;
+ if (R < 0) {
+ return 0;
+ }
+ R = sk_float_sqrt(R);
+
+#if 1
+ float Q = B;
+ if (Q < 0) {
+ Q -= R;
+ } else {
+ Q += R;
+ }
+#else
+ // on 10.6 this was much slower than the above branch :(
+ float Q = B + copysignf(R, B);
+#endif
+ Q *= -0.5f;
+ if (0 == Q) {
+ roots[0] = 0;
+ return 1;
+ }
+
+ float r0 = Q / A;
+ float r1 = C / Q;
+ roots[0] = r0 < r1 ? r0 : r1;
+ roots[1] = r0 > r1 ? r0 : r1;
+ return 2;
+}
+
+static float lerp(float x, float dx, float t) {
+ return x + t * dx;
+}
+
+static float sqr(float x) { return x * x; }
+
+void TwoPtRadial::init(const SkPoint& center0, SkScalar rad0,
+ const SkPoint& center1, SkScalar rad1) {
+ fCenterX = SkScalarToFloat(center0.fX);
+ fCenterY = SkScalarToFloat(center0.fY);
+ fDCenterX = SkScalarToFloat(center1.fX) - fCenterX;
+ fDCenterY = SkScalarToFloat(center1.fY) - fCenterY;
+ fRadius = SkScalarToFloat(rad0);
+ fDRadius = SkScalarToFloat(rad1) - fRadius;
+
+ fA = sqr(fDCenterX) + sqr(fDCenterY) - sqr(fDRadius);
+ fRadius2 = sqr(fRadius);
+ fRDR = fRadius * fDRadius;
+}
+
+void TwoPtRadial::setup(SkScalar fx, SkScalar fy, SkScalar dfx, SkScalar dfy) {
+ fRelX = SkScalarToFloat(fx) - fCenterX;
+ fRelY = SkScalarToFloat(fy) - fCenterY;
+ fIncX = SkScalarToFloat(dfx);
+ fIncY = SkScalarToFloat(dfy);
+ fB = -2 * (fDCenterX * fRelX + fDCenterY * fRelY + fRDR);
+ fDB = -2 * (fDCenterX * fIncX + fDCenterY * fIncY);
+}
+
+SkFixed TwoPtRadial::nextT() {
+ float roots[2];
+
+ float C = sqr(fRelX) + sqr(fRelY) - fRadius2;
+ int countRoots = find_quad_roots(fA, fB, C, roots);
+
+ fRelX += fIncX;
+ fRelY += fIncY;
+ fB += fDB;
+
+ if (0 == countRoots) {
+ return kDontDrawT;
+ }
+
+ // Prefer the bigger t value if both give a radius(t) > 0
+ // find_quad_roots returns the values sorted, so we start with the last
+ float t = roots[countRoots - 1];
+ float r = lerp(fRadius, fDRadius, t);
+ if (r <= 0) {
+ t = roots[0]; // might be the same as roots[countRoots-1]
+ r = lerp(fRadius, fDRadius, t);
+ if (r <= 0) {
+ return kDontDrawT;
+ }
+ }
+ return SkFloatToFixed(t);
+}
+
+typedef void (*TwoPointConicalProc)(TwoPtRadial* rec, SkPMColor* dstC,
+ const SkPMColor* cache, int toggle, int count);
+
+static void twopoint_clamp(TwoPtRadial* rec, SkPMColor* SK_RESTRICT dstC,
+ const SkPMColor* SK_RESTRICT cache, int toggle,
+ int count) {
+ for (; count > 0; --count) {
+ SkFixed t = rec->nextT();
+ if (TwoPtRadial::DontDrawT(t)) {
+ *dstC++ = 0;
+ } else {
+ SkFixed index = SkClampMax(t, 0xFFFF);
+ SkASSERT(index <= 0xFFFF);
+ *dstC++ = cache[toggle +
+ (index >> SkGradientShaderBase::kCache32Shift)];
+ }
+ toggle = next_dither_toggle(toggle);
+ }
+}
+
+static void twopoint_repeat(TwoPtRadial* rec, SkPMColor* SK_RESTRICT dstC,
+ const SkPMColor* SK_RESTRICT cache, int toggle,
+ int count) {
+ for (; count > 0; --count) {
+ SkFixed t = rec->nextT();
+ if (TwoPtRadial::DontDrawT(t)) {
+ *dstC++ = 0;
+ } else {
+ SkFixed index = repeat_tileproc(t);
+ SkASSERT(index <= 0xFFFF);
+ *dstC++ = cache[toggle +
+ (index >> SkGradientShaderBase::kCache32Shift)];
+ }
+ toggle = next_dither_toggle(toggle);
+ }
+}
+
+static void twopoint_mirror(TwoPtRadial* rec, SkPMColor* SK_RESTRICT dstC,
+ const SkPMColor* SK_RESTRICT cache, int toggle,
+ int count) {
+ for (; count > 0; --count) {
+ SkFixed t = rec->nextT();
+ if (TwoPtRadial::DontDrawT(t)) {
+ *dstC++ = 0;
+ } else {
+ SkFixed index = mirror_tileproc(t);
+ SkASSERT(index <= 0xFFFF);
+ *dstC++ = cache[toggle +
+ (index >> SkGradientShaderBase::kCache32Shift)];
+ }
+ toggle = next_dither_toggle(toggle);
+ }
+}
+
+void SkTwoPointConicalGradient::init() {
+ fRec.init(fCenter1, fRadius1, fCenter2, fRadius2);
+ fPtsToUnit.reset();
+}
+
+/////////////////////////////////////////////////////////////////////
+
+SkTwoPointConicalGradient::SkTwoPointConicalGradient(
+ const SkPoint& start, SkScalar startRadius,
+ const SkPoint& end, SkScalar endRadius,
+ const Descriptor& desc)
+ : SkGradientShaderBase(desc),
+ fCenter1(start),
+ fCenter2(end),
+ fRadius1(startRadius),
+ fRadius2(endRadius) {
+ // this is degenerate, and should be caught by our caller
+ SkASSERT(fCenter1 != fCenter2 || fRadius1 != fRadius2);
+ this->init();
+}
+
+bool SkTwoPointConicalGradient::isOpaque() const {
+ // Because areas outside the cone are left untouched, we cannot treat the
+ // shader as opaque even if the gradient itself is opaque.
+ // TODO(junov): Compute whether the cone fills the plane crbug.com/222380
+ return false;
+}
+
+void SkTwoPointConicalGradient::shadeSpan(int x, int y, SkPMColor* dstCParam,
+ int count) {
+ int toggle = init_dither_toggle(x, y);
+
+ SkASSERT(count > 0);
+
+ SkPMColor* SK_RESTRICT dstC = dstCParam;
+
+ SkMatrix::MapXYProc dstProc = fDstToIndexProc;
+
+ const SkPMColor* SK_RESTRICT cache = this->getCache32();
+
+ TwoPointConicalProc shadeProc = twopoint_repeat;
+ if (SkShader::kClamp_TileMode == fTileMode) {
+ shadeProc = twopoint_clamp;
+ } else if (SkShader::kMirror_TileMode == fTileMode) {
+ shadeProc = twopoint_mirror;
+ } else {
+ SkASSERT(SkShader::kRepeat_TileMode == fTileMode);
+ }
+
+ if (fDstToIndexClass != kPerspective_MatrixClass) {
+ SkPoint srcPt;
+ dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+ SkScalar dx, fx = srcPt.fX;
+ SkScalar dy, fy = srcPt.fY;
+
+ if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
+ SkFixed fixedX, fixedY;
+ (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
+ dx = SkFixedToScalar(fixedX);
+ dy = SkFixedToScalar(fixedY);
+ } else {
+ SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
+ dx = fDstToIndex.getScaleX();
+ dy = fDstToIndex.getSkewY();
+ }
+
+ fRec.setup(fx, fy, dx, dy);
+ (*shadeProc)(&fRec, dstC, cache, toggle, count);
+ } else { // perspective case
+ SkScalar dstX = SkIntToScalar(x);
+ SkScalar dstY = SkIntToScalar(y);
+ for (; count > 0; --count) {
+ SkPoint srcPt;
+ dstProc(fDstToIndex, dstX, dstY, &srcPt);
+ dstX += SK_Scalar1;
+
+ fRec.setup(srcPt.fX, srcPt.fY, 0, 0);
+ (*shadeProc)(&fRec, dstC, cache, toggle, 1);
+ toggle = next_dither_toggle(toggle);
+ }
+ }
+}
+
+bool SkTwoPointConicalGradient::setContext(const SkBitmap& device,
+ const SkPaint& paint,
+ const SkMatrix& matrix) {
+ if (!this->INHERITED::setContext(device, paint, matrix)) {
+ return false;
+ }
+
+ // we don't have a span16 proc
+ fFlags &= ~kHasSpan16_Flag;
+
+ // in general, we might discard based on computed-radius, so clear
+ // this flag (todo: sometimes we can detect that we never discard...)
+ fFlags &= ~kOpaqueAlpha_Flag;
+
+ return true;
+}
+
+SkShader::BitmapType SkTwoPointConicalGradient::asABitmap(
+ SkBitmap* bitmap, SkMatrix* matrix, SkShader::TileMode* xy) const {
+ SkPoint diff = fCenter2 - fCenter1;
+ SkScalar diffLen = 0;
+
+ if (bitmap) {
+ this->getGradientTableBitmap(bitmap);
+ }
+ if (matrix) {
+ diffLen = diff.length();
+ }
+ if (matrix) {
+ if (diffLen) {
+ SkScalar invDiffLen = SkScalarInvert(diffLen);
+ // rotate to align circle centers with the x-axis
+ matrix->setSinCos(-SkScalarMul(invDiffLen, diff.fY),
+ SkScalarMul(invDiffLen, diff.fX));
+ } else {
+ matrix->reset();
+ }
+ matrix->preTranslate(-fCenter1.fX, -fCenter1.fY);
+ }
+ if (xy) {
+ xy[0] = fTileMode;
+ xy[1] = kClamp_TileMode;
+ }
+ return kTwoPointConical_BitmapType;
+}
+
+SkShader::GradientType SkTwoPointConicalGradient::asAGradient(
+ GradientInfo* info) const {
+ if (info) {
+ commonAsAGradient(info);
+ info->fPoint[0] = fCenter1;
+ info->fPoint[1] = fCenter2;
+ info->fRadius[0] = fRadius1;
+ info->fRadius[1] = fRadius2;
+ }
+ return kConical_GradientType;
+}
+
+SkTwoPointConicalGradient::SkTwoPointConicalGradient(
+ SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer),
+ fCenter1(buffer.readPoint()),
+ fCenter2(buffer.readPoint()),
+ fRadius1(buffer.readScalar()),
+ fRadius2(buffer.readScalar()) {
+ this->init();
+};
+
+void SkTwoPointConicalGradient::flatten(
+ SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writePoint(fCenter1);
+ buffer.writePoint(fCenter2);
+ buffer.writeScalar(fRadius1);
+ buffer.writeScalar(fRadius2);
+}
+
+/////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+
+#include "GrTBackendEffectFactory.h"
+
+// For brevity
+typedef GrGLUniformManager::UniformHandle UniformHandle;
+static const UniformHandle kInvalidUniformHandle = GrGLUniformManager::kInvalidUniformHandle;
+
+class GrGLConical2Gradient : public GrGLGradientEffect {
+public:
+
+ GrGLConical2Gradient(const GrBackendEffectFactory& factory, const GrDrawEffect&);
+ virtual ~GrGLConical2Gradient() { }
+
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+ static EffectKey GenKey(const GrDrawEffect&, const GrGLCaps& caps);
+
+protected:
+
+ UniformHandle fVSParamUni;
+ UniformHandle fFSParamUni;
+
+ const char* fVSVaryingName;
+ const char* fFSVaryingName;
+
+ bool fIsDegenerate;
+
+ // @{
+ /// Values last uploaded as uniforms
+
+ SkScalar fCachedCenter;
+ SkScalar fCachedRadius;
+ SkScalar fCachedDiffRadius;
+
+ // @}
+
+private:
+
+ typedef GrGLGradientEffect INHERITED;
+
+};
+
+/////////////////////////////////////////////////////////////////////
+
+class GrConical2Gradient : public GrGradientEffect {
+public:
+
+ static GrEffectRef* Create(GrContext* ctx,
+ const SkTwoPointConicalGradient& shader,
+ const SkMatrix& matrix,
+ SkShader::TileMode tm) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrConical2Gradient, (ctx, shader, matrix, tm)));
+ return CreateEffectRef(effect);
+ }
+
+ virtual ~GrConical2Gradient() { }
+
+ static const char* Name() { return "Two-Point Conical Gradient"; }
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<GrConical2Gradient>::getInstance();
+ }
+
+ // The radial gradient parameters can collapse to a linear (instead of quadratic) equation.
+ bool isDegenerate() const { return SkScalarAbs(fDiffRadius) == SkScalarAbs(fCenterX1); }
+ SkScalar center() const { return fCenterX1; }
+ SkScalar diffRadius() const { return fDiffRadius; }
+ SkScalar radius() const { return fRadius0; }
+
+ typedef GrGLConical2Gradient GLEffect;
+
+private:
+ virtual bool onIsEqual(const GrEffect& sBase) const SK_OVERRIDE {
+ const GrConical2Gradient& s = CastEffect<GrConical2Gradient>(sBase);
+ return (INHERITED::onIsEqual(sBase) &&
+ this->fCenterX1 == s.fCenterX1 &&
+ this->fRadius0 == s.fRadius0 &&
+ this->fDiffRadius == s.fDiffRadius);
+ }
+
+ GrConical2Gradient(GrContext* ctx,
+ const SkTwoPointConicalGradient& shader,
+ const SkMatrix& matrix,
+ SkShader::TileMode tm)
+ : INHERITED(ctx, shader, matrix, tm)
+ , fCenterX1(shader.getCenterX1())
+ , fRadius0(shader.getStartRadius())
+ , fDiffRadius(shader.getDiffRadius()) { }
+
+ GR_DECLARE_EFFECT_TEST;
+
+ // @{
+ // Cache of values - these can change arbitrarily, EXCEPT
+ // we shouldn't change between degenerate and non-degenerate?!
+
+ SkScalar fCenterX1;
+ SkScalar fRadius0;
+ SkScalar fDiffRadius;
+
+ // @}
+
+ typedef GrGradientEffect INHERITED;
+};
+
+GR_DEFINE_EFFECT_TEST(GrConical2Gradient);
+
+GrEffectRef* GrConical2Gradient::TestCreate(SkMWCRandom* random,
+ GrContext* context,
+ const GrDrawTargetCaps&,
+ GrTexture**) {
+ SkPoint center1 = {random->nextUScalar1(), random->nextUScalar1()};
+ SkScalar radius1 = random->nextUScalar1();
+ SkPoint center2;
+ SkScalar radius2;
+ do {
+ center2.set(random->nextUScalar1(), random->nextUScalar1());
+ radius2 = random->nextUScalar1 ();
+ // If the circles are identical the factory will give us an empty shader.
+ } while (radius1 == radius2 && center1 == center2);
+
+ SkColor colors[kMaxRandomGradientColors];
+ SkScalar stopsArray[kMaxRandomGradientColors];
+ SkScalar* stops = stopsArray;
+ SkShader::TileMode tm;
+ int colorCount = RandomGradientParams(random, colors, &stops, &tm);
+ SkAutoTUnref<SkShader> shader(SkGradientShader::CreateTwoPointConical(center1, radius1,
+ center2, radius2,
+ colors, stops, colorCount,
+ tm));
+ SkPaint paint;
+ return shader->asNewEffect(context, paint);
+}
+
+
+/////////////////////////////////////////////////////////////////////
+
+GrGLConical2Gradient::GrGLConical2Gradient(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect)
+ : INHERITED(factory)
+ , fVSParamUni(kInvalidUniformHandle)
+ , fFSParamUni(kInvalidUniformHandle)
+ , fVSVaryingName(NULL)
+ , fFSVaryingName(NULL)
+ , fCachedCenter(SK_ScalarMax)
+ , fCachedRadius(-SK_ScalarMax)
+ , fCachedDiffRadius(-SK_ScalarMax) {
+
+ const GrConical2Gradient& data = drawEffect.castEffect<GrConical2Gradient>();
+ fIsDegenerate = data.isDegenerate();
+}
+
+void GrGLConical2Gradient::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect&,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+ const char* fsCoords;
+ const char* vsCoordsVarying;
+ GrSLType coordsVaryingType;
+ this->setupMatrix(builder, key, &fsCoords, &vsCoordsVarying, &coordsVaryingType);
+
+ this->emitYCoordUniform(builder);
+ // 2 copies of uniform array, 1 for each of vertex & fragment shader,
+ // to work around Xoom bug. Doesn't seem to cause performance decrease
+ // in test apps, but need to keep an eye on it.
+ fVSParamUni = builder->addUniformArray(GrGLShaderBuilder::kVertex_ShaderType,
+ kFloat_GrSLType, "Conical2VSParams", 6);
+ fFSParamUni = builder->addUniformArray(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "Conical2FSParams", 6);
+
+ // For radial gradients without perspective we can pass the linear
+ // part of the quadratic as a varying.
+ if (kVec2f_GrSLType == coordsVaryingType) {
+ builder->addVarying(kFloat_GrSLType, "Conical2BCoeff",
+ &fVSVaryingName, &fFSVaryingName);
+ }
+
+ // VS
+ {
+ SkString p2; // distance between centers
+ SkString p3; // start radius
+ SkString p5; // difference in radii (r1 - r0)
+ builder->getUniformVariable(fVSParamUni).appendArrayAccess(2, &p2);
+ builder->getUniformVariable(fVSParamUni).appendArrayAccess(3, &p3);
+ builder->getUniformVariable(fVSParamUni).appendArrayAccess(5, &p5);
+
+ // For radial gradients without perspective we can pass the linear
+ // part of the quadratic as a varying.
+ if (kVec2f_GrSLType == coordsVaryingType) {
+ // r2Var = -2 * (r2Parm[2] * varCoord.x - r2Param[3] * r2Param[5])
+ builder->vsCodeAppendf("\t%s = -2.0 * (%s * %s.x + %s * %s);\n",
+ fVSVaryingName, p2.c_str(),
+ vsCoordsVarying, p3.c_str(), p5.c_str());
+ }
+ }
+
+ // FS
+ {
+
+ SkString cName("c");
+ SkString ac4Name("ac4");
+ SkString dName("d");
+ SkString qName("q");
+ SkString r0Name("r0");
+ SkString r1Name("r1");
+ SkString tName("t");
+ SkString p0; // 4a
+ SkString p1; // 1/a
+ SkString p2; // distance between centers
+ SkString p3; // start radius
+ SkString p4; // start radius squared
+ SkString p5; // difference in radii (r1 - r0)
+
+ builder->getUniformVariable(fFSParamUni).appendArrayAccess(0, &p0);
+ builder->getUniformVariable(fFSParamUni).appendArrayAccess(1, &p1);
+ builder->getUniformVariable(fFSParamUni).appendArrayAccess(2, &p2);
+ builder->getUniformVariable(fFSParamUni).appendArrayAccess(3, &p3);
+ builder->getUniformVariable(fFSParamUni).appendArrayAccess(4, &p4);
+ builder->getUniformVariable(fFSParamUni).appendArrayAccess(5, &p5);
+
+ // If we we're able to interpolate the linear component,
+ // bVar is the varying; otherwise compute it
+ SkString bVar;
+ if (kVec2f_GrSLType == coordsVaryingType) {
+ bVar = fFSVaryingName;
+ } else {
+ bVar = "b";
+ builder->fsCodeAppendf("\tfloat %s = -2.0 * (%s * %s.x + %s * %s);\n",
+ bVar.c_str(), p2.c_str(), fsCoords,
+ p3.c_str(), p5.c_str());
+ }
+
+ // output will default to transparent black (we simply won't write anything
+ // else to it if invalid, instead of discarding or returning prematurely)
+ builder->fsCodeAppendf("\t%s = vec4(0.0,0.0,0.0,0.0);\n", outputColor);
+
+ // c = (x^2)+(y^2) - params[4]
+ builder->fsCodeAppendf("\tfloat %s = dot(%s, %s) - %s;\n", cName.c_str(),
+ fsCoords, fsCoords,
+ p4.c_str());
+
+ // Non-degenerate case (quadratic)
+ if (!fIsDegenerate) {
+
+ // ac4 = params[0] * c
+ builder->fsCodeAppendf("\tfloat %s = %s * %s;\n", ac4Name.c_str(), p0.c_str(),
+ cName.c_str());
+
+ // d = b^2 - ac4
+ builder->fsCodeAppendf("\tfloat %s = %s * %s - %s;\n", dName.c_str(),
+ bVar.c_str(), bVar.c_str(), ac4Name.c_str());
+
+ // only proceed if discriminant is >= 0
+ builder->fsCodeAppendf("\tif (%s >= 0.0) {\n", dName.c_str());
+
+ // intermediate value we'll use to compute the roots
+ // q = -0.5 * (b +/- sqrt(d))
+ builder->fsCodeAppendf("\t\tfloat %s = -0.5 * (%s + (%s < 0.0 ? -1.0 : 1.0)"
+ " * sqrt(%s));\n", qName.c_str(), bVar.c_str(),
+ bVar.c_str(), dName.c_str());
+
+ // compute both roots
+ // r0 = q * params[1]
+ builder->fsCodeAppendf("\t\tfloat %s = %s * %s;\n", r0Name.c_str(),
+ qName.c_str(), p1.c_str());
+ // r1 = c / q
+ builder->fsCodeAppendf("\t\tfloat %s = %s / %s;\n", r1Name.c_str(),
+ cName.c_str(), qName.c_str());
+
+ // Note: If there are two roots that both generate radius(t) > 0, the
+ // Canvas spec says to choose the larger t.
+
+ // so we'll look at the larger one first:
+ builder->fsCodeAppendf("\t\tfloat %s = max(%s, %s);\n", tName.c_str(),
+ r0Name.c_str(), r1Name.c_str());
+
+ // if r(t) > 0, then we're done; t will be our x coordinate
+ builder->fsCodeAppendf("\t\tif (%s * %s + %s > 0.0) {\n", tName.c_str(),
+ p5.c_str(), p3.c_str());
+
+ builder->fsCodeAppend("\t\t");
+ this->emitColorLookup(builder, tName.c_str(), outputColor, inputColor, samplers[0]);
+
+ // otherwise, if r(t) for the larger root was <= 0, try the other root
+ builder->fsCodeAppend("\t\t} else {\n");
+ builder->fsCodeAppendf("\t\t\t%s = min(%s, %s);\n", tName.c_str(),
+ r0Name.c_str(), r1Name.c_str());
+
+ // if r(t) > 0 for the smaller root, then t will be our x coordinate
+ builder->fsCodeAppendf("\t\t\tif (%s * %s + %s > 0.0) {\n",
+ tName.c_str(), p5.c_str(), p3.c_str());
+
+ builder->fsCodeAppend("\t\t\t");
+ this->emitColorLookup(builder, tName.c_str(), outputColor, inputColor, samplers[0]);
+
+ // end if (r(t) > 0) for smaller root
+ builder->fsCodeAppend("\t\t\t}\n");
+ // end if (r(t) > 0), else, for larger root
+ builder->fsCodeAppend("\t\t}\n");
+ // end if (discriminant >= 0)
+ builder->fsCodeAppend("\t}\n");
+ } else {
+
+ // linear case: t = -c/b
+ builder->fsCodeAppendf("\tfloat %s = -(%s / %s);\n", tName.c_str(),
+ cName.c_str(), bVar.c_str());
+
+ // if r(t) > 0, then t will be the x coordinate
+ builder->fsCodeAppendf("\tif (%s * %s + %s > 0.0) {\n", tName.c_str(),
+ p5.c_str(), p3.c_str());
+ builder->fsCodeAppend("\t");
+ this->emitColorLookup(builder, tName.c_str(), outputColor, inputColor, samplers[0]);
+ builder->fsCodeAppend("\t}\n");
+ }
+ }
+}
+
+void GrGLConical2Gradient::setData(const GrGLUniformManager& uman,
+ const GrDrawEffect& drawEffect) {
+ INHERITED::setData(uman, drawEffect);
+ const GrConical2Gradient& data = drawEffect.castEffect<GrConical2Gradient>();
+ GrAssert(data.isDegenerate() == fIsDegenerate);
+ SkScalar centerX1 = data.center();
+ SkScalar radius0 = data.radius();
+ SkScalar diffRadius = data.diffRadius();
+
+ if (fCachedCenter != centerX1 ||
+ fCachedRadius != radius0 ||
+ fCachedDiffRadius != diffRadius) {
+
+ SkScalar a = SkScalarMul(centerX1, centerX1) - diffRadius * diffRadius;
+
+ // When we're in the degenerate (linear) case, the second
+ // value will be INF but the program doesn't read it. (We
+ // use the same 6 uniforms even though we don't need them
+ // all in the linear case just to keep the code complexity
+ // down).
+ float values[6] = {
+ SkScalarToFloat(a * 4),
+ 1.f / (SkScalarToFloat(a)),
+ SkScalarToFloat(centerX1),
+ SkScalarToFloat(radius0),
+ SkScalarToFloat(SkScalarMul(radius0, radius0)),
+ SkScalarToFloat(diffRadius)
+ };
+
+ uman.set1fv(fVSParamUni, 0, 6, values);
+ uman.set1fv(fFSParamUni, 0, 6, values);
+ fCachedCenter = centerX1;
+ fCachedRadius = radius0;
+ fCachedDiffRadius = diffRadius;
+ }
+}
+
+GrGLEffect::EffectKey GrGLConical2Gradient::GenKey(const GrDrawEffect& drawEffect,
+ const GrGLCaps&) {
+ enum {
+ kIsDegenerate = 1 << kMatrixKeyBitCnt,
+ };
+
+ EffectKey key = GenMatrixKey(drawEffect);
+ if (drawEffect.castEffect<GrConical2Gradient>().isDegenerate()) {
+ key |= kIsDegenerate;
+ }
+ return key;
+}
+
+/////////////////////////////////////////////////////////////////////
+
+GrEffectRef* SkTwoPointConicalGradient::asNewEffect(GrContext* context, const SkPaint&) const {
+ SkASSERT(NULL != context);
+ SkASSERT(fPtsToUnit.isIdentity());
+ // invert the localM, translate to center1, rotate so center2 is on x axis.
+ SkMatrix matrix;
+ if (!this->getLocalMatrix().invert(&matrix)) {
+ return NULL;
+ }
+ matrix.postTranslate(-fCenter1.fX, -fCenter1.fY);
+
+ SkPoint diff = fCenter2 - fCenter1;
+ SkScalar diffLen = diff.length();
+ if (0 != diffLen) {
+ SkScalar invDiffLen = SkScalarInvert(diffLen);
+ SkMatrix rot;
+ rot.setSinCos(-SkScalarMul(invDiffLen, diff.fY),
+ SkScalarMul(invDiffLen, diff.fX));
+ matrix.postConcat(rot);
+ }
+
+ return GrConical2Gradient::Create(context, *this, matrix, fTileMode);
+}
+
+#else
+
+GrEffectRef* SkTwoPointConicalGradient::asNewEffect(GrContext*, const SkPaint&) const {
+ SkDEBUGFAIL("Should not call in GPU-less build");
+ return NULL;
+}
+
+#endif
+
+#ifdef SK_DEVELOPER
+void SkTwoPointConicalGradient::toString(SkString* str) const {
+ str->append("SkTwoPointConicalGradient: (");
+
+ str->append("center1: (");
+ str->appendScalar(fCenter1.fX);
+ str->append(", ");
+ str->appendScalar(fCenter1.fY);
+ str->append(") radius1: ");
+ str->appendScalar(fRadius1);
+ str->append(" ");
+
+ str->append("center2: (");
+ str->appendScalar(fCenter2.fX);
+ str->append(", ");
+ str->appendScalar(fCenter2.fY);
+ str->append(") radius2: ");
+ str->appendScalar(fRadius2);
+ str->append(" ");
+
+ this->INHERITED::toString(str);
+
+ str->append(")");
+}
+#endif
diff --git a/effects/gradients/SkTwoPointConicalGradient.h b/effects/gradients/SkTwoPointConicalGradient.h
new file mode 100644
index 00000000..1358f0b2
--- /dev/null
+++ b/effects/gradients/SkTwoPointConicalGradient.h
@@ -0,0 +1,84 @@
+
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+ #ifndef SkTwoPointConicalGradient_DEFINED
+ #define SkTwoPointConicalGradient_DEFINED
+
+#include "SkGradientShaderPriv.h"
+
+struct TwoPtRadial {
+ enum {
+ kDontDrawT = 0x80000000
+ };
+
+ float fCenterX, fCenterY;
+ float fDCenterX, fDCenterY;
+ float fRadius;
+ float fDRadius;
+ float fA;
+ float fRadius2;
+ float fRDR;
+
+ void init(const SkPoint& center0, SkScalar rad0,
+ const SkPoint& center1, SkScalar rad1);
+
+ // used by setup and nextT
+ float fRelX, fRelY, fIncX, fIncY;
+ float fB, fDB;
+
+ void setup(SkScalar fx, SkScalar fy, SkScalar dfx, SkScalar dfy);
+ SkFixed nextT();
+
+ static bool DontDrawT(SkFixed t) {
+ return kDontDrawT == (uint32_t)t;
+ }
+};
+
+
+class SkTwoPointConicalGradient : public SkGradientShaderBase {
+ TwoPtRadial fRec;
+ void init();
+
+public:
+ SkTwoPointConicalGradient(const SkPoint& start, SkScalar startRadius,
+ const SkPoint& end, SkScalar endRadius,
+ const Descriptor&);
+
+ virtual void shadeSpan(int x, int y, SkPMColor* dstCParam,
+ int count) SK_OVERRIDE;
+ virtual bool setContext(const SkBitmap& device,
+ const SkPaint& paint,
+ const SkMatrix& matrix) SK_OVERRIDE;
+
+ virtual BitmapType asABitmap(SkBitmap* bitmap,
+ SkMatrix* matrix,
+ TileMode* xy) const;
+ virtual SkShader::GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE;
+ virtual GrEffectRef* asNewEffect(GrContext* context, const SkPaint& paint) const SK_OVERRIDE;
+ virtual bool isOpaque() const SK_OVERRIDE;
+
+ SkScalar getCenterX1() const { return SkPoint::Distance(fCenter1, fCenter2); }
+ SkScalar getStartRadius() const { return fRadius1; }
+ SkScalar getDiffRadius() const { return fRadius2 - fRadius1; }
+
+ SK_DEVELOPER_TO_STRING()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkTwoPointConicalGradient)
+
+protected:
+ SkTwoPointConicalGradient(SkFlattenableReadBuffer& buffer);
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE;
+
+private:
+ typedef SkGradientShaderBase INHERITED;
+ const SkPoint fCenter1;
+ const SkPoint fCenter2;
+ const SkScalar fRadius1;
+ const SkScalar fRadius2;
+};
+
+#endif
diff --git a/effects/gradients/SkTwoPointRadialGradient.cpp b/effects/gradients/SkTwoPointRadialGradient.cpp
new file mode 100644
index 00000000..989c1395
--- /dev/null
+++ b/effects/gradients/SkTwoPointRadialGradient.cpp
@@ -0,0 +1,717 @@
+
+/*
+ * 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 "SkTwoPointRadialGradient.h"
+
+/* Two-point radial gradients are specified by two circles, each with a center
+ point and radius. The gradient can be considered to be a series of
+ concentric circles, with the color interpolated from the start circle
+ (at t=0) to the end circle (at t=1).
+
+ For each point (x, y) in the span, we want to find the
+ interpolated circle that intersects that point. The center
+ of the desired circle (Cx, Cy) falls at some distance t
+ along the line segment between the start point (Sx, Sy) and
+ end point (Ex, Ey):
+
+ Cx = (1 - t) * Sx + t * Ex (0 <= t <= 1)
+ Cy = (1 - t) * Sy + t * Ey
+
+ The radius of the desired circle (r) is also a linear interpolation t
+ between the start and end radii (Sr and Er):
+
+ r = (1 - t) * Sr + t * Er
+
+ But
+
+ (x - Cx)^2 + (y - Cy)^2 = r^2
+
+ so
+
+ (x - ((1 - t) * Sx + t * Ex))^2
+ + (y - ((1 - t) * Sy + t * Ey))^2
+ = ((1 - t) * Sr + t * Er)^2
+
+ Solving for t yields
+
+ [(Sx - Ex)^2 + (Sy - Ey)^2 - (Er - Sr)^2)] * t^2
+ + [2 * (Sx - Ex)(x - Sx) + 2 * (Sy - Ey)(y - Sy) - 2 * (Er - Sr) * Sr] * t
+ + [(x - Sx)^2 + (y - Sy)^2 - Sr^2] = 0
+
+ To simplify, let Dx = Sx - Ex, Dy = Sy - Ey, Dr = Er - Sr, dx = x - Sx, dy = y - Sy
+
+ [Dx^2 + Dy^2 - Dr^2)] * t^2
+ + 2 * [Dx * dx + Dy * dy - Dr * Sr] * t
+ + [dx^2 + dy^2 - Sr^2] = 0
+
+ A quadratic in t. The two roots of the quadratic reflect the two
+ possible circles on which the point may fall. Solving for t yields
+ the gradient value to use.
+
+ If a<0, the start circle is entirely contained in the
+ end circle, and one of the roots will be <0 or >1 (off the line
+ segment). If a>0, the start circle falls at least partially
+ outside the end circle (or vice versa), and the gradient
+ defines a "tube" where a point may be on one circle (on the
+ inside of the tube) or the other (outside of the tube). We choose
+ one arbitrarily.
+
+ In order to keep the math to within the limits of fixed point,
+ we divide the entire quadratic by Dr^2, and replace
+ (x - Sx)/Dr with x' and (y - Sy)/Dr with y', giving
+
+ [Dx^2 / Dr^2 + Dy^2 / Dr^2 - 1)] * t^2
+ + 2 * [x' * Dx / Dr + y' * Dy / Dr - Sr / Dr] * t
+ + [x'^2 + y'^2 - Sr^2/Dr^2] = 0
+
+ (x' and y' are computed by appending the subtract and scale to the
+ fDstToIndex matrix in the constructor).
+
+ Since the 'A' component of the quadratic is independent of x' and y', it
+ is precomputed in the constructor. Since the 'B' component is linear in
+ x' and y', if x and y are linear in the span, 'B' can be computed
+ incrementally with a simple delta (db below). If it is not (e.g.,
+ a perspective projection), it must be computed in the loop.
+
+*/
+
+namespace {
+
+inline SkFixed two_point_radial(SkScalar b, SkScalar fx, SkScalar fy,
+ SkScalar sr2d2, SkScalar foura,
+ SkScalar oneOverTwoA, bool posRoot) {
+ SkScalar c = SkScalarSquare(fx) + SkScalarSquare(fy) - sr2d2;
+ if (0 == foura) {
+ return SkScalarToFixed(SkScalarDiv(-c, b));
+ }
+
+ SkScalar discrim = SkScalarSquare(b) - SkScalarMul(foura, c);
+ if (discrim < 0) {
+ discrim = -discrim;
+ }
+ SkScalar rootDiscrim = SkScalarSqrt(discrim);
+ SkScalar result;
+ if (posRoot) {
+ result = SkScalarMul(-b + rootDiscrim, oneOverTwoA);
+ } else {
+ result = SkScalarMul(-b - rootDiscrim, oneOverTwoA);
+ }
+ return SkScalarToFixed(result);
+}
+
+typedef void (* TwoPointRadialShadeProc)(SkScalar fx, SkScalar dx,
+ SkScalar fy, SkScalar dy,
+ SkScalar b, SkScalar db,
+ SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
+ SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
+ int count);
+
+void shadeSpan_twopoint_clamp(SkScalar fx, SkScalar dx,
+ SkScalar fy, SkScalar dy,
+ SkScalar b, SkScalar db,
+ SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
+ SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
+ int count) {
+ for (; count > 0; --count) {
+ SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
+ fOneOverTwoA, posRoot);
+ SkFixed index = SkClampMax(t, 0xFFFF);
+ SkASSERT(index <= 0xFFFF);
+ *dstC++ = cache[index >> SkGradientShaderBase::kCache32Shift];
+ fx += dx;
+ fy += dy;
+ b += db;
+ }
+}
+void shadeSpan_twopoint_mirror(SkScalar fx, SkScalar dx,
+ SkScalar fy, SkScalar dy,
+ SkScalar b, SkScalar db,
+ SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
+ SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
+ int count) {
+ for (; count > 0; --count) {
+ SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
+ fOneOverTwoA, posRoot);
+ SkFixed index = mirror_tileproc(t);
+ SkASSERT(index <= 0xFFFF);
+ *dstC++ = cache[index >> SkGradientShaderBase::kCache32Shift];
+ fx += dx;
+ fy += dy;
+ b += db;
+ }
+}
+
+void shadeSpan_twopoint_repeat(SkScalar fx, SkScalar dx,
+ SkScalar fy, SkScalar dy,
+ SkScalar b, SkScalar db,
+ SkScalar fSr2D2, SkScalar foura, SkScalar fOneOverTwoA, bool posRoot,
+ SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
+ int count) {
+ for (; count > 0; --count) {
+ SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
+ fOneOverTwoA, posRoot);
+ SkFixed index = repeat_tileproc(t);
+ SkASSERT(index <= 0xFFFF);
+ *dstC++ = cache[index >> SkGradientShaderBase::kCache32Shift];
+ fx += dx;
+ fy += dy;
+ b += db;
+ }
+}
+}
+
+/////////////////////////////////////////////////////////////////////
+
+SkTwoPointRadialGradient::SkTwoPointRadialGradient(
+ const SkPoint& start, SkScalar startRadius,
+ const SkPoint& end, SkScalar endRadius,
+ const Descriptor& desc)
+ : SkGradientShaderBase(desc),
+ fCenter1(start),
+ fCenter2(end),
+ fRadius1(startRadius),
+ fRadius2(endRadius) {
+ init();
+}
+
+SkShader::BitmapType SkTwoPointRadialGradient::asABitmap(
+ SkBitmap* bitmap,
+ SkMatrix* matrix,
+ SkShader::TileMode* xy) const {
+ if (bitmap) {
+ this->getGradientTableBitmap(bitmap);
+ }
+ SkScalar diffL = 0; // just to avoid gcc warning
+ if (matrix) {
+ diffL = SkScalarSqrt(SkScalarSquare(fDiff.fX) +
+ SkScalarSquare(fDiff.fY));
+ }
+ if (matrix) {
+ if (diffL) {
+ SkScalar invDiffL = SkScalarInvert(diffL);
+ matrix->setSinCos(-SkScalarMul(invDiffL, fDiff.fY),
+ SkScalarMul(invDiffL, fDiff.fX));
+ } else {
+ matrix->reset();
+ }
+ matrix->preConcat(fPtsToUnit);
+ }
+ if (xy) {
+ xy[0] = fTileMode;
+ xy[1] = kClamp_TileMode;
+ }
+ return kTwoPointRadial_BitmapType;
+}
+
+SkShader::GradientType SkTwoPointRadialGradient::asAGradient(
+ SkShader::GradientInfo* info) const {
+ if (info) {
+ commonAsAGradient(info);
+ info->fPoint[0] = fCenter1;
+ info->fPoint[1] = fCenter2;
+ info->fRadius[0] = fRadius1;
+ info->fRadius[1] = fRadius2;
+ }
+ return kRadial2_GradientType;
+}
+
+void SkTwoPointRadialGradient::shadeSpan(int x, int y, SkPMColor* dstCParam,
+ int count) {
+ SkASSERT(count > 0);
+
+ SkPMColor* SK_RESTRICT dstC = dstCParam;
+
+ // Zero difference between radii: fill with transparent black.
+ if (fDiffRadius == 0) {
+ sk_bzero(dstC, count * sizeof(*dstC));
+ return;
+ }
+ SkMatrix::MapXYProc dstProc = fDstToIndexProc;
+ TileProc proc = fTileProc;
+ const SkPMColor* SK_RESTRICT cache = this->getCache32();
+
+ SkScalar foura = fA * 4;
+ bool posRoot = fDiffRadius < 0;
+ if (fDstToIndexClass != kPerspective_MatrixClass) {
+ SkPoint srcPt;
+ dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
+ SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+ SkScalar dx, fx = srcPt.fX;
+ SkScalar dy, fy = srcPt.fY;
+
+ if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
+ SkFixed fixedX, fixedY;
+ (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
+ dx = SkFixedToScalar(fixedX);
+ dy = SkFixedToScalar(fixedY);
+ } else {
+ SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
+ dx = fDstToIndex.getScaleX();
+ dy = fDstToIndex.getSkewY();
+ }
+ SkScalar b = (SkScalarMul(fDiff.fX, fx) +
+ SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
+ SkScalar db = (SkScalarMul(fDiff.fX, dx) +
+ SkScalarMul(fDiff.fY, dy)) * 2;
+
+ TwoPointRadialShadeProc shadeProc = shadeSpan_twopoint_repeat;
+ if (SkShader::kClamp_TileMode == fTileMode) {
+ shadeProc = shadeSpan_twopoint_clamp;
+ } else if (SkShader::kMirror_TileMode == fTileMode) {
+ shadeProc = shadeSpan_twopoint_mirror;
+ } else {
+ SkASSERT(SkShader::kRepeat_TileMode == fTileMode);
+ }
+ (*shadeProc)(fx, dx, fy, dy, b, db,
+ fSr2D2, foura, fOneOverTwoA, posRoot,
+ dstC, cache, count);
+ } else { // perspective case
+ SkScalar dstX = SkIntToScalar(x);
+ SkScalar dstY = SkIntToScalar(y);
+ for (; count > 0; --count) {
+ SkPoint srcPt;
+ dstProc(fDstToIndex, dstX, dstY, &srcPt);
+ SkScalar fx = srcPt.fX;
+ SkScalar fy = srcPt.fY;
+ SkScalar b = (SkScalarMul(fDiff.fX, fx) +
+ SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
+ SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura,
+ fOneOverTwoA, posRoot);
+ SkFixed index = proc(t);
+ SkASSERT(index <= 0xFFFF);
+ *dstC++ = cache[index >> SkGradientShaderBase::kCache32Shift];
+ dstX += SK_Scalar1;
+ }
+ }
+}
+
+bool SkTwoPointRadialGradient::setContext( const SkBitmap& device,
+ const SkPaint& paint,
+ const SkMatrix& matrix){
+ // For now, we might have divided by zero, so detect that
+ if (0 == fDiffRadius) {
+ return false;
+ }
+
+ if (!this->INHERITED::setContext(device, paint, matrix)) {
+ return false;
+ }
+
+ // we don't have a span16 proc
+ fFlags &= ~kHasSpan16_Flag;
+ return true;
+}
+
+#ifdef SK_DEVELOPER
+void SkTwoPointRadialGradient::toString(SkString* str) const {
+ str->append("SkTwoPointRadialGradient: (");
+
+ str->append("center1: (");
+ str->appendScalar(fCenter1.fX);
+ str->append(", ");
+ str->appendScalar(fCenter1.fY);
+ str->append(") radius1: ");
+ str->appendScalar(fRadius1);
+ str->append(" ");
+
+ str->append("center2: (");
+ str->appendScalar(fCenter2.fX);
+ str->append(", ");
+ str->appendScalar(fCenter2.fY);
+ str->append(") radius2: ");
+ str->appendScalar(fRadius2);
+ str->append(" ");
+
+ this->INHERITED::toString(str);
+
+ str->append(")");
+}
+#endif
+
+SkTwoPointRadialGradient::SkTwoPointRadialGradient(
+ SkFlattenableReadBuffer& buffer)
+ : INHERITED(buffer),
+ fCenter1(buffer.readPoint()),
+ fCenter2(buffer.readPoint()),
+ fRadius1(buffer.readScalar()),
+ fRadius2(buffer.readScalar()) {
+ init();
+};
+
+void SkTwoPointRadialGradient::flatten(
+ SkFlattenableWriteBuffer& buffer) const {
+ this->INHERITED::flatten(buffer);
+ buffer.writePoint(fCenter1);
+ buffer.writePoint(fCenter2);
+ buffer.writeScalar(fRadius1);
+ buffer.writeScalar(fRadius2);
+}
+
+void SkTwoPointRadialGradient::init() {
+ fDiff = fCenter1 - fCenter2;
+ fDiffRadius = fRadius2 - fRadius1;
+ // hack to avoid zero-divide for now
+ SkScalar inv = fDiffRadius ? SkScalarInvert(fDiffRadius) : 0;
+ fDiff.fX = SkScalarMul(fDiff.fX, inv);
+ fDiff.fY = SkScalarMul(fDiff.fY, inv);
+ fStartRadius = SkScalarMul(fRadius1, inv);
+ fSr2D2 = SkScalarSquare(fStartRadius);
+ fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
+ fOneOverTwoA = fA ? SkScalarInvert(fA * 2) : 0;
+
+ fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
+ fPtsToUnit.postScale(inv, inv);
+}
+
+/////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+
+#include "GrTBackendEffectFactory.h"
+
+// For brevity
+typedef GrGLUniformManager::UniformHandle UniformHandle;
+static const UniformHandle kInvalidUniformHandle = GrGLUniformManager::kInvalidUniformHandle;
+
+class GrGLRadial2Gradient : public GrGLGradientEffect {
+
+public:
+
+ GrGLRadial2Gradient(const GrBackendEffectFactory& factory, const GrDrawEffect&);
+ virtual ~GrGLRadial2Gradient() { }
+
+ virtual void emitCode(GrGLShaderBuilder*,
+ const GrDrawEffect&,
+ EffectKey,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray&) SK_OVERRIDE;
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+ static EffectKey GenKey(const GrDrawEffect&, const GrGLCaps& caps);
+
+protected:
+
+ UniformHandle fVSParamUni;
+ UniformHandle fFSParamUni;
+
+ const char* fVSVaryingName;
+ const char* fFSVaryingName;
+
+ bool fIsDegenerate;
+
+ // @{
+ /// Values last uploaded as uniforms
+
+ SkScalar fCachedCenter;
+ SkScalar fCachedRadius;
+ bool fCachedPosRoot;
+
+ // @}
+
+private:
+
+ typedef GrGLGradientEffect INHERITED;
+
+};
+
+/////////////////////////////////////////////////////////////////////
+
+class GrRadial2Gradient : public GrGradientEffect {
+public:
+ static GrEffectRef* Create(GrContext* ctx,
+ const SkTwoPointRadialGradient& shader,
+ const SkMatrix& matrix,
+ SkShader::TileMode tm) {
+ AutoEffectUnref effect(SkNEW_ARGS(GrRadial2Gradient, (ctx, shader, matrix, tm)));
+ return CreateEffectRef(effect);
+ }
+
+ virtual ~GrRadial2Gradient() { }
+
+ static const char* Name() { return "Two-Point Radial Gradient"; }
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+ return GrTBackendEffectFactory<GrRadial2Gradient>::getInstance();
+ }
+
+ // The radial gradient parameters can collapse to a linear (instead of quadratic) equation.
+ bool isDegenerate() const { return SK_Scalar1 == fCenterX1; }
+ SkScalar center() const { return fCenterX1; }
+ SkScalar radius() const { return fRadius0; }
+ bool isPosRoot() const { return SkToBool(fPosRoot); }
+
+ typedef GrGLRadial2Gradient GLEffect;
+
+private:
+ virtual bool onIsEqual(const GrEffect& sBase) const SK_OVERRIDE {
+ const GrRadial2Gradient& s = CastEffect<GrRadial2Gradient>(sBase);
+ return (INHERITED::onIsEqual(sBase) &&
+ this->fCenterX1 == s.fCenterX1 &&
+ this->fRadius0 == s.fRadius0 &&
+ this->fPosRoot == s.fPosRoot);
+ }
+
+ GrRadial2Gradient(GrContext* ctx,
+ const SkTwoPointRadialGradient& shader,
+ const SkMatrix& matrix,
+ SkShader::TileMode tm)
+ : INHERITED(ctx, shader, matrix, tm)
+ , fCenterX1(shader.getCenterX1())
+ , fRadius0(shader.getStartRadius())
+ , fPosRoot(shader.getDiffRadius() < 0) { }
+
+ GR_DECLARE_EFFECT_TEST;
+
+ // @{
+ // Cache of values - these can change arbitrarily, EXCEPT
+ // we shouldn't change between degenerate and non-degenerate?!
+
+ SkScalar fCenterX1;
+ SkScalar fRadius0;
+ SkBool8 fPosRoot;
+
+ // @}
+
+ typedef GrGradientEffect INHERITED;
+};
+
+/////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_EFFECT_TEST(GrRadial2Gradient);
+
+GrEffectRef* GrRadial2Gradient::TestCreate(SkMWCRandom* random,
+ GrContext* context,
+ const GrDrawTargetCaps&,
+ GrTexture**) {
+ SkPoint center1 = {random->nextUScalar1(), random->nextUScalar1()};
+ SkScalar radius1 = random->nextUScalar1();
+ SkPoint center2;
+ SkScalar radius2;
+ do {
+ center2.set(random->nextUScalar1(), random->nextUScalar1());
+ radius2 = random->nextUScalar1 ();
+ // There is a bug in two point radial gradients with identical radii
+ } while (radius1 == radius2);
+
+ SkColor colors[kMaxRandomGradientColors];
+ SkScalar stopsArray[kMaxRandomGradientColors];
+ SkScalar* stops = stopsArray;
+ SkShader::TileMode tm;
+ int colorCount = RandomGradientParams(random, colors, &stops, &tm);
+ SkAutoTUnref<SkShader> shader(SkGradientShader::CreateTwoPointRadial(center1, radius1,
+ center2, radius2,
+ colors, stops, colorCount,
+ tm));
+ SkPaint paint;
+ return shader->asNewEffect(context, paint);
+}
+
+/////////////////////////////////////////////////////////////////////
+
+GrGLRadial2Gradient::GrGLRadial2Gradient(const GrBackendEffectFactory& factory,
+ const GrDrawEffect& drawEffect)
+ : INHERITED(factory)
+ , fVSParamUni(kInvalidUniformHandle)
+ , fFSParamUni(kInvalidUniformHandle)
+ , fVSVaryingName(NULL)
+ , fFSVaryingName(NULL)
+ , fCachedCenter(SK_ScalarMax)
+ , fCachedRadius(-SK_ScalarMax)
+ , fCachedPosRoot(0) {
+
+ const GrRadial2Gradient& data = drawEffect.castEffect<GrRadial2Gradient>();
+ fIsDegenerate = data.isDegenerate();
+}
+
+void GrGLRadial2Gradient::emitCode(GrGLShaderBuilder* builder,
+ const GrDrawEffect& drawEffect,
+ EffectKey key,
+ const char* outputColor,
+ const char* inputColor,
+ const TextureSamplerArray& samplers) {
+
+ this->emitYCoordUniform(builder);
+ const char* fsCoords;
+ const char* vsCoordsVarying;
+ GrSLType coordsVaryingType;
+ this->setupMatrix(builder, key, &fsCoords, &vsCoordsVarying, &coordsVaryingType);
+
+ // 2 copies of uniform array, 1 for each of vertex & fragment shader,
+ // to work around Xoom bug. Doesn't seem to cause performance decrease
+ // in test apps, but need to keep an eye on it.
+ fVSParamUni = builder->addUniformArray(GrGLShaderBuilder::kVertex_ShaderType,
+ kFloat_GrSLType, "Radial2VSParams", 6);
+ fFSParamUni = builder->addUniformArray(GrGLShaderBuilder::kFragment_ShaderType,
+ kFloat_GrSLType, "Radial2FSParams", 6);
+
+ // For radial gradients without perspective we can pass the linear
+ // part of the quadratic as a varying.
+ if (kVec2f_GrSLType == coordsVaryingType) {
+ builder->addVarying(kFloat_GrSLType, "Radial2BCoeff", &fVSVaryingName, &fFSVaryingName);
+ }
+
+ // VS
+ {
+ SkString p2;
+ SkString p3;
+ builder->getUniformVariable(fVSParamUni).appendArrayAccess(2, &p2);
+ builder->getUniformVariable(fVSParamUni).appendArrayAccess(3, &p3);
+
+ // For radial gradients without perspective we can pass the linear
+ // part of the quadratic as a varying.
+ if (kVec2f_GrSLType == coordsVaryingType) {
+ // r2Var = 2 * (r2Parm[2] * varCoord.x - r2Param[3])
+ builder->vsCodeAppendf("\t%s = 2.0 *(%s * %s.x - %s);\n",
+ fVSVaryingName, p2.c_str(),
+ vsCoordsVarying, p3.c_str());
+ }
+ }
+
+ // FS
+ {
+ SkString cName("c");
+ SkString ac4Name("ac4");
+ SkString rootName("root");
+ SkString t;
+ SkString p0;
+ SkString p1;
+ SkString p2;
+ SkString p3;
+ SkString p4;
+ SkString p5;
+ builder->getUniformVariable(fFSParamUni).appendArrayAccess(0, &p0);
+ builder->getUniformVariable(fFSParamUni).appendArrayAccess(1, &p1);
+ builder->getUniformVariable(fFSParamUni).appendArrayAccess(2, &p2);
+ builder->getUniformVariable(fFSParamUni).appendArrayAccess(3, &p3);
+ builder->getUniformVariable(fFSParamUni).appendArrayAccess(4, &p4);
+ builder->getUniformVariable(fFSParamUni).appendArrayAccess(5, &p5);
+
+ // If we we're able to interpolate the linear component,
+ // bVar is the varying; otherwise compute it
+ SkString bVar;
+ if (kVec2f_GrSLType == coordsVaryingType) {
+ bVar = fFSVaryingName;
+ } else {
+ bVar = "b";
+ builder->fsCodeAppendf("\tfloat %s = 2.0 * (%s * %s.x - %s);\n",
+ bVar.c_str(), p2.c_str(), fsCoords, p3.c_str());
+ }
+
+ // c = (x^2)+(y^2) - params[4]
+ builder->fsCodeAppendf("\tfloat %s = dot(%s, %s) - %s;\n",
+ cName.c_str(),
+ fsCoords,
+ fsCoords,
+ p4.c_str());
+
+ // If we aren't degenerate, emit some extra code, and accept a slightly
+ // more complex coord.
+ if (!fIsDegenerate) {
+
+ // ac4 = 4.0 * params[0] * c
+ builder->fsCodeAppendf("\tfloat %s = %s * 4.0 * %s;\n",
+ ac4Name.c_str(), p0.c_str(),
+ cName.c_str());
+
+ // root = sqrt(b^2-4ac)
+ // (abs to avoid exception due to fp precision)
+ builder->fsCodeAppendf("\tfloat %s = sqrt(abs(%s*%s - %s));\n",
+ rootName.c_str(), bVar.c_str(), bVar.c_str(),
+ ac4Name.c_str());
+
+ // t is: (-b + params[5] * sqrt(b^2-4ac)) * params[1]
+ t.printf("(-%s + %s * %s) * %s", bVar.c_str(), p5.c_str(),
+ rootName.c_str(), p1.c_str());
+ } else {
+ // t is: -c/b
+ t.printf("-%s / %s", cName.c_str(), bVar.c_str());
+ }
+
+ this->emitColorLookup(builder, t.c_str(), outputColor, inputColor, samplers[0]);
+ }
+}
+
+void GrGLRadial2Gradient::setData(const GrGLUniformManager& uman,
+ const GrDrawEffect& drawEffect) {
+ INHERITED::setData(uman, drawEffect);
+ const GrRadial2Gradient& data = drawEffect.castEffect<GrRadial2Gradient>();
+ GrAssert(data.isDegenerate() == fIsDegenerate);
+ SkScalar centerX1 = data.center();
+ SkScalar radius0 = data.radius();
+ if (fCachedCenter != centerX1 ||
+ fCachedRadius != radius0 ||
+ fCachedPosRoot != data.isPosRoot()) {
+
+ SkScalar a = SkScalarMul(centerX1, centerX1) - SK_Scalar1;
+
+ // When we're in the degenerate (linear) case, the second
+ // value will be INF but the program doesn't read it. (We
+ // use the same 6 uniforms even though we don't need them
+ // all in the linear case just to keep the code complexity
+ // down).
+ float values[6] = {
+ SkScalarToFloat(a),
+ 1 / (2.f * SkScalarToFloat(a)),
+ SkScalarToFloat(centerX1),
+ SkScalarToFloat(radius0),
+ SkScalarToFloat(SkScalarMul(radius0, radius0)),
+ data.isPosRoot() ? 1.f : -1.f
+ };
+
+ uman.set1fv(fVSParamUni, 0, 6, values);
+ uman.set1fv(fFSParamUni, 0, 6, values);
+ fCachedCenter = centerX1;
+ fCachedRadius = radius0;
+ fCachedPosRoot = data.isPosRoot();
+ }
+}
+
+GrGLEffect::EffectKey GrGLRadial2Gradient::GenKey(const GrDrawEffect& drawEffect,
+ const GrGLCaps&) {
+ enum {
+ kIsDegenerate = 1 << kMatrixKeyBitCnt,
+ };
+
+ EffectKey key = GenMatrixKey(drawEffect);
+ if (drawEffect.castEffect<GrRadial2Gradient>().isDegenerate()) {
+ key |= kIsDegenerate;
+ }
+ return key;
+}
+
+/////////////////////////////////////////////////////////////////////
+
+GrEffectRef* SkTwoPointRadialGradient::asNewEffect(GrContext* context, const SkPaint&) const {
+ SkASSERT(NULL != context);
+ // invert the localM, translate to center1 (fPtsToUni), rotate so center2 is on x axis.
+ SkMatrix matrix;
+ if (!this->getLocalMatrix().invert(&matrix)) {
+ return NULL;
+ }
+ matrix.postConcat(fPtsToUnit);
+
+ SkScalar diffLen = fDiff.length();
+ if (0 != diffLen) {
+ SkScalar invDiffLen = SkScalarInvert(diffLen);
+ SkMatrix rot;
+ rot.setSinCos(-SkScalarMul(invDiffLen, fDiff.fY),
+ SkScalarMul(invDiffLen, fDiff.fX));
+ matrix.postConcat(rot);
+ }
+
+ return GrRadial2Gradient::Create(context, *this, matrix, fTileMode);
+}
+
+#else
+
+GrEffectRef* SkTwoPointRadialGradient::asNewEffect(GrContext*, const SkPaint&) const {
+ SkDEBUGFAIL("Should not call in GPU-less build");
+ return NULL;
+}
+
+#endif
diff --git a/effects/gradients/SkTwoPointRadialGradient.h b/effects/gradients/SkTwoPointRadialGradient.h
new file mode 100644
index 00000000..444c23dd
--- /dev/null
+++ b/effects/gradients/SkTwoPointRadialGradient.h
@@ -0,0 +1,55 @@
+
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+ #ifndef SkTwoPointRadialGradient_DEFINED
+ #define SkTwoPointRadialGradient_DEFINED
+
+ #include "SkGradientShaderPriv.h"
+
+class SkTwoPointRadialGradient : public SkGradientShaderBase {
+public:
+ SkTwoPointRadialGradient(const SkPoint& start, SkScalar startRadius,
+ const SkPoint& end, SkScalar endRadius,
+ const Descriptor&);
+
+ virtual BitmapType asABitmap(SkBitmap* bitmap,
+ SkMatrix* matrix,
+ TileMode* xy) const SK_OVERRIDE;
+ virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE;
+ virtual GrEffectRef* asNewEffect(GrContext* context, const SkPaint&) const SK_OVERRIDE;
+
+ virtual void shadeSpan(int x, int y, SkPMColor* dstCParam,
+ int count) SK_OVERRIDE;
+ virtual bool setContext(const SkBitmap& device,
+ const SkPaint& paint,
+ const SkMatrix& matrix) SK_OVERRIDE;
+
+ SkScalar getCenterX1() const { return fDiff.length(); }
+ SkScalar getStartRadius() const { return fStartRadius; }
+ SkScalar getDiffRadius() const { return fDiffRadius; }
+
+ SK_DEVELOPER_TO_STRING()
+ SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkTwoPointRadialGradient)
+
+protected:
+ SkTwoPointRadialGradient(SkFlattenableReadBuffer& buffer);
+ virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE;
+
+private:
+ typedef SkGradientShaderBase INHERITED;
+ const SkPoint fCenter1;
+ const SkPoint fCenter2;
+ const SkScalar fRadius1;
+ const SkScalar fRadius2;
+ SkPoint fDiff;
+ SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA;
+
+ void init();
+};
+
+#endif