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