| |
| /* |
| * 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 "SkColorPriv.h" |
| #include "SkReadBuffer.h" |
| #include "SkWriteBuffer.h" |
| #include "SkPixelRef.h" |
| #include "SkErrorInternals.h" |
| #include "SkBitmapProcShader.h" |
| |
| #if SK_SUPPORT_GPU |
| #include "effects/GrSimpleTextureEffect.h" |
| #include "effects/GrBicubicEffect.h" |
| #endif |
| |
| bool SkBitmapProcShader::CanDo(const SkBitmap& bm, TileMode tx, TileMode ty) { |
| switch (bm.colorType()) { |
| case kAlpha_8_SkColorType: |
| case kRGB_565_SkColorType: |
| case kIndex_8_SkColorType: |
| case kN32_SkColorType: |
| // if (tx == ty && (kClamp_TileMode == tx || kRepeat_TileMode == tx)) |
| return true; |
| default: |
| break; |
| } |
| return false; |
| } |
| |
| SkBitmapProcShader::SkBitmapProcShader(const SkBitmap& src, TileMode tmx, TileMode tmy, |
| const SkMatrix* localMatrix) |
| : INHERITED(localMatrix) { |
| fRawBitmap = src; |
| fTileModeX = (uint8_t)tmx; |
| fTileModeY = (uint8_t)tmy; |
| } |
| |
| SkBitmapProcShader::SkBitmapProcShader(SkReadBuffer& buffer) |
| : INHERITED(buffer) { |
| buffer.readBitmap(&fRawBitmap); |
| fRawBitmap.setImmutable(); |
| fTileModeX = buffer.readUInt(); |
| fTileModeY = buffer.readUInt(); |
| } |
| |
| SkShader::BitmapType SkBitmapProcShader::asABitmap(SkBitmap* texture, |
| SkMatrix* texM, |
| TileMode xy[]) const { |
| if (texture) { |
| *texture = fRawBitmap; |
| } |
| if (texM) { |
| texM->reset(); |
| } |
| if (xy) { |
| xy[0] = (TileMode)fTileModeX; |
| xy[1] = (TileMode)fTileModeY; |
| } |
| return kDefault_BitmapType; |
| } |
| |
| void SkBitmapProcShader::flatten(SkWriteBuffer& buffer) const { |
| this->INHERITED::flatten(buffer); |
| |
| buffer.writeBitmap(fRawBitmap); |
| buffer.writeUInt(fTileModeX); |
| buffer.writeUInt(fTileModeY); |
| } |
| |
| static bool only_scale_and_translate(const SkMatrix& matrix) { |
| unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask; |
| return (matrix.getType() & ~mask) == 0; |
| } |
| |
| bool SkBitmapProcShader::isOpaque() const { |
| return fRawBitmap.isOpaque(); |
| } |
| |
| static bool valid_for_drawing(const SkBitmap& bm) { |
| if (0 == bm.width() || 0 == bm.height()) { |
| return false; // nothing to draw |
| } |
| if (NULL == bm.pixelRef()) { |
| return false; // no pixels to read |
| } |
| if (kIndex_8_SkColorType == bm.colorType()) { |
| // ugh, I have to lock-pixels to inspect the colortable |
| SkAutoLockPixels alp(bm); |
| if (!bm.getColorTable()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool SkBitmapProcShader::validInternal(const ContextRec& rec, SkMatrix* totalInverse, |
| SkBitmapProcState* state) const { |
| if (!fRawBitmap.getTexture() && !valid_for_drawing(fRawBitmap)) { |
| return false; |
| } |
| |
| // Make sure we can use totalInverse as a cache. |
| SkMatrix totalInverseLocal; |
| if (NULL == totalInverse) { |
| totalInverse = &totalInverseLocal; |
| } |
| |
| // Do this first, so we know the matrix can be inverted. |
| if (!this->INHERITED::validContext(rec, totalInverse)) { |
| return false; |
| } |
| |
| SkASSERT(state); |
| state->fTileModeX = fTileModeX; |
| state->fTileModeY = fTileModeY; |
| state->fOrigBitmap = fRawBitmap; |
| return state->chooseProcs(*totalInverse, *rec.fPaint); |
| } |
| |
| bool SkBitmapProcShader::validContext(const ContextRec& rec, SkMatrix* totalInverse) const { |
| SkBitmapProcState state; |
| return this->validInternal(rec, totalInverse, &state); |
| } |
| |
| SkShader::Context* SkBitmapProcShader::createContext(const ContextRec& rec, void* storage) const { |
| void* stateStorage = (char*)storage + sizeof(BitmapProcShaderContext); |
| SkBitmapProcState* state = SkNEW_PLACEMENT(stateStorage, SkBitmapProcState); |
| if (!this->validInternal(rec, NULL, state)) { |
| state->~SkBitmapProcState(); |
| return NULL; |
| } |
| |
| return SkNEW_PLACEMENT_ARGS(storage, BitmapProcShaderContext, (*this, rec, state)); |
| } |
| |
| size_t SkBitmapProcShader::contextSize() const { |
| // The SkBitmapProcState is stored outside of the context object, with the context holding |
| // a pointer to it. |
| return sizeof(BitmapProcShaderContext) + sizeof(SkBitmapProcState); |
| } |
| |
| SkBitmapProcShader::BitmapProcShaderContext::BitmapProcShaderContext( |
| const SkBitmapProcShader& shader, const ContextRec& rec, SkBitmapProcState* state) |
| : INHERITED(shader, rec) |
| , fState(state) |
| { |
| const SkBitmap& bitmap = *fState->fBitmap; |
| bool bitmapIsOpaque = bitmap.isOpaque(); |
| |
| // update fFlags |
| uint32_t flags = 0; |
| if (bitmapIsOpaque && (255 == this->getPaintAlpha())) { |
| flags |= kOpaqueAlpha_Flag; |
| } |
| |
| switch (bitmap.colorType()) { |
| case kRGB_565_SkColorType: |
| flags |= (kHasSpan16_Flag | kIntrinsicly16_Flag); |
| break; |
| case kIndex_8_SkColorType: |
| case kN32_SkColorType: |
| if (bitmapIsOpaque) { |
| flags |= kHasSpan16_Flag; |
| } |
| break; |
| case kAlpha_8_SkColorType: |
| break; // never set kHasSpan16_Flag |
| default: |
| break; |
| } |
| |
| if (rec.fPaint->isDither() && bitmap.colorType() != kRGB_565_SkColorType) { |
| // gradients can auto-dither in their 16bit sampler, but we don't so |
| // we clear the flag here. |
| flags &= ~kHasSpan16_Flag; |
| } |
| |
| // if we're only 1-pixel high, and we don't rotate, then we can claim this |
| if (1 == bitmap.height() && |
| only_scale_and_translate(this->getTotalInverse())) { |
| flags |= kConstInY32_Flag; |
| if (flags & kHasSpan16_Flag) { |
| flags |= kConstInY16_Flag; |
| } |
| } |
| |
| fFlags = flags; |
| } |
| |
| SkBitmapProcShader::BitmapProcShaderContext::~BitmapProcShaderContext() { |
| // The bitmap proc state has been created outside of the context on memory that will be freed |
| // elsewhere. Only call the destructor but leave the freeing of the memory to the caller. |
| fState->~SkBitmapProcState(); |
| } |
| |
| #define BUF_MAX 128 |
| |
| #define TEST_BUFFER_OVERRITEx |
| |
| #ifdef TEST_BUFFER_OVERRITE |
| #define TEST_BUFFER_EXTRA 32 |
| #define TEST_PATTERN 0x88888888 |
| #else |
| #define TEST_BUFFER_EXTRA 0 |
| #endif |
| |
| void SkBitmapProcShader::BitmapProcShaderContext::shadeSpan(int x, int y, SkPMColor dstC[], |
| int count) { |
| const SkBitmapProcState& state = *fState; |
| if (state.getShaderProc32()) { |
| state.getShaderProc32()(state, x, y, dstC, count); |
| return; |
| } |
| |
| uint32_t buffer[BUF_MAX + TEST_BUFFER_EXTRA]; |
| SkBitmapProcState::MatrixProc mproc = state.getMatrixProc(); |
| SkBitmapProcState::SampleProc32 sproc = state.getSampleProc32(); |
| int max = state.maxCountForBufferSize(sizeof(buffer[0]) * BUF_MAX); |
| |
| SkASSERT(state.fBitmap->getPixels()); |
| SkASSERT(state.fBitmap->pixelRef() == NULL || |
| state.fBitmap->pixelRef()->isLocked()); |
| |
| for (;;) { |
| int n = count; |
| if (n > max) { |
| n = max; |
| } |
| SkASSERT(n > 0 && n < BUF_MAX*2); |
| #ifdef TEST_BUFFER_OVERRITE |
| for (int i = 0; i < TEST_BUFFER_EXTRA; i++) { |
| buffer[BUF_MAX + i] = TEST_PATTERN; |
| } |
| #endif |
| mproc(state, buffer, n, x, y); |
| #ifdef TEST_BUFFER_OVERRITE |
| for (int j = 0; j < TEST_BUFFER_EXTRA; j++) { |
| SkASSERT(buffer[BUF_MAX + j] == TEST_PATTERN); |
| } |
| #endif |
| sproc(state, buffer, n, dstC); |
| |
| if ((count -= n) == 0) { |
| break; |
| } |
| SkASSERT(count > 0); |
| x += n; |
| dstC += n; |
| } |
| } |
| |
| SkShader::Context::ShadeProc SkBitmapProcShader::BitmapProcShaderContext::asAShadeProc(void** ctx) { |
| if (fState->getShaderProc32()) { |
| *ctx = fState; |
| return (ShadeProc)fState->getShaderProc32(); |
| } |
| return NULL; |
| } |
| |
| void SkBitmapProcShader::BitmapProcShaderContext::shadeSpan16(int x, int y, uint16_t dstC[], |
| int count) { |
| const SkBitmapProcState& state = *fState; |
| if (state.getShaderProc16()) { |
| state.getShaderProc16()(state, x, y, dstC, count); |
| return; |
| } |
| |
| uint32_t buffer[BUF_MAX]; |
| SkBitmapProcState::MatrixProc mproc = state.getMatrixProc(); |
| SkBitmapProcState::SampleProc16 sproc = state.getSampleProc16(); |
| int max = state.maxCountForBufferSize(sizeof(buffer)); |
| |
| SkASSERT(state.fBitmap->getPixels()); |
| SkASSERT(state.fBitmap->pixelRef() == NULL || |
| state.fBitmap->pixelRef()->isLocked()); |
| |
| for (;;) { |
| int n = count; |
| if (n > max) { |
| n = max; |
| } |
| mproc(state, buffer, n, x, y); |
| sproc(state, buffer, n, dstC); |
| |
| if ((count -= n) == 0) { |
| break; |
| } |
| x += n; |
| dstC += n; |
| } |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| #include "SkUnPreMultiply.h" |
| #include "SkColorShader.h" |
| #include "SkEmptyShader.h" |
| |
| // returns true and set color if the bitmap can be drawn as a single color |
| // (for efficiency) |
| static bool canUseColorShader(const SkBitmap& bm, SkColor* color) { |
| if (1 != bm.width() || 1 != bm.height()) { |
| return false; |
| } |
| |
| SkAutoLockPixels alp(bm); |
| if (!bm.readyToDraw()) { |
| return false; |
| } |
| |
| switch (bm.colorType()) { |
| case kN32_SkColorType: |
| *color = SkUnPreMultiply::PMColorToColor(*bm.getAddr32(0, 0)); |
| return true; |
| case kRGB_565_SkColorType: |
| *color = SkPixel16ToColor(*bm.getAddr16(0, 0)); |
| return true; |
| case kIndex_8_SkColorType: |
| *color = SkUnPreMultiply::PMColorToColor(bm.getIndex8Color(0, 0)); |
| return true; |
| default: // just skip the other configs for now |
| break; |
| } |
| return false; |
| } |
| |
| static bool bitmapIsTooBig(const SkBitmap& bm) { |
| // SkBitmapProcShader stores bitmap coordinates in a 16bit buffer, as it |
| // communicates between its matrix-proc and its sampler-proc. Until we can |
| // widen that, we have to reject bitmaps that are larger. |
| // |
| const int maxSize = 65535; |
| |
| return bm.width() > maxSize || bm.height() > maxSize; |
| } |
| |
| SkShader* CreateBitmapShader(const SkBitmap& src, SkShader::TileMode tmx, |
| SkShader::TileMode tmy, const SkMatrix* localMatrix, SkTBlitterAllocator* allocator) { |
| SkShader* shader; |
| SkColor color; |
| if (src.isNull() || bitmapIsTooBig(src)) { |
| if (NULL == allocator) { |
| shader = SkNEW(SkEmptyShader); |
| } else { |
| shader = allocator->createT<SkEmptyShader>(); |
| } |
| } |
| else if (canUseColorShader(src, &color)) { |
| if (NULL == allocator) { |
| shader = SkNEW_ARGS(SkColorShader, (color)); |
| } else { |
| shader = allocator->createT<SkColorShader>(color); |
| } |
| } else { |
| if (NULL == allocator) { |
| shader = SkNEW_ARGS(SkBitmapProcShader, (src, tmx, tmy, localMatrix)); |
| } else { |
| shader = allocator->createT<SkBitmapProcShader>(src, tmx, tmy, localMatrix); |
| } |
| } |
| return shader; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| #ifndef SK_IGNORE_TO_STRING |
| void SkBitmapProcShader::toString(SkString* str) const { |
| static const char* gTileModeName[SkShader::kTileModeCount] = { |
| "clamp", "repeat", "mirror" |
| }; |
| |
| str->append("BitmapShader: ("); |
| |
| str->appendf("(%s, %s)", |
| gTileModeName[fTileModeX], |
| gTileModeName[fTileModeY]); |
| |
| str->append(" "); |
| fRawBitmap.toString(str); |
| |
| this->INHERITED::toString(str); |
| |
| str->append(")"); |
| } |
| #endif |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| #if SK_SUPPORT_GPU |
| |
| #include "GrTextureAccess.h" |
| #include "effects/GrSimpleTextureEffect.h" |
| #include "SkGr.h" |
| |
| // Note that this will return -1 if either matrix is perspective. |
| static SkScalar get_combined_min_stretch(const SkMatrix& viewMatrix, const SkMatrix& localMatrix) { |
| if (localMatrix.isIdentity()) { |
| return viewMatrix.getMinStretch(); |
| } else { |
| SkMatrix combined; |
| combined.setConcat(viewMatrix, localMatrix); |
| return combined.getMinStretch(); |
| } |
| } |
| |
| GrEffectRef* SkBitmapProcShader::asNewEffect(GrContext* context, const SkPaint& paint) const { |
| SkMatrix matrix; |
| matrix.setIDiv(fRawBitmap.width(), fRawBitmap.height()); |
| |
| SkMatrix lmInverse; |
| if (!this->getLocalMatrix().invert(&lmInverse)) { |
| return NULL; |
| } |
| matrix.preConcat(lmInverse); |
| |
| SkShader::TileMode tm[] = { |
| (TileMode)fTileModeX, |
| (TileMode)fTileModeY, |
| }; |
| |
| // Must set wrap and filter on the sampler before requesting a texture. In two places below |
| // we check the matrix scale factors to determine how to interpret the filter quality setting. |
| // This completely ignores the complexity of the drawVertices case where explicit local coords |
| // are provided by the caller. |
| SkPaint::FilterLevel paintFilterLevel = paint.getFilterLevel(); |
| GrTextureParams::FilterMode textureFilterMode; |
| switch(paintFilterLevel) { |
| case SkPaint::kNone_FilterLevel: |
| textureFilterMode = GrTextureParams::kNone_FilterMode; |
| break; |
| case SkPaint::kLow_FilterLevel: |
| textureFilterMode = GrTextureParams::kBilerp_FilterMode; |
| break; |
| case SkPaint::kMedium_FilterLevel: |
| if (get_combined_min_stretch(context->getMatrix(), this->getLocalMatrix()) < |
| SK_Scalar1) { |
| textureFilterMode = GrTextureParams::kMipMap_FilterMode; |
| } else { |
| // Don't trigger MIP level generation unnecessarily. |
| textureFilterMode = GrTextureParams::kBilerp_FilterMode; |
| } |
| break; |
| case SkPaint::kHigh_FilterLevel: |
| // Minification can look bad with bicubic filtering. |
| if (get_combined_min_stretch(context->getMatrix(), this->getLocalMatrix()) >= |
| SK_Scalar1) { |
| // fall back to no filtering here; we will install another shader that will do the |
| // HQ filtering. |
| textureFilterMode = GrTextureParams::kNone_FilterMode; |
| } else { |
| // Fall back to MIP-mapping. |
| paintFilterLevel = SkPaint::kMedium_FilterLevel; |
| textureFilterMode = GrTextureParams::kMipMap_FilterMode; |
| } |
| break; |
| default: |
| SkErrorInternals::SetError( kInvalidPaint_SkError, |
| "Sorry, I don't understand the filtering " |
| "mode you asked for. Falling back to " |
| "MIPMaps."); |
| textureFilterMode = GrTextureParams::kMipMap_FilterMode; |
| break; |
| |
| } |
| GrTextureParams params(tm, textureFilterMode); |
| GrTexture* texture = GrLockAndRefCachedBitmapTexture(context, fRawBitmap, ¶ms); |
| |
| if (NULL == texture) { |
| SkErrorInternals::SetError( kInternalError_SkError, |
| "Couldn't convert bitmap to texture."); |
| return NULL; |
| } |
| |
| GrEffectRef* effect = NULL; |
| if (paintFilterLevel == SkPaint::kHigh_FilterLevel) { |
| effect = GrBicubicEffect::Create(texture, matrix, tm); |
| } else { |
| effect = GrSimpleTextureEffect::Create(texture, matrix, params); |
| } |
| GrUnlockAndUnrefCachedBitmapTexture(texture); |
| return effect; |
| } |
| #endif |