diff options
Diffstat (limited to 'effects')
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, ¶ms); + 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 |