/* * 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 "SkGradientShader.h" #include "SkClampRange.h" #include "SkColorPriv.h" #include "SkMallocPixelRef.h" #include "SkUnitMapper.h" #include "SkUtils.h" #include "SkTemplates.h" #include "SkBitmapCache.h" #ifndef SK_DISABLE_DITHER_32BIT_GRADIENT #define USE_DITHER_32BIT_GRADIENT #endif static 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; } } } } /////////////////////////////////////////////////////////////////////////////// // Can't use a two-argument function with side effects like this in a // constructor's initializer's argument list because the order of // evaluations in that context is undefined (and backwards on linux/gcc). static SkPoint unflatten_point(SkReader32& buffer) { SkPoint retval; retval.fX = buffer.readScalar(); retval.fY = buffer.readScalar(); return retval; } // Clamp static SkFixed clamp_tileproc(SkFixed x) { return SkClampMax(x, 0xFFFF); } // Repeat static SkFixed repeat_tileproc(SkFixed x) { return x & 0xFFFF; } 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; } // 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; } static inline int mirror_bits(int x, const int bits) { #ifdef SK_CPU_HAS_CONDITIONAL_INSTR if (x & (1 << bits)) x = ~x; return x & ((1 << bits) - 1); #else int s = x << (31 - bits) >> 31; return (x ^ s) & ((1 << bits) - 1); #endif } static inline int mirror_8bits(int x) { #ifdef SK_CPU_HAS_CONDITIONAL_INSTR if (x & 256) { x = ~x; } return x & 255; #else int s = x << 23 >> 31; return (x ^ s) & 0xFF; #endif } #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 Gradient_Shader : public SkShader { public: Gradient_Shader(const SkColor colors[], const SkScalar pos[], int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper); virtual ~Gradient_Shader(); // overrides 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; enum { /// Seems like enough for visual accuracy. TODO: if pos[] deserves /// it, use a larger cache. kCache16Bits = 8, kGradient16Length = (1 << kCache16Bits), /// Each cache gets 1 extra entry at the end so we don't have to /// test for end-of-cache in lerps. This is also the value used /// to stride *writes* into the dither cache; it must not be zero. /// Total space for a cache is 2x kCache16Count entries: one /// regular cache, one for dithering. kCache16Count = kGradient16Length + 1, kCache16Shift = 16 - kCache16Bits, kSqrt16Shift = 8 - kCache16Bits, /// Seems like enough for visual accuracy. TODO: if pos[] deserves /// it, use a larger cache. kCache32Bits = 8, kGradient32Length = (1 << kCache32Bits), /// Each cache gets 1 extra entry at the end so we don't have to /// test for end-of-cache in lerps. This is also the value used /// to stride *writes* into the dither cache; it must not be zero. /// Total space for a cache is 2x kCache32Count entries: one /// regular cache, one for dithering. kCache32Count = kGradient32Length + 1, kCache32Shift = 16 - kCache32Bits, kSqrt32Shift = 8 - kCache32Bits, /// This value is used to *read* the dither cache; it may be 0 /// if dithering is disabled. #ifdef USE_DITHER_32BIT_GRADIENT kDitherStride32 = kCache32Count, #else kDitherStride32 = 0, #endif kDitherStride16 = kCache16Count, kLerpRemainderMask32 = (1 << (16 - kCache32Bits)) - 1 }; protected: Gradient_Shader(SkFlattenableReadBuffer& ); SkUnitMapper* fMapper; SkMatrix fPtsToUnit; // set by subclass SkMatrix fDstToIndex; SkMatrix::MapXYProc fDstToIndexProc; TileMode fTileMode; TileProc fTileProc; int fColorCount; uint8_t fDstToIndexClass; uint8_t fFlags; struct Rec { SkFixed fPos; // 0...1 uint32_t fScale; // (1 << 24) / range }; Rec* fRecs; virtual void flatten(SkFlattenableWriteBuffer& ); const uint16_t* getCache16() const; const SkPMColor* getCache32() const; void commonAsABitmap(SkBitmap*) 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); void setCacheAlpha(U8CPU alpha) const; void initCommon(); typedef SkShader INHERITED; }; static inline unsigned scalarToU16(SkScalar x) { SkASSERT(x >= 0 && x <= SK_Scalar1); #ifdef SK_SCALAR_IS_FLOAT return (unsigned)(x * 0xFFFF); #else return x - (x >> 16); // probably should be x - (x > 0x7FFF) but that is slower #endif } Gradient_Shader::Gradient_Shader(const SkColor colors[], const SkScalar pos[], int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper) { SkASSERT(colorCount > 1); fCacheAlpha = 256; // init to a value that paint.getAlpha() can't return fMapper = mapper; SkSafeRef(mapper); SkASSERT((unsigned)mode < SkShader::kTileModeCount); SkASSERT(SkShader::kTileModeCount == SK_ARRAY_COUNT(gTileProcs)); fTileMode = mode; fTileProc = gTileProcs[mode]; 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 = colorCount; // check if we need to add in dummy start and/or end position/colors bool dummyFirst = false; bool dummyLast = false; if (pos) { dummyFirst = pos[0] != 0; dummyLast = pos[colorCount - 1] != SK_Scalar1; fColorCount += dummyFirst + dummyLast; } if (fColorCount > kColorStorageCount) { size_t size = sizeof(SkColor) + sizeof(Rec); fOrigColors = reinterpret_cast( sk_malloc_throw(size * fColorCount)); } else { fOrigColors = fStorage; } // Now copy over the colors, adding the dummies as needed { SkColor* origColors = fOrigColors; if (dummyFirst) { *origColors++ = colors[0]; } memcpy(origColors, colors, colorCount * sizeof(SkColor)); if (dummyLast) { origColors += colorCount; *origColors = colors[colorCount - 1]; } } fRecs = (Rec*)(fOrigColors + fColorCount); if (fColorCount > 2) { Rec* recs = fRecs; recs->fPos = 0; // recs->fScale = 0; // unused; recs += 1; if (pos) { /* 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 = colorCount + dummyLast; for (int i = startIndex; i < count; i++) { // force the last value to be 1.0 SkFixed curr; if (i == colorCount) { // we're really at the dummyLast curr = SK_Fixed1; } else { curr = SkScalarToFixed(pos[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 / (colorCount - 1); SkFixed p = dp; SkFixed scale = (colorCount - 1) << 8; // (1 << 24) / dp for (int i = 1; i < colorCount; i++) { recs->fPos = p; recs->fScale = scale; recs += 1; p += dp; } } } this->initCommon(); } Gradient_Shader::Gradient_Shader(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) { fCacheAlpha = 256; fMapper = static_cast(buffer.readFlattenable()); fCache16 = fCache16Storage = NULL; fCache32 = NULL; fCache32PixelRef = NULL; int colorCount = fColorCount = buffer.readU32(); if (colorCount > kColorStorageCount) { size_t size = sizeof(SkColor) + sizeof(SkPMColor) + sizeof(Rec); fOrigColors = (SkColor*)sk_malloc_throw(size * colorCount); } else { fOrigColors = fStorage; } buffer.read(fOrigColors, colorCount * sizeof(SkColor)); fTileMode = (TileMode)buffer.readU8(); 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.readS32(); recs[i].fScale = buffer.readU32(); } } SkReadMatrix(&buffer, &fPtsToUnit); this->initCommon(); } Gradient_Shader::~Gradient_Shader() { if (fCache16Storage) { sk_free(fCache16Storage); } SkSafeUnref(fCache32PixelRef); if (fOrigColors != fStorage) { sk_free(fOrigColors); } SkSafeUnref(fMapper); } void Gradient_Shader::initCommon() { fFlags = 0; unsigned colorAlpha = 0xFF; for (int i = 0; i < fColorCount; i++) { colorAlpha &= SkColorGetA(fOrigColors[i]); } fColorsAreOpaque = colorAlpha == 0xFF; } void Gradient_Shader::flatten(SkFlattenableWriteBuffer& buffer) { this->INHERITED::flatten(buffer); buffer.writeFlattenable(fMapper); buffer.write32(fColorCount); buffer.writeMul4(fOrigColors, fColorCount * sizeof(SkColor)); buffer.write8(fTileMode); if (fColorCount > 2) { Rec* recs = fRecs; for (int i = 1; i < fColorCount; i++) { buffer.write32(recs[i].fPos); buffer.write32(recs[i].fScale); } } SkWriteMatrix(&buffer, fPtsToUnit); } bool Gradient_Shader::isOpaque() const { return fColorsAreOpaque; } bool Gradient_Shader::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)) { 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 Gradient_Shader::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(); } } } static inline int blend8(int a, int b, int scale) { SkASSERT(a == SkToU8(a)); SkASSERT(b == SkToU8(b)); SkASSERT(scale >= 0 && scale <= 256); return a + ((b - a) * scale >> 8); } static inline uint32_t dot8_blend_packed32(uint32_t s0, uint32_t s1, int blend) { #if 0 int a = blend8(SkGetPackedA32(s0), SkGetPackedA32(s1), blend); int r = blend8(SkGetPackedR32(s0), SkGetPackedR32(s1), blend); int g = blend8(SkGetPackedG32(s0), SkGetPackedG32(s1), blend); int b = blend8(SkGetPackedB32(s0), SkGetPackedB32(s1), blend); return SkPackARGB32(a, r, g, b); #else int otherBlend = 256 - blend; #if 0 U32 t0 = (((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF; U32 t1 = (((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00; SkASSERT((t0 & t1) == 0); return t0 | t1; #else return ((((s0 & 0xFF00FF) * blend + (s1 & 0xFF00FF) * otherBlend) >> 8) & 0xFF00FF) | ((((s0 >> 8) & 0xFF00FF) * blend + ((s1 >> 8) & 0xFF00FF) * otherBlend) & 0xFF00FF00); #endif #endif } #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 Gradient_Shader::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); } /* * 2x2 dither a fixed-point color component (8.16) down to 8, matching the * semantics of how we 2x2 dither 32->16 */ static inline U8CPU dither_fixed_to_8(SkFixed n) { n >>= 8; return ((n << 1) - ((n >> 8 << 8) | (n >> 8))) >> 8; } /* * For dithering with premultiply, we want to ceiling the alpha component, * to ensure that it is always >= any color component. */ static inline U8CPU dither_ceil_fixed_to_8(SkFixed n) { n >>= 8; return ((n << 1) - (n | (n >> 8))) >> 8; } void Gradient_Shader::Build32bitCache(SkPMColor cache[], SkColor c0, SkColor c1, int count, U8CPU paintAlpha) { SkASSERT(count > 1); // need to apply paintAlpha to our two endpoints SkFixed a = SkMulDiv255Round(SkColorGetA(c0), paintAlpha); SkFixed da; { int tmp = SkMulDiv255Round(SkColorGetA(c1), paintAlpha); da = SkIntToFixed(tmp - a) / (count - 1); } 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); a = SkIntToFixed(a) + 0x8000; r = SkIntToFixed(r) + 0x8000; g = SkIntToFixed(g) + 0x8000; b = SkIntToFixed(b) + 0x8000; do { cache[0] = SkPremultiplyARGBInline(a >> 16, r >> 16, g >> 16, b >> 16); cache[kCache32Count] = SkPremultiplyARGBInline(dither_ceil_fixed_to_8(a), dither_fixed_to_8(r), dither_fixed_to_8(g), dither_fixed_to_8(b)); 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; } /** We duplicate the last value in each half of the cache so that interpolation doesn't have to special-case being at the last point. */ static void complete_16bit_cache(uint16_t* cache, int stride) { cache[stride - 1] = cache[stride - 2]; cache[2 * stride - 1] = cache[2 * stride - 2]; } const uint16_t* Gradient_Shader::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], kGradient16Length); } 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; } // one extra space left over at the end for complete_16bit_cache() SkASSERT(prevIndex == kGradient16Length - 1); } 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 < kGradient16Length; i++) { int index = map->mapUnit16(bitsTo16(i, kCache16Bits)) >> kCache16Shift; mapped[i] = linear[index]; mapped[i + kCache16Count] = linear[index + kCache16Count]; } sk_free(fCache16); fCache16 = fCache16Storage; } complete_16bit_cache(fCache16, kCache16Count); } return fCache16; } /** We duplicate the last value in each half of the cache so that interpolation doesn't have to special-case being at the last point. */ static void complete_32bit_cache(SkPMColor* cache, int stride) { cache[stride - 1] = cache[stride - 2]; cache[2 * stride - 1] = cache[2 * stride - 2]; } const SkPMColor* Gradient_Shader::getCache32() const { if (fCache32 == NULL) { // double the count for dither entries const int entryCount = kCache32Count * 2; 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], kGradient32Length, fCacheAlpha); } else { Rec* rec = fRecs; int prevIndex = 0; for (int i = 1; i < fColorCount; i++) { int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache32Shift; SkASSERT(nextIndex < kGradient32Length); if (nextIndex > prevIndex) Build32bitCache(fCache32 + prevIndex, fOrigColors[i-1], fOrigColors[i], nextIndex - prevIndex + 1, fCacheAlpha); prevIndex = nextIndex; } SkASSERT(prevIndex == kGradient32Length - 1); } 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 < kGradient32Length; i++) { int index = map->mapUnit16((i << 8) | i) >> 8; mapped[i] = linear[index]; mapped[i + kCache32Count] = linear[index + kCache32Count]; } fCache32PixelRef->unref(); fCache32PixelRef = newPR; fCache32 = (SkPMColor*)newPR->getAddr(); } complete_32bit_cache(fCache32, kCache32Count); } 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 Gradient_Shader::commonAsABitmap(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, kGradient32Length, 1); bitmap->setPixelRef(fCache32PixelRef); return; } // build our key: [numColors + colors[] + {positions[]} ] int count = 1 + fColorCount; 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; } } 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 = new 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(); // Only expose the linear section of the cache; don't let the caller // know about the padding at the end to make interpolation faster. bitmap->setConfig(SkBitmap::kARGB_8888_Config, kGradient32Length, 1); bitmap->setPixelRef(fCache32PixelRef); gCache->add(storage.get(), size, *bitmap); } } void Gradient_Shader::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; } } /////////////////////////////////////////////////////////////////////////////// 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); } /////////////////////////////////////////////////////////////////////////////// class Linear_Gradient : public Gradient_Shader { public: Linear_Gradient(const SkPoint pts[2], const SkColor colors[], const SkScalar pos[], int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper) : Gradient_Shader(colors, pos, colorCount, mode, mapper), fStart(pts[0]), fEnd(pts[1]) { pts_to_unit_matrix(pts, &fPtsToUnit); } 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*, SkScalar* twoPointRadialParams) const SK_OVERRIDE; virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE; static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) { return SkNEW_ARGS(Linear_Gradient, (buffer)); } virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE { this->INHERITED::flatten(buffer); buffer.writeScalar(fStart.fX); buffer.writeScalar(fStart.fY); buffer.writeScalar(fEnd.fX); buffer.writeScalar(fEnd.fY); } SK_DECLARE_FLATTENABLE_REGISTRAR() protected: Linear_Gradient(SkFlattenableReadBuffer& buffer) : Gradient_Shader(buffer), fStart(unflatten_point(buffer)), fEnd(unflatten_point(buffer)) { } virtual Factory getFactory() SK_OVERRIDE { return CreateProc; } private: typedef Gradient_Shader INHERITED; const SkPoint fStart; const SkPoint fEnd; }; bool Linear_Gradient::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) { fFlags |= SkShader::kConstInY32_Flag; 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 >> Gradient_Shader::kCache32Shift; \ SkASSERT(fi <= 0xFF); \ fx += dx; \ *dstC++ = cache[toggle + fi]; \ toggle ^= Gradient_Shader::kDitherStride32; \ } while (0) namespace { typedef void (*LinearShadeProc)(TileProc proc, SkFixed dx, SkFixed fx, SkPMColor* dstC, const SkPMColor* cache, int toggle, int count); // This function is deprecated, and will be replaced by // shadeSpan_linear_vertical_lerp() once Chrome has been weaned off of it. void shadeSpan_linear_vertical(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 >> (16 - Gradient_Shader::kCache32Bits); sk_memset32_dither(dstC, cache[toggle + fi], cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi], 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 >> (16 - Gradient_Shader::kCache32Bits); unsigned remainder = fullIndex & Gradient_Shader::kLerpRemainderMask32; SkPMColor lerp = SkFastFourByteInterp( cache[toggle + fi + 1], cache[toggle + fi], remainder); SkPMColor dlerp = SkFastFourByteInterp( cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi + 1], cache[(toggle ^ Gradient_Shader::kDitherStride32) + fi], 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, Gradient_Shader::kGradient32Length); if ((count = range.fCount0) > 0) { sk_memset32_dither(dstC, cache[toggle + range.fV0], cache[(toggle ^ Gradient_Shader::kDitherStride32) + 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[(toggle ^ Gradient_Shader::kDitherStride32) + 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 ^= Gradient_Shader::kDitherStride32; } 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 ^= Gradient_Shader::kDitherStride32; } while (--count != 0); } } void Linear_Gradient::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(); #ifdef USE_DITHER_32BIT_GRADIENT int toggle = ((x ^ y) & 1) * kDitherStride32; #else int toggle = 0; #endif 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 (SkFixedNearlyZero(dx)) { #ifdef SK_SIMPLE_TWOCOLOR_VERTICAL_GRADIENTS if (fColorCount > 2) { shadeProc = shadeSpan_linear_vertical_lerp; } else { shadeProc = shadeSpan_linear_vertical; } #else shadeProc = shadeSpan_linear_vertical_lerp; #endif } else if (proc == clamp_tileproc) { shadeProc = shadeSpan_linear_clamp; } else if (proc == mirror_tileproc) { shadeProc = shadeSpan_linear_mirror; } else { SkASSERT(proc == repeat_tileproc); } (*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 ^= Gradient_Shader::kDitherStride32; dstX += SK_Scalar1; } while (--count != 0); } } SkShader::BitmapType Linear_Gradient::asABitmap(SkBitmap* bitmap, SkMatrix* matrix, TileMode xy[], SkScalar* twoPointRadialParams) const { if (bitmap) { this->commonAsABitmap(bitmap); } if (matrix) { matrix->setScale(SkIntToScalar(kGradient32Length), SK_Scalar1); matrix->preConcat(fPtsToUnit); } if (xy) { xy[0] = fTileMode; xy[1] = kClamp_TileMode; } return kDefault_BitmapType; } SkShader::GradientType Linear_Gradient::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(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 >> Gradient_Shader::kCache16Shift; \ SkASSERT(fi < Gradient_Shader::kCache16Count); \ fx += dx; \ *dstC++ = cache[toggle + fi]; \ toggle ^= Gradient_Shader::kDitherStride16; \ } 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) >> Gradient_Shader::kCache16Shift; SkASSERT(fi < Gradient_Shader::kCache16Count); dither_memset16(dstC, cache[toggle + fi], cache[(toggle ^ Gradient_Shader::kDitherStride16) + 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, Gradient_Shader::kGradient16Length); if ((count = range.fCount0) > 0) { dither_memset16(dstC, cache[toggle + range.fV0], cache[(toggle ^ Gradient_Shader::kDitherStride16) + 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[(toggle ^ Gradient_Shader::kDitherStride16) + 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 >> Gradient_Shader::kCache16Shift, Gradient_Shader::kCache16Bits); SkASSERT(fi < Gradient_Shader::kCache16Count); fx += dx; *dstC++ = cache[toggle + fi]; toggle ^= Gradient_Shader::kDitherStride16; } 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) { SkASSERT(proc == repeat_tileproc); do { unsigned fi = repeat_bits(fx >> Gradient_Shader::kCache16Shift, Gradient_Shader::kCache16Bits); SkASSERT(fi < Gradient_Shader::kCache16Count); fx += dx; *dstC++ = cache[toggle + fi]; toggle ^= Gradient_Shader::kDitherStride16; } while (--count != 0); } } void Linear_Gradient::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 = ((x ^ y) & 1) * kDitherStride16; 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 (proc == clamp_tileproc) { shadeProc = shadeSpan16_linear_clamp; } else if (proc == mirror_tileproc) { shadeProc = shadeSpan16_linear_mirror; } else { SkASSERT(proc == repeat_tileproc); } (*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 ^= Gradient_Shader::kDitherStride16; dstX += SK_Scalar1; } while (--count != 0); } } /////////////////////////////////////////////////////////////////////////////// #define kSQRT_TABLE_BITS 11 #define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS) #include "SkRadialGradient_Table.h" #if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG) #include 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 static 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); } namespace { 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] >> Gradient_Shader::kSqrt16Shift)]; toggle ^= Gradient_Shader::kDitherStride16; } 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] >> Gradient_Shader::kSqrt16Shift)]; toggle ^= Gradient_Shader::kDitherStride16; } 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 >> Gradient_Shader::kCache16Shift)]; toggle ^= Gradient_Shader::kDitherStride16; 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 >> Gradient_Shader::kCache16Shift)]; toggle ^= Gradient_Shader::kDitherStride16; } while (--count != 0); } } class Radial_Gradient : public Gradient_Shader { public: Radial_Gradient(const SkPoint& center, SkScalar radius, const SkColor colors[], const SkScalar pos[], int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper) : Gradient_Shader(colors, pos, colorCount, mode, mapper), 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); } virtual void shadeSpan(int x, int y, SkPMColor* dstC, int count) SK_OVERRIDE; virtual void shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC, int count) SK_OVERRIDE { SkASSERT(count > 0); SkPoint srcPt; SkMatrix::MapXYProc dstProc = fDstToIndexProc; TileProc proc = fTileProc; const uint16_t* SK_RESTRICT cache = this->getCache16(); int toggle = ((x ^ y) & 1) * kDitherStride16; 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 (proc == clamp_tileproc) { shadeProc = shadeSpan16_radial_clamp; } else if (proc == mirror_tileproc) { shadeProc = shadeSpan16_radial_mirror; } else { SkASSERT(proc == repeat_tileproc); } (*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 ^= kDitherStride16; dstX += SK_Scalar1; } while (--count != 0); } } virtual BitmapType asABitmap(SkBitmap* bitmap, SkMatrix* matrix, TileMode* xy, SkScalar* twoPointRadialParams) const SK_OVERRIDE { if (bitmap) { this->commonAsABitmap(bitmap); } if (matrix) { matrix->setScale(SkIntToScalar(kGradient32Length), SkIntToScalar(kGradient32Length)); matrix->preConcat(fPtsToUnit); } if (xy) { xy[0] = fTileMode; xy[1] = kClamp_TileMode; } return kRadial_BitmapType; } virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE { if (info) { commonAsAGradient(info); info->fPoint[0] = fCenter; info->fRadius[0] = fRadius; } return kRadial_GradientType; } static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) { return SkNEW_ARGS(Radial_Gradient, (buffer)); } virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE { this->INHERITED::flatten(buffer); buffer.writeScalar(fCenter.fX); buffer.writeScalar(fCenter.fY); buffer.writeScalar(fRadius); } protected: Radial_Gradient(SkFlattenableReadBuffer& buffer) : Gradient_Shader(buffer), fCenter(unflatten_point(buffer)), fRadius(buffer.readScalar()) { } virtual Factory getFactory() SK_OVERRIDE { return CreateProc; } private: typedef Gradient_Shader INHERITED; const SkPoint fCenter; const SkScalar 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] >> Gradient_Shader::kSqrt32Shift)]; \ toggle ^= Gradient_Shader::kDitherStride32; \ 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 = Gradient_Shader::kGradient32Length; sk_memset32_dither(dstC, cache[toggle + fi], cache[(toggle ^ Gradient_Shader::kDitherStride32) + 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] >> Gradient_Shader::kSqrt32Shift)]; toggle ^= Gradient_Shader::kDitherStride32; 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] >> Gradient_Shader::kSqrt32Shift)]; toggle ^= Gradient_Shader::kDitherStride32; 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 >> Gradient_Shader::kCache32Shift)]; toggle ^= Gradient_Shader::kDitherStride32; 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 >> Gradient_Shader::kCache32Shift)]; toggle ^= Gradient_Shader::kDitherStride32; fx += dx; fy += dy; } while (--count != 0); } } void Radial_Gradient::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(); #ifdef USE_DITHER_32BIT_GRADIENT int toggle = ((x ^ y) & 1) * Gradient_Shader::kDitherStride32; #else int toggle = 0; #endif 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 (proc == clamp_tileproc) { shadeProc = shadeSpan_radial_clamp; } else if (proc == mirror_tileproc) { shadeProc = shadeSpan_radial_mirror; } else { SkASSERT(proc == repeat_tileproc); } (*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 >> Gradient_Shader::kCache32Shift]; dstX += SK_Scalar1; } while (--count != 0); } } /* 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 >> Gradient_Shader::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 >> Gradient_Shader::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 >> Gradient_Shader::kCache32Shift]; fx += dx; fy += dy; b += db; } } } class Two_Point_Radial_Gradient : public Gradient_Shader { public: Two_Point_Radial_Gradient(const SkPoint& start, SkScalar startRadius, const SkPoint& end, SkScalar endRadius, const SkColor colors[], const SkScalar pos[], int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper) : Gradient_Shader(colors, pos, colorCount, mode, mapper), fCenter1(start), fCenter2(end), fRadius1(startRadius), fRadius2(endRadius) { init(); } virtual BitmapType asABitmap(SkBitmap* bitmap, SkMatrix* matrix, TileMode* xy, SkScalar* twoPointRadialParams) const { if (bitmap) { this->commonAsABitmap(bitmap); } SkScalar diffL = 0; // just to avoid gcc warning if (matrix || twoPointRadialParams) { 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; } if (NULL != twoPointRadialParams) { twoPointRadialParams[0] = diffL; twoPointRadialParams[1] = fStartRadius; twoPointRadialParams[2] = fDiffRadius; } return kTwoPointRadial_BitmapType; } virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE { if (info) { commonAsAGradient(info); info->fPoint[0] = fCenter1; info->fPoint[1] = fCenter2; info->fRadius[0] = fRadius1; info->fRadius[1] = fRadius2; } return kRadial2_GradientType; } virtual void shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count) SK_OVERRIDE { SkASSERT(count > 0); // 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 (proc == clamp_tileproc) { shadeProc = shadeSpan_twopoint_clamp; } else if (proc == mirror_tileproc) { shadeProc = shadeSpan_twopoint_mirror; } else { SkASSERT(proc == repeat_tileproc); } (*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 >> Gradient_Shader::kCache32Shift]; dstX += SK_Scalar1; } } } virtual bool setContext(const SkBitmap& device, const SkPaint& paint, const SkMatrix& matrix) SK_OVERRIDE { if (!this->INHERITED::setContext(device, paint, matrix)) { return false; } // we don't have a span16 proc fFlags &= ~kHasSpan16_Flag; return true; } static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) { return SkNEW_ARGS(Two_Point_Radial_Gradient, (buffer)); } virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE { this->INHERITED::flatten(buffer); buffer.writeScalar(fCenter1.fX); buffer.writeScalar(fCenter1.fY); buffer.writeScalar(fCenter2.fX); buffer.writeScalar(fCenter2.fY); buffer.writeScalar(fRadius1); buffer.writeScalar(fRadius2); } protected: Two_Point_Radial_Gradient(SkFlattenableReadBuffer& buffer) : Gradient_Shader(buffer), fCenter1(unflatten_point(buffer)), fCenter2(unflatten_point(buffer)), fRadius1(buffer.readScalar()), fRadius2(buffer.readScalar()) { init(); }; virtual Factory getFactory() SK_OVERRIDE { return CreateProc; } private: typedef Gradient_Shader INHERITED; const SkPoint fCenter1; const SkPoint fCenter2; const SkScalar fRadius1; const SkScalar fRadius2; SkPoint fDiff; SkScalar fStartRadius, fDiffRadius, fSr2D2, fA, fOneOverTwoA; void init() { fDiff = fCenter1 - fCenter2; fDiffRadius = fRadius2 - fRadius1; SkScalar inv = SkScalarInvert(fDiffRadius); 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); } }; /////////////////////////////////////////////////////////////////////////////// class Sweep_Gradient : public Gradient_Shader { public: Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[], const SkScalar pos[], int count, SkUnitMapper* mapper) : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper), fCenter(SkPoint::Make(cx, cy)) { fPtsToUnit.setTranslate(-cx, -cy); } 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, SkScalar* twoPointRadialParams) const SK_OVERRIDE { if (bitmap) { this->commonAsABitmap(bitmap); } if (matrix) { *matrix = fPtsToUnit; } if (xy) { xy[0] = fTileMode; xy[1] = kClamp_TileMode; } return kSweep_BitmapType; } virtual GradientType asAGradient(GradientInfo* info) const SK_OVERRIDE { if (info) { commonAsAGradient(info); info->fPoint[0] = fCenter; } return kSweep_GradientType; } static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) { return SkNEW_ARGS(Sweep_Gradient, (buffer)); } virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE { this->INHERITED::flatten(buffer); buffer.writeScalar(fCenter.fX); buffer.writeScalar(fCenter.fY); } protected: Sweep_Gradient(SkFlattenableReadBuffer& buffer) : Gradient_Shader(buffer), fCenter(unflatten_point(buffer)) { } virtual Factory getFactory() SK_OVERRIDE { return CreateProc; } private: typedef Gradient_Shader INHERITED; const SkPoint fCenter; }; #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 // 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); 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; } // Given x,y in the first quadrant, return 0..63 for the angle [0..90] 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(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; } // 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(x, y); } unsigned result = add + atan_0_90(y, x); SkASSERT(result < 256); return result; } #endif void Sweep_Gradient::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(); 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[SkATan2_255(fy, fx)]; 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); *dstC++ = cache[SkATan2_255(srcPt.fY, srcPt.fX)]; } } } void Sweep_Gradient::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 = ((x ^ y) & 1) * kDitherStride16; 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 ^= kDitherStride16; 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 ^= kDitherStride16; } } } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // 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) SkShader* SkGradientShader::CreateLinear(const SkPoint pts[2], const SkColor colors[], const SkScalar pos[], int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper) { if (NULL == pts || NULL == colors || colorCount < 1) { return NULL; } EXPAND_1_COLOR(colorCount); return SkNEW_ARGS(Linear_Gradient, (pts, colors, pos, colorCount, mode, mapper)); } SkShader* SkGradientShader::CreateRadial(const SkPoint& center, SkScalar radius, const SkColor colors[], const SkScalar pos[], int colorCount, SkShader::TileMode mode, SkUnitMapper* mapper) { if (radius <= 0 || NULL == colors || colorCount < 1) { return NULL; } EXPAND_1_COLOR(colorCount); return SkNEW_ARGS(Radial_Gradient, (center, radius, colors, pos, colorCount, mode, mapper)); } 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) { if (startRadius < 0 || endRadius < 0 || NULL == colors || colorCount < 1) { return NULL; } EXPAND_1_COLOR(colorCount); return SkNEW_ARGS(Two_Point_Radial_Gradient, (start, startRadius, end, endRadius, colors, pos, colorCount, mode, mapper)); } SkShader* SkGradientShader::CreateSweep(SkScalar cx, SkScalar cy, const SkColor colors[], const SkScalar pos[], int count, SkUnitMapper* mapper) { if (NULL == colors || count < 1) { return NULL; } EXPAND_1_COLOR(count); return SkNEW_ARGS(Sweep_Gradient, (cx, cy, colors, pos, count, mapper)); } SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkGradientShader) SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Linear_Gradient) SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Radial_Gradient) SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Sweep_Gradient) SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(Two_Point_Radial_Gradient) SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END