| /* |
| * Copyright 2015 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "SkArenaAlloc.h" |
| #include "SkBitmapController.h" |
| #include "SkBitmapProcShader.h" |
| #include "SkBitmapProvider.h" |
| #include "SkColorTable.h" |
| #include "SkEmptyShader.h" |
| #include "SkImage_Base.h" |
| #include "SkImageShader.h" |
| #include "SkImageShaderContext.h" |
| #include "SkPM4fPriv.h" |
| #include "SkReadBuffer.h" |
| #include "SkWriteBuffer.h" |
| |
| SkImageShader::SkImageShader(sk_sp<SkImage> img, TileMode tmx, TileMode tmy, const SkMatrix* matrix) |
| : INHERITED(matrix) |
| , fImage(std::move(img)) |
| , fTileModeX(tmx) |
| , fTileModeY(tmy) |
| {} |
| |
| sk_sp<SkFlattenable> SkImageShader::CreateProc(SkReadBuffer& buffer) { |
| const TileMode tx = (TileMode)buffer.readUInt(); |
| const TileMode ty = (TileMode)buffer.readUInt(); |
| SkMatrix matrix; |
| buffer.readMatrix(&matrix); |
| sk_sp<SkImage> img = buffer.readImage(); |
| if (!img) { |
| return nullptr; |
| } |
| return SkImageShader::Make(std::move(img), tx, ty, &matrix); |
| } |
| |
| void SkImageShader::flatten(SkWriteBuffer& buffer) const { |
| buffer.writeUInt(fTileModeX); |
| buffer.writeUInt(fTileModeY); |
| buffer.writeMatrix(this->getLocalMatrix()); |
| buffer.writeImage(fImage.get()); |
| } |
| |
| bool SkImageShader::isOpaque() const { |
| return fImage->isOpaque(); |
| } |
| |
| size_t SkImageShader::onContextSize(const ContextRec& rec) const { |
| return SkBitmapProcLegacyShader::ContextSize(rec, as_IB(fImage)->onImageInfo()); |
| } |
| |
| SkShader::Context* SkImageShader::onCreateContext(const ContextRec& rec, void* storage) const { |
| return SkBitmapProcLegacyShader::MakeContext(*this, fTileModeX, fTileModeY, |
| SkBitmapProvider(fImage.get(), rec.fDstColorSpace), |
| rec, storage); |
| } |
| |
| SkImage* SkImageShader::onIsAImage(SkMatrix* texM, TileMode xy[]) const { |
| if (texM) { |
| *texM = this->getLocalMatrix(); |
| } |
| if (xy) { |
| xy[0] = (TileMode)fTileModeX; |
| xy[1] = (TileMode)fTileModeY; |
| } |
| return const_cast<SkImage*>(fImage.get()); |
| } |
| |
| #ifdef SK_SUPPORT_LEGACY_SHADER_ISABITMAP |
| bool SkImageShader::onIsABitmap(SkBitmap* texture, SkMatrix* texM, TileMode xy[]) const { |
| const SkBitmap* bm = as_IB(fImage)->onPeekBitmap(); |
| if (!bm) { |
| return false; |
| } |
| |
| if (texture) { |
| *texture = *bm; |
| } |
| if (texM) { |
| *texM = this->getLocalMatrix(); |
| } |
| if (xy) { |
| xy[0] = (TileMode)fTileModeX; |
| xy[1] = (TileMode)fTileModeY; |
| } |
| return true; |
| } |
| #endif |
| |
| static bool bitmap_is_too_big(int w, int h) { |
| // 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. |
| // |
| static const int kMaxSize = 65535; |
| |
| return w > kMaxSize || h > kMaxSize; |
| } |
| |
| sk_sp<SkShader> SkImageShader::Make(sk_sp<SkImage> image, TileMode tx, TileMode ty, |
| const SkMatrix* localMatrix, |
| SkTBlitterAllocator* allocator) { |
| SkShader* shader; |
| if (!image || bitmap_is_too_big(image->width(), image->height())) { |
| if (nullptr == allocator) { |
| shader = new SkEmptyShader; |
| } else { |
| shader = allocator->createT<SkEmptyShader>(); |
| } |
| } else { |
| if (nullptr == allocator) { |
| shader = new SkImageShader(image, tx, ty, localMatrix); |
| } else { |
| shader = allocator->createT<SkImageShader>(image, tx, ty, localMatrix); |
| } |
| } |
| return sk_sp<SkShader>(shader); |
| } |
| |
| #ifndef SK_IGNORE_TO_STRING |
| void SkImageShader::toString(SkString* str) const { |
| const char* gTileModeName[SkShader::kTileModeCount] = { |
| "clamp", "repeat", "mirror" |
| }; |
| |
| str->appendf("ImageShader: ((%s %s) ", gTileModeName[fTileModeX], gTileModeName[fTileModeY]); |
| fImage->toString(str); |
| this->INHERITED::toString(str); |
| str->append(")"); |
| } |
| #endif |
| |
| /////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| #if SK_SUPPORT_GPU |
| |
| #include "SkGr.h" |
| #include "SkGrPriv.h" |
| #include "effects/GrSimpleTextureEffect.h" |
| #include "effects/GrBicubicEffect.h" |
| #include "effects/GrSimpleTextureEffect.h" |
| |
| sk_sp<GrFragmentProcessor> SkImageShader::asFragmentProcessor(const AsFPArgs& args) const { |
| |
| SkMatrix lmInverse; |
| if (!this->getLocalMatrix().invert(&lmInverse)) { |
| return nullptr; |
| } |
| if (args.fLocalMatrix) { |
| SkMatrix inv; |
| if (!args.fLocalMatrix->invert(&inv)) { |
| return nullptr; |
| } |
| lmInverse.postConcat(inv); |
| } |
| |
| SkShader::TileMode tm[] = { fTileModeX, 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. |
| bool doBicubic; |
| GrSamplerParams::FilterMode textureFilterMode = |
| GrSkFilterQualityToGrFilterMode(args.fFilterQuality, *args.fViewMatrix, this->getLocalMatrix(), |
| &doBicubic); |
| GrSamplerParams params(tm, textureFilterMode); |
| sk_sp<SkColorSpace> texColorSpace; |
| SkScalar scaleAdjust[2] = { 1.0f, 1.0f }; |
| sk_sp<GrTexture> texture(as_IB(fImage)->asTextureRef(args.fContext, params, args.fDstColorSpace, |
| &texColorSpace, scaleAdjust)); |
| if (!texture) { |
| return nullptr; |
| } |
| |
| lmInverse.postScale(scaleAdjust[0], scaleAdjust[1]); |
| |
| sk_sp<GrColorSpaceXform> colorSpaceXform = GrColorSpaceXform::Make(texColorSpace.get(), |
| args.fDstColorSpace); |
| sk_sp<GrFragmentProcessor> inner; |
| if (doBicubic) { |
| inner = GrBicubicEffect::Make(texture.get(), std::move(colorSpaceXform), lmInverse, tm); |
| } else { |
| inner = GrSimpleTextureEffect::Make(texture.get(), std::move(colorSpaceXform), |
| lmInverse, params); |
| } |
| |
| if (GrPixelConfigIsAlphaOnly(texture->config())) { |
| return inner; |
| } |
| return sk_sp<GrFragmentProcessor>(GrFragmentProcessor::MulOutputByInputAlpha(std::move(inner))); |
| } |
| |
| #endif |
| |
| /////////////////////////////////////////////////////////////////////////////////////////////////// |
| #include "SkImagePriv.h" |
| |
| sk_sp<SkShader> SkMakeBitmapShader(const SkBitmap& src, SkShader::TileMode tmx, |
| SkShader::TileMode tmy, const SkMatrix* localMatrix, |
| SkCopyPixelsMode cpm, SkTBlitterAllocator* allocator) { |
| // Until we learn otherwise, it seems that any caller that is passing an allocator must be |
| // assuming that the returned shader will have a stack-frame lifetime, so we assert that |
| // they are also asking for kNever_SkCopyPixelsMode. If that proves otherwise, we can remove |
| // or modify this assert. |
| SkASSERT(!allocator || (kNever_SkCopyPixelsMode == cpm)); |
| |
| return SkImageShader::Make(SkMakeImageFromRasterBitmap(src, cpm, allocator), |
| tmx, tmy, localMatrix, allocator); |
| } |
| |
| static sk_sp<SkFlattenable> SkBitmapProcShader_CreateProc(SkReadBuffer& buffer) { |
| SkMatrix lm; |
| buffer.readMatrix(&lm); |
| sk_sp<SkImage> image = buffer.readBitmapAsImage(); |
| SkShader::TileMode mx = (SkShader::TileMode)buffer.readUInt(); |
| SkShader::TileMode my = (SkShader::TileMode)buffer.readUInt(); |
| return image ? image->makeShader(mx, my, &lm) : nullptr; |
| } |
| |
| SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkShader) |
| SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkImageShader) |
| SkFlattenable::Register("SkBitmapProcShader", SkBitmapProcShader_CreateProc, kSkShader_Type); |
| SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END |
| |
| |
| bool SkImageShader::onAppendStages(SkRasterPipeline* p, SkColorSpace* dst, SkArenaAlloc* scratch, |
| const SkMatrix& ctm, const SkPaint& paint, |
| const SkMatrix* localM) const { |
| auto matrix = SkMatrix::Concat(ctm, this->getLocalMatrix()); |
| if (localM) { |
| matrix.preConcat(*localM); |
| } |
| |
| if (!matrix.invert(&matrix)) { |
| return false; |
| } |
| auto quality = paint.getFilterQuality(); |
| |
| SkBitmapProvider provider(fImage.get(), dst); |
| SkDefaultBitmapController controller(SkDefaultBitmapController::CanShadeHQ::kYes); |
| std::unique_ptr<SkBitmapController::State> state { |
| controller.requestBitmap(provider, matrix, quality) |
| }; |
| if (!state) { |
| return false; |
| } |
| |
| const SkPixmap& pm = state->pixmap(); |
| matrix = state->invMatrix(); |
| quality = state->quality(); |
| auto info = pm.info(); |
| |
| // When the matrix is just an integer translate, bilerp == nearest neighbor. |
| if (quality == kLow_SkFilterQuality && |
| matrix.getType() <= SkMatrix::kTranslate_Mask && |
| matrix.getTranslateX() == (int)matrix.getTranslateX() && |
| matrix.getTranslateY() == (int)matrix.getTranslateY()) { |
| quality = kNone_SkFilterQuality; |
| } |
| |
| // See skia:4649 and the GM image_scale_aligned. |
| if (quality == kNone_SkFilterQuality) { |
| if (matrix.getScaleX() >= 0) { |
| matrix.setTranslateX(nextafterf(matrix.getTranslateX(), |
| floorf(matrix.getTranslateX()))); |
| } |
| if (matrix.getScaleY() >= 0) { |
| matrix.setTranslateY(nextafterf(matrix.getTranslateY(), |
| floorf(matrix.getTranslateY()))); |
| } |
| } |
| |
| auto ctx = scratch->make<SkImageShaderContext>(); |
| ctx->state = std::move(state); // Extend lifetime to match the pipeline's. |
| ctx->pixels = pm.addr(); |
| ctx->ctable = pm.ctable(); |
| ctx->color4f = SkColor4f_from_SkColor(paint.getColor(), dst); |
| ctx->stride = pm.rowBytesAsPixels(); |
| ctx->width = (float)pm.width(); |
| ctx->height = (float)pm.height(); |
| if (matrix.asAffine(ctx->matrix)) { |
| p->append(SkRasterPipeline::matrix_2x3, ctx->matrix); |
| } else { |
| matrix.get9(ctx->matrix); |
| p->append(SkRasterPipeline::matrix_perspective, ctx->matrix); |
| } |
| |
| auto append_tiling_and_gather = [&] { |
| switch (fTileModeX) { |
| case kClamp_TileMode: p->append(SkRasterPipeline::clamp_x, &ctx->width); break; |
| case kMirror_TileMode: p->append(SkRasterPipeline::mirror_x, &ctx->width); break; |
| case kRepeat_TileMode: p->append(SkRasterPipeline::repeat_x, &ctx->width); break; |
| } |
| switch (fTileModeY) { |
| case kClamp_TileMode: p->append(SkRasterPipeline::clamp_y, &ctx->height); break; |
| case kMirror_TileMode: p->append(SkRasterPipeline::mirror_y, &ctx->height); break; |
| case kRepeat_TileMode: p->append(SkRasterPipeline::repeat_y, &ctx->height); break; |
| } |
| switch (info.colorType()) { |
| case kAlpha_8_SkColorType: p->append(SkRasterPipeline::gather_a8, ctx); break; |
| case kIndex_8_SkColorType: p->append(SkRasterPipeline::gather_i8, ctx); break; |
| case kGray_8_SkColorType: p->append(SkRasterPipeline::gather_g8, ctx); break; |
| case kRGB_565_SkColorType: p->append(SkRasterPipeline::gather_565, ctx); break; |
| case kARGB_4444_SkColorType: p->append(SkRasterPipeline::gather_4444, ctx); break; |
| case kRGBA_8888_SkColorType: |
| case kBGRA_8888_SkColorType: p->append(SkRasterPipeline::gather_8888, ctx); break; |
| case kRGBA_F16_SkColorType: p->append(SkRasterPipeline::gather_f16, ctx); break; |
| default: SkASSERT(false); |
| } |
| if (info.gammaCloseToSRGB() && dst != nullptr) { |
| p->append_from_srgb(info.alphaType()); |
| } |
| }; |
| |
| auto sample = [&](SkRasterPipeline::StockStage setup_x, |
| SkRasterPipeline::StockStage setup_y) { |
| p->append(setup_x, ctx); |
| p->append(setup_y, ctx); |
| append_tiling_and_gather(); |
| p->append(SkRasterPipeline::accumulate, ctx); |
| }; |
| |
| if (quality == kNone_SkFilterQuality) { |
| append_tiling_and_gather(); |
| } else if (quality == kLow_SkFilterQuality) { |
| p->append(SkRasterPipeline::save_xy, ctx); |
| |
| sample(SkRasterPipeline::bilinear_nx, SkRasterPipeline::bilinear_ny); |
| sample(SkRasterPipeline::bilinear_px, SkRasterPipeline::bilinear_ny); |
| sample(SkRasterPipeline::bilinear_nx, SkRasterPipeline::bilinear_py); |
| sample(SkRasterPipeline::bilinear_px, SkRasterPipeline::bilinear_py); |
| |
| p->append(SkRasterPipeline::move_dst_src); |
| } else { |
| p->append(SkRasterPipeline::save_xy, ctx); |
| |
| sample(SkRasterPipeline::bicubic_n3x, SkRasterPipeline::bicubic_n3y); |
| sample(SkRasterPipeline::bicubic_n1x, SkRasterPipeline::bicubic_n3y); |
| sample(SkRasterPipeline::bicubic_p1x, SkRasterPipeline::bicubic_n3y); |
| sample(SkRasterPipeline::bicubic_p3x, SkRasterPipeline::bicubic_n3y); |
| |
| sample(SkRasterPipeline::bicubic_n3x, SkRasterPipeline::bicubic_n1y); |
| sample(SkRasterPipeline::bicubic_n1x, SkRasterPipeline::bicubic_n1y); |
| sample(SkRasterPipeline::bicubic_p1x, SkRasterPipeline::bicubic_n1y); |
| sample(SkRasterPipeline::bicubic_p3x, SkRasterPipeline::bicubic_n1y); |
| |
| sample(SkRasterPipeline::bicubic_n3x, SkRasterPipeline::bicubic_p1y); |
| sample(SkRasterPipeline::bicubic_n1x, SkRasterPipeline::bicubic_p1y); |
| sample(SkRasterPipeline::bicubic_p1x, SkRasterPipeline::bicubic_p1y); |
| sample(SkRasterPipeline::bicubic_p3x, SkRasterPipeline::bicubic_p1y); |
| |
| sample(SkRasterPipeline::bicubic_n3x, SkRasterPipeline::bicubic_p3y); |
| sample(SkRasterPipeline::bicubic_n1x, SkRasterPipeline::bicubic_p3y); |
| sample(SkRasterPipeline::bicubic_p1x, SkRasterPipeline::bicubic_p3y); |
| sample(SkRasterPipeline::bicubic_p3x, SkRasterPipeline::bicubic_p3y); |
| |
| p->append(SkRasterPipeline::move_dst_src); |
| } |
| |
| auto effective_color_type = [](SkColorType ct) { |
| return ct == kIndex_8_SkColorType ? kN32_SkColorType : ct; |
| }; |
| |
| if (effective_color_type(info.colorType()) == kBGRA_8888_SkColorType) { |
| p->append(SkRasterPipeline::swap_rb); |
| } |
| if (info.colorType() == kAlpha_8_SkColorType) { |
| p->append(SkRasterPipeline::set_rgb, &ctx->color4f); |
| } |
| if (info.colorType() == kAlpha_8_SkColorType || info.alphaType() == kUnpremul_SkAlphaType) { |
| p->append(SkRasterPipeline::premul); |
| } |
| if (quality > kLow_SkFilterQuality) { |
| // Bicubic filtering naturally produces out of range values on both sides. |
| p->append(SkRasterPipeline::clamp_0); |
| p->append(SkRasterPipeline::clamp_a); |
| } |
| return append_gamut_transform(p, scratch, info.colorSpace(), dst); |
| } |