diff options
Diffstat (limited to 'effects/gradients')
-rw-r--r-- | effects/gradients/SkBitmapCache.cpp | 153 | ||||
-rw-r--r-- | effects/gradients/SkBitmapCache.h | 49 | ||||
-rw-r--r-- | effects/gradients/SkClampRange.cpp | 165 | ||||
-rw-r--r-- | effects/gradients/SkClampRange.h | 38 | ||||
-rw-r--r-- | effects/gradients/SkGradientShader.cpp | 986 | ||||
-rw-r--r-- | effects/gradients/SkGradientShaderPriv.h | 349 | ||||
-rw-r--r-- | effects/gradients/SkLinearGradient.cpp | 568 | ||||
-rw-r--r-- | effects/gradients/SkLinearGradient.h | 38 | ||||
-rw-r--r-- | effects/gradients/SkRadialGradient.cpp | 608 | ||||
-rw-r--r-- | effects/gradients/SkRadialGradient.h | 40 | ||||
-rw-r--r-- | effects/gradients/SkRadialGradient_Table.h | 139 | ||||
-rw-r--r-- | effects/gradients/SkSweepGradient.cpp | 517 | ||||
-rw-r--r-- | effects/gradients/SkSweepGradient.h | 40 | ||||
-rw-r--r-- | effects/gradients/SkTwoPointConicalGradient.cpp | 765 | ||||
-rw-r--r-- | effects/gradients/SkTwoPointConicalGradient.h | 84 | ||||
-rw-r--r-- | effects/gradients/SkTwoPointRadialGradient.cpp | 717 | ||||
-rw-r--r-- | effects/gradients/SkTwoPointRadialGradient.h | 55 |
17 files changed, 5311 insertions, 0 deletions
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 |