diff options
Diffstat (limited to 'src/shaders/SkImageShader.cpp')
-rw-r--r-- | src/shaders/SkImageShader.cpp | 391 |
1 files changed, 391 insertions, 0 deletions
diff --git a/src/shaders/SkImageShader.cpp b/src/shaders/SkImageShader.cpp new file mode 100644 index 0000000000..751300e951 --- /dev/null +++ b/src/shaders/SkImageShader.cpp @@ -0,0 +1,391 @@ +/* + * 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 "SkPM4fPriv.h" +#include "SkReadBuffer.h" +#include "SkWriteBuffer.h" +#include "../jumper/SkJumper.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(); +} + +SkShaderBase::Context* SkImageShader::onMakeContext(const ContextRec& rec, + SkArenaAlloc* alloc) const { + return SkBitmapProcLegacyShader::MakeContext(*this, fTileModeX, fTileModeY, + SkBitmapProvider(fImage.get(), rec.fDstColorSpace), + rec, alloc); +} + +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) { + if (!image || bitmap_is_too_big(image->width(), image->height())) { + return sk_make_sp<SkEmptyShader>(); + } else { + return sk_make_sp<SkImageShader>(image, tx, ty, localMatrix); + } +} + +#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 "GrContext.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<GrTextureProxy> proxy(as_IB(fImage)->asTextureProxyRef(args.fContext, params, + args.fDstColorSpace, + &texColorSpace, scaleAdjust)); + if (!proxy) { + return nullptr; + } + + bool isAlphaOnly = GrPixelConfigIsAlphaOnly(proxy->config()); + + 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(args.fContext->resourceProvider(), std::move(proxy), + std::move(colorSpaceXform), lmInverse, tm); + } else { + inner = GrSimpleTextureEffect::Make(args.fContext->resourceProvider(), std::move(proxy), + std::move(colorSpaceXform), lmInverse, params); + } + + if (isAlphaOnly) { + 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) { + return SkImageShader::Make(SkMakeImageFromRasterBitmap(src, cpm), + tmx, tmy, localMatrix); +} + +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(SkShaderBase) +SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkImageShader) +SkFlattenable::Register("SkBitmapProcShader", SkBitmapProcShader_CreateProc, kSkShaderBase_Type); +SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END + + +bool SkImageShader::onAppendStages(SkRasterPipeline* p, SkColorSpace* dstCS, SkArenaAlloc* alloc, + 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(), dstCS); + 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()))); + } + } + + + struct MiscCtx { + std::unique_ptr<SkBitmapController::State> state; + SkColor4f paint_color; + float matrix[9]; + }; + auto misc = alloc->make<MiscCtx>(); + misc->state = std::move(state); // Extend lifetime to match the pipeline's. + misc->paint_color = SkColor4f_from_SkColor(paint.getColor(), dstCS); + if (matrix.asAffine(misc->matrix)) { + p->append(SkRasterPipeline::matrix_2x3, misc->matrix); + } else { + matrix.get9(misc->matrix); + p->append(SkRasterPipeline::matrix_perspective, misc->matrix); + } + + auto gather = alloc->make<SkJumper_GatherCtx>(); + gather->pixels = pm.addr(); + gather->ctable = pm.ctable() ? pm.ctable()->readColors() : nullptr; + gather->stride = pm.rowBytesAsPixels(); + + // Tiling stages (clamp_x, mirror_y, etc.) are inclusive of their limit, + // so we tick down our width and height by one float to make them exclusive. + auto ulp_before = [](float f) { + uint32_t bits; + memcpy(&bits, &f, 4); + bits--; + memcpy(&f, &bits, 4); + return f; + }; + auto limit_x = alloc->make<float>(ulp_before((float)pm. width())), + limit_y = alloc->make<float>(ulp_before((float)pm.height())); + + auto append_tiling_and_gather = [&] { + switch (fTileModeX) { + case kClamp_TileMode: p->append(SkRasterPipeline::clamp_x, limit_x); break; + case kMirror_TileMode: p->append(SkRasterPipeline::mirror_x, limit_x); break; + case kRepeat_TileMode: p->append(SkRasterPipeline::repeat_x, limit_x); break; + } + switch (fTileModeY) { + case kClamp_TileMode: p->append(SkRasterPipeline::clamp_y, limit_y); break; + case kMirror_TileMode: p->append(SkRasterPipeline::mirror_y, limit_y); break; + case kRepeat_TileMode: p->append(SkRasterPipeline::repeat_y, limit_y); break; + } + switch (info.colorType()) { + case kAlpha_8_SkColorType: p->append(SkRasterPipeline::gather_a8, gather); break; + case kIndex_8_SkColorType: p->append(SkRasterPipeline::gather_i8, gather); break; + case kGray_8_SkColorType: p->append(SkRasterPipeline::gather_g8, gather); break; + case kRGB_565_SkColorType: p->append(SkRasterPipeline::gather_565, gather); break; + case kARGB_4444_SkColorType: p->append(SkRasterPipeline::gather_4444, gather); break; + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: p->append(SkRasterPipeline::gather_8888, gather); break; + case kRGBA_F16_SkColorType: p->append(SkRasterPipeline::gather_f16, gather); break; + default: SkASSERT(false); + } + if (info.gammaCloseToSRGB() && dstCS != nullptr) { + p->append_from_srgb(info.alphaType()); + } + }; + + SkJumper_SamplerCtx* sampler = nullptr; + if (quality != kNone_SkFilterQuality) { + sampler = alloc->make<SkJumper_SamplerCtx>(); + } + + auto sample = [&](SkRasterPipeline::StockStage setup_x, + SkRasterPipeline::StockStage setup_y) { + p->append(setup_x, sampler); + p->append(setup_y, sampler); + append_tiling_and_gather(); + p->append(SkRasterPipeline::accumulate, sampler); + }; + + if (quality == kNone_SkFilterQuality) { + append_tiling_and_gather(); + } else if (quality == kLow_SkFilterQuality) { + p->append(SkRasterPipeline::save_xy, sampler); + + 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, sampler); + + 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, &misc->paint_color); + } + 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); + } + append_gamut_transform(p, alloc, info.colorSpace(), dstCS, kPremul_SkAlphaType); + return true; +} |