diff --git a/src/core/SkPicturePriv.h b/src/core/SkPicturePriv.h index abca1ce7c2..7c954ff655 100644 --- a/src/core/SkPicturePriv.h +++ b/src/core/SkPicturePriv.h @@ -89,10 +89,11 @@ public: kFilterOptionsInImageShader_Version = 77, kSerializeMipmaps_Version = 78, kCubicResamplerImageShader_Version = 79, + kSamplingInImageShader_Version = 80, // Only SKPs within the min/current picture version range (inclusive) can be read. kMin_Version = kEdgeAAQuadColor4f_Version, - kCurrent_Version = kCubicResamplerImageShader_Version + kCurrent_Version = kSamplingInImageShader_Version }; static_assert(SkPicturePriv::kMin_Version <= SkPicturePriv::kCubicResamplerImageShader_Version, diff --git a/src/core/SkPixmap.cpp b/src/core/SkPixmap.cpp index 290858ae7e..cf6ddba139 100644 --- a/src/core/SkPixmap.cpp +++ b/src/core/SkPixmap.cpp @@ -239,11 +239,12 @@ bool SkPixmap::scalePixels(const SkPixmap& actualDst, SkFilterQuality quality) c SkMatrix::kFill_ScaleToFit); // We'll create a shader to do this draw so we have control over the bicubic clamp. + SkSamplingOptions sampling = SkSamplingOptions::Make(quality); sk_sp shader = SkImageShader::Make(SkImage::MakeFromBitmap(bitmap), SkTileMode::kClamp, SkTileMode::kClamp, + &sampling, &scale, - (SkImageShader::FilterEnum)quality, clampAsIfUnpremul); sk_sp surface = SkSurface::MakeRasterDirect(dst.info(), diff --git a/src/image/SkImage.cpp b/src/image/SkImage.cpp index ba9b9c0cee..90ffa530ee 100644 --- a/src/image/SkImage.cpp +++ b/src/image/SkImage.cpp @@ -136,21 +136,23 @@ sk_sp SkImage::refColorSpace() const { return fInfo.refColorSpace( sk_sp SkImage::makeShader(SkTileMode tmx, SkTileMode tmy, const SkMatrix* localMatrix) const { - return SkImageShader::Make(sk_ref_sp(const_cast(this)), tmx, tmy, localMatrix, - SkImageShader::kInheritFromPaint); + const SkSamplingOptions* inherit_from_paint = nullptr; + return SkImageShader::Make(sk_ref_sp(const_cast(this)), tmx, tmy, inherit_from_paint, + localMatrix); } sk_sp SkImage::makeShader(SkTileMode tmx, SkTileMode tmy, - const SkSamplingOptions& options, + const SkSamplingOptions& sampling, const SkMatrix* localMatrix) const { return SkImageShader::Make(sk_ref_sp(const_cast(this)), tmx, tmy, - options, localMatrix); + &sampling, localMatrix); } sk_sp SkImage::makeShader(SkTileMode tmx, SkTileMode tmy, const SkMatrix* localMatrix, SkFilterQuality filtering) const { - return SkImageShader::Make(sk_ref_sp(const_cast(this)), tmx, tmy, localMatrix, - SkImageShader::FilterEnum(filtering)); + SkSamplingOptions sampling = SkSamplingOptions::Make(filtering); + return SkImageShader::Make(sk_ref_sp(const_cast(this)), tmx, tmy, &sampling, + localMatrix); } sk_sp SkImage::encodeToData(SkEncodedImageFormat type, int quality) const { diff --git a/src/shaders/SkImageShader.cpp b/src/shaders/SkImageShader.cpp index 8867e3819c..0c7565c961 100755 --- a/src/shaders/SkImageShader.cpp +++ b/src/shaders/SkImageShader.cpp @@ -63,45 +63,43 @@ static SkTileMode optimize(SkTileMode tm, int dimension) { SkImageShader::SkImageShader(sk_sp img, SkTileMode tmx, SkTileMode tmy, + const SkSamplingOptions* sampling, const SkMatrix* localMatrix, - FilterEnum filtering, bool clampAsIfUnpremul) : INHERITED(localMatrix) , fImage(std::move(img)) + , fSampling(sampling ? *sampling : SkSamplingOptions()) , fTileModeX(optimize(tmx, fImage->width())) , fTileModeY(optimize(tmy, fImage->height())) - , fFilterEnum(filtering) , fClampAsIfUnpremul(clampAsIfUnpremul) - , fFilterOptions({}) // ignored -{ - SkASSERT(filtering != kUseFilterOptions); -} - -SkImageShader::SkImageShader(sk_sp img, - SkTileMode tmx, SkTileMode tmy, - const SkSamplingOptions& options, - const SkMatrix* localMatrix) - : INHERITED(localMatrix) - , fImage(std::move(img)) - , fTileModeX(optimize(tmx, fImage->width())) - , fTileModeY(optimize(tmy, fImage->height())) - , fFilterEnum(options.fUseCubic ? FilterEnum::kUseCubicResampler - : FilterEnum::kUseFilterOptions) - , fClampAsIfUnpremul(false) - , fFilterOptions(options.fFilter) - , fCubic(options.fCubic) + , fUseSamplingOptions(sampling != nullptr) {} -// fClampAsIfUnpremul is always false when constructed through public APIs, -// so there's no need to read or write it here. +// just used for legacy-unflattening +enum class LegacyFilterEnum { + kNone, + kLow, + kMedium, + kHigh, + // this is the special value for backward compatibility + kInheritFromPaint, + // this signals we should use the new SkFilterOptions + kUseFilterOptions, + // use fCubic and ignore FilterOptions + kUseCubicResampler, + + kLast = kUseCubicResampler, +}; + +sk_sp SkImageShader::PreSamplingCreate(SkReadBuffer& buffer) { + SkASSERT(buffer.isVersionLT(SkPicturePriv::kSamplingInImageShader_Version)); -sk_sp SkImageShader::CreateProc(SkReadBuffer& buffer) { auto tmx = buffer.read32LE(SkTileMode::kLastTileMode); auto tmy = buffer.read32LE(SkTileMode::kLastTileMode); - FilterEnum fe = kInheritFromPaint; + LegacyFilterEnum fe = LegacyFilterEnum::kInheritFromPaint; if (!buffer.isVersionLT(SkPicturePriv::kFilterEnumInImageShader_Version)) { - fe = buffer.read32LE(kLast); + fe = buffer.read32LE(LegacyFilterEnum::kLast); } SkSamplingOptions op; @@ -114,12 +112,12 @@ sk_sp SkImageShader::CreateProc(SkReadBuffer& buffer) { } } else { switch (fe) { - case kUseFilterOptions: + case LegacyFilterEnum::kUseFilterOptions: op.fUseCubic = false; op.fFilter.fSampling = buffer.read32LE(SkSamplingMode::kLinear); op.fFilter.fMipmap = buffer.read32LE(SkMipmapMode::kLinear); break; - case kUseCubicResampler: + case LegacyFilterEnum::kUseCubicResampler: op.fUseCubic = true; op.fCubic.B = buffer.readScalar(); op.fCubic.C = buffer.readScalar(); @@ -137,31 +135,77 @@ sk_sp SkImageShader::CreateProc(SkReadBuffer& buffer) { } switch (fe) { - case kUseFilterOptions: - case kUseCubicResampler: - return SkImageShader::Make(std::move(img), tmx, tmy, op, &localMatrix); + case LegacyFilterEnum::kUseFilterOptions: + case LegacyFilterEnum::kUseCubicResampler: + return SkImageShader::Make(std::move(img), tmx, tmy, &op, &localMatrix); default: break; } - return SkImageShader::Make(std::move(img), tmx, tmy, &localMatrix, fe); + return SkImageShader::Make(std::move(img), tmx, tmy, nullptr, &localMatrix); +} + +static void write_sampling(SkWriteBuffer& buffer, SkSamplingOptions sampling) { + buffer.writeBool(sampling.fUseCubic); + if (sampling.fUseCubic) { + buffer.writeScalar(sampling.fCubic.B); + buffer.writeScalar(sampling.fCubic.C); + } else { + buffer.writeUInt((unsigned)sampling.fFilter.fSampling); + buffer.writeUInt((unsigned)sampling.fFilter.fMipmap); + } +} + +static SkSamplingOptions read_sampling(SkReadBuffer& buffer) { + SkSamplingOptions sampling; + sampling.fUseCubic = buffer.readBool(); + if (sampling.fUseCubic) { + sampling.fCubic.B = buffer.readScalar(); + sampling.fCubic.C = buffer.readScalar(); + } else { + sampling.fFilter.fSampling = buffer.read32LE(SkSamplingMode::kLinear); + sampling.fFilter.fMipmap = buffer.read32LE(SkMipmapMode::kLinear); + } + return sampling; +} + +// fClampAsIfUnpremul is always false when constructed through public APIs, +// so there's no need to read or write it here. + +sk_sp SkImageShader::CreateProc(SkReadBuffer& buffer) { + if (buffer.isVersionLT(SkPicturePriv::kSamplingInImageShader_Version)) { + return PreSamplingCreate(buffer); + } + + auto tmx = buffer.read32LE(SkTileMode::kLastTileMode); + auto tmy = buffer.read32LE(SkTileMode::kLastTileMode); + + SkSamplingOptions sampling, + *samplingPtr = nullptr; + + if (buffer.readBool()) { // fUseSamplingOptions + sampling = read_sampling(buffer); + samplingPtr = &sampling; + } + + SkMatrix localMatrix; + buffer.readMatrix(&localMatrix); + sk_sp img = buffer.readImage(); + if (!img) { + return nullptr; + } + + return SkImageShader::Make(std::move(img), tmx, tmy, samplingPtr, &localMatrix); } void SkImageShader::flatten(SkWriteBuffer& buffer) const { buffer.writeUInt((unsigned)fTileModeX); buffer.writeUInt((unsigned)fTileModeY); - buffer.writeUInt((unsigned)fFilterEnum); - switch (fFilterEnum) { - case kUseCubicResampler: - buffer.writeScalar(fCubic.B); - buffer.writeScalar(fCubic.C); - break; - case kUseFilterOptions: - buffer.writeUInt((unsigned)fFilterOptions.fSampling); - buffer.writeUInt((unsigned)fFilterOptions.fMipmap); - break; - default: - break; + + buffer.writeBool(fUseSamplingOptions); + if (fUseSamplingOptions) { + write_sampling(buffer, fSampling); } + buffer.writeMatrix(this->getLocalMatrix()); buffer.writeImage(fImage.get()); SkASSERT(fClampAsIfUnpremul == false); @@ -172,6 +216,43 @@ bool SkImageShader::isOpaque() const { fTileModeX != SkTileMode::kDecal && fTileModeY != SkTileMode::kDecal; } +constexpr SkCubicResampler kDefaultCubicResampler{1.0f/3, 1.0f/3}; + +static bool is_default_cubic_resampler(SkCubicResampler cubic) { + return SkScalarNearlyEqual(cubic.B, kDefaultCubicResampler.B) && + SkScalarNearlyEqual(cubic.C, kDefaultCubicResampler.C); +} + +static bool sampling_to_quality(SkSamplingOptions sampling, SkFilterQuality* quality) { + int q = -1; // not a legal quality enum + + if (sampling.fUseCubic) { + if (is_default_cubic_resampler(sampling.fCubic)) { + q = kHigh_SkFilterQuality; + } + } else { + switch (sampling.fFilter.fMipmap) { + case SkMipmapMode::kNone: + q = sampling.fFilter.fSampling == SkSamplingMode::kLinear ? + kLow_SkFilterQuality : + kNone_SkFilterQuality; + break; + case SkMipmapMode::kNearest: + if (sampling.fFilter.fSampling == SkSamplingMode::kLinear) { + q = kMedium_SkFilterQuality; + } + break; + case SkMipmapMode::kLinear: + break; + } + } + if (q >= 0) { + *quality = (SkFilterQuality)q; + return true; + } + return false; +} + #ifdef SK_ENABLE_LEGACY_SHADERCONTEXT static bool legacy_shader_can_handle(const SkMatrix& inv) { SkASSERT(!inv.hasPerspective()); @@ -199,16 +280,6 @@ static bool legacy_shader_can_handle(const SkMatrix& inv) { SkShaderBase::Context* SkImageShader::onMakeContext(const ContextRec& rec, SkArenaAlloc* alloc) const { - // we only support the old SkFilterQuality setting - if (fFilterEnum > kInheritFromPaint) { - return nullptr; - } - - auto quality = this->resolveFiltering(rec.fPaint->getFilterQuality()); - - if (quality == kHigh_SkFilterQuality) { - return nullptr; - } if (fImage->alphaType() == kUnpremul_SkAlphaType) { return nullptr; } @@ -222,6 +293,19 @@ SkShaderBase::Context* SkImageShader::onMakeContext(const ContextRec& rec, return nullptr; } + SkFilterQuality quality = rec.fPaint->getFilterQuality(); + if (fUseSamplingOptions) { + // we turn our sampling backwards into a quality (if possible) + // Note: if/when we can retool the legacy shader to explicitly take SkFilterOptions + // we can skip this funny step. + if (!sampling_to_quality(fSampling, &quality)) { + return nullptr; + } + } + if (quality == kHigh_SkFilterQuality) { + return nullptr; + } + // SkBitmapProcShader stores bitmap coordinates in a 16bit buffer, // so it can't handle bitmaps larger than 65535. // @@ -273,26 +357,14 @@ SkImage* SkImageShader::onIsAImage(SkMatrix* texM, SkTileMode xy[]) const { sk_sp SkImageShader::Make(sk_sp image, SkTileMode tmx, SkTileMode tmy, + const SkSamplingOptions* options, const SkMatrix* localMatrix, - FilterEnum filtering, bool clampAsIfUnpremul) { - if (!image) { - return sk_make_sp(); - } - return sk_sp{ - new SkImageShader(image, tmx, tmy, localMatrix, filtering, clampAsIfUnpremul) - }; -} - -sk_sp SkImageShader::Make(sk_sp image, - SkTileMode tmx, SkTileMode tmy, - const SkSamplingOptions& options, - const SkMatrix* localMatrix) { auto is_unit = [](float x) { return x >= 0 && x <= 1; }; - if (options.fUseCubic) { - if (!is_unit(options.fCubic.B) || !is_unit(options.fCubic.C)) { + if (options && options->fUseCubic) { + if (!is_unit(options->fCubic.B) || !is_unit(options->fCubic.C)) { return nullptr; } } @@ -300,7 +372,7 @@ sk_sp SkImageShader::Make(sk_sp image, return sk_make_sp(); } return sk_sp{ - new SkImageShader(image, tmx, tmy, options, localMatrix) + new SkImageShader(image, tmx, tmy, options, localMatrix, clampAsIfUnpremul) }; } @@ -373,40 +445,34 @@ std::unique_ptr SkImageShader::asFragmentProcessor( // quality setting. This completely ignores the complexity of the drawVertices case // where explicit local coords are provided by the caller. bool sharpen = args.fContext->priv().options().fSharpenMipmappedTextures; - GrSamplerState::Filter fm; - GrSamplerState::MipmapMode mm; + GrSamplerState::Filter fm = GrSamplerState::Filter::kNearest; + GrSamplerState::MipmapMode mm = GrSamplerState::MipmapMode::kNone; bool bicubic; SkCubicResampler kernel = GrBicubicEffect::gMitchell; - switch (fFilterEnum) { - case FilterEnum::kUseFilterOptions: - bicubic = false; - switch (fFilterOptions.fSampling) { + if (fUseSamplingOptions) { + bicubic = fSampling.fUseCubic; + if (bicubic) { + kernel = fSampling.fCubic; + } else { + switch (fSampling.fFilter.fSampling) { case SkSamplingMode::kNearest: fm = GrSamplerState::Filter::kNearest; break; case SkSamplingMode::kLinear : fm = GrSamplerState::Filter::kLinear ; break; } - switch (fFilterOptions.fMipmap) { + switch (fSampling.fFilter.fMipmap) { case SkMipmapMode::kNone : mm = GrSamplerState::MipmapMode::kNone ; break; case SkMipmapMode::kNearest: mm = GrSamplerState::MipmapMode::kNearest; break; case SkMipmapMode::kLinear : mm = GrSamplerState::MipmapMode::kLinear ; break; } - break; - case FilterEnum::kUseCubicResampler: - bicubic = true; - kernel = fCubic; - fm = GrSamplerState::Filter::kNearest; - mm = GrSamplerState::MipmapMode::kNone; - break; - case FilterEnum::kInheritFromPaint: - default: // none, low, medium, high - std::tie(fm, mm, bicubic) = - GrInterpretFilterQuality(fImage->dimensions(), - this->resolveFiltering(args.fFilterQuality), - args.fMatrixProvider.localToDevice(), - *lm, - sharpen, - args.fAllowFilterQualityReduction); - break; + } + } else { // inherit filterquality from paint + std::tie(fm, mm, bicubic) = + GrInterpretFilterQuality(fImage->dimensions(), + args.fFilterQuality, + args.fMatrixProvider.localToDevice(), + *lm, + sharpen, + args.fAllowFilterQualityReduction); } std::unique_ptr fp; if (bicubic) { @@ -436,8 +502,9 @@ std::unique_ptr SkImageShader::asFragmentProcessor( sk_sp SkMakeBitmapShader(const SkBitmap& src, SkTileMode tmx, SkTileMode tmy, const SkMatrix* localMatrix, SkCopyPixelsMode cpm) { + const SkSamplingOptions* inherit_from_paint = nullptr; return SkImageShader::Make(SkMakeImageFromRasterBitmap(src, cpm), - tmx, tmy, localMatrix, SkImageShader::kInheritFromPaint); + tmx, tmy, inherit_from_paint, localMatrix); } sk_sp SkMakeBitmapShaderForPaint(const SkPaint& paint, const SkBitmap& src, @@ -533,17 +600,11 @@ static void tweak_quality_and_inv_matrix(SkFilterQuality* quality, SkMatrix* mat } bool SkImageShader::doStages(const SkStageRec& rec, SkImageStageUpdater* updater) const { - SkFilterQuality quality; - switch (fFilterEnum) { - case FilterEnum::kUseFilterOptions: - case FilterEnum::kUseCubicResampler: - return false; // TODO: support these in stages - case FilterEnum::kInheritFromPaint: - quality = rec.fPaint.getFilterQuality(); - break; - default: - quality = (SkFilterQuality)fFilterEnum; - break; + SkFilterQuality quality = rec.fPaint.getFilterQuality(); + if (fUseSamplingOptions) { + if (!sampling_to_quality(fSampling, &quality)) { + return false; // TODO: support samplingoptions in stages? + } } if (updater && quality == kMedium_SkFilterQuality) { @@ -840,11 +901,12 @@ skvm::Color SkImageShader::onProgram(skvm::Builder* p, } baseInv.normalizePerspective(); + SkCubicResampler cubic = kDefaultCubicResampler; const SkPixmap *upper = nullptr, *lower = nullptr; SkMatrix upperInv; float lowerWeight = 0; - SamplingEnum sampling = (SamplingEnum)fFilterOptions.fSampling; + SamplingEnum sampling = (SamplingEnum)fSampling.fFilter.fSampling; auto post_scale = [&](SkISize level, const SkMatrix& base) { return SkMatrix::Scale(SkIntToScalar(level.width()) / fImage->width(), @@ -852,30 +914,30 @@ skvm::Color SkImageShader::onProgram(skvm::Builder* p, * base; }; - if (fFilterEnum == kUseFilterOptions) { - auto* access = alloc->make(as_IB(fImage.get()), baseInv, - fFilterOptions.fMipmap); - upper = &access->level(); - upperInv = post_scale(upper->dimensions(), baseInv); - lowerWeight = access->lowerWeight(); - if (lowerWeight > 0) { - lower = &access->lowerLevel(); + if (fUseSamplingOptions) { + if (fSampling.fUseCubic) { + auto* access = alloc->make(as_IB(fImage.get()), baseInv, + SkMipmapMode::kNone); + upper = &access->level(); + upperInv = post_scale(upper->dimensions(), baseInv); + sampling = SamplingEnum::kBicubic; + cubic = fSampling.fCubic; + } else { + auto* access = alloc->make(as_IB(fImage.get()), baseInv, + fSampling.fFilter.fMipmap); + upper = &access->level(); + upperInv = post_scale(upper->dimensions(), baseInv); + lowerWeight = access->lowerWeight(); + if (lowerWeight > 0) { + lower = &access->lowerLevel(); + } } - } else if (fFilterEnum == kUseCubicResampler){ - auto* access = alloc->make(as_IB(fImage.get()), baseInv, - SkMipmapMode::kNone); - upper = &access->level(); - upperInv = post_scale(upper->dimensions(), baseInv); - sampling = SamplingEnum::kBicubic; } else { // Convert from the filter-quality enum to our working description: // sampling : nearest, bilerp, bicubic // miplevel(s) and associated matrices // SkFilterQuality quality = paintQuality; - if (fFilterEnum != kInheritFromPaint) { - quality = (SkFilterQuality)fFilterEnum; - } // We use RequestBitmap() to make sure our SkBitmapController::State lives in the alloc. // This lets the SkVMBlitter hang on to this state and keep our image alive. @@ -1048,7 +1110,7 @@ skvm::Color SkImageShader::onProgram(skvm::Builder* p, skvm::F32 wx[4], wy[4]; - SkM44 weights = CubicResamplerMatrix(fCubic.B, fCubic.C); + SkM44 weights = CubicResamplerMatrix(cubic.B, cubic.C); auto dot = [](const skvm::F32 a[], const skvm::F32 b[]) { return a[0]*b[0] + a[1]*b[1] + a[2]*b[2] + a[3]*b[3]; diff --git a/src/shaders/SkImageShader.h b/src/shaders/SkImageShader.h index 8a89188cee..fe4ab232a6 100644 --- a/src/shaders/SkImageShader.h +++ b/src/shaders/SkImageShader.h @@ -17,34 +17,13 @@ class SkImageStageUpdater; class SkImageShader : public SkShaderBase { public: - enum FilterEnum { // first 4 entries match SkFilterQuality - kNone, - kLow, - kMedium, - kHigh, - // this is the special value for backward compatibility - kInheritFromPaint, - // this signals we should use the new SkFilterOptions - kUseFilterOptions, - // use fCubic and ignore FilterOptions - kUseCubicResampler, - - kLast = kUseCubicResampler, - }; - static sk_sp Make(sk_sp, SkTileMode tmx, SkTileMode tmy, + const SkSamplingOptions*, // null means inherit-from-paint-fq const SkMatrix* localMatrix, - FilterEnum, bool clampAsIfUnpremul = false); - static sk_sp Make(sk_sp, - SkTileMode tmx, - SkTileMode tmy, - const SkSamplingOptions&, - const SkMatrix* localMatrix); - bool isOpaque() const override; #if SK_SUPPORT_GPU @@ -59,14 +38,9 @@ private: SkImageShader(sk_sp, SkTileMode tmx, SkTileMode tmy, + const SkSamplingOptions*, const SkMatrix* localMatrix, - FilterEnum, - bool clampAsIfUnpremul); - SkImageShader(sk_sp, - SkTileMode tmx, - SkTileMode tmy, - const SkSamplingOptions&, - const SkMatrix* localMatrix); + bool clampAsIfUnpremul = false); void flatten(SkWriteBuffer&) const override; #ifdef SK_ENABLE_LEGACY_SHADERCONTEXT @@ -84,29 +58,18 @@ private: bool doStages(const SkStageRec&, SkImageStageUpdater* = nullptr) const; - SkFilterQuality resolveFiltering(SkFilterQuality paintQuality) const { - switch (fFilterEnum) { - case kUseCubicResampler: return kHigh_SkFilterQuality; // TODO: handle explicitly - case kUseFilterOptions: return kNone_SkFilterQuality; // TODO: handle explicitly - case kInheritFromPaint: return paintQuality; - default: break; - } - return (SkFilterQuality)fFilterEnum; - } - - sk_sp fImage; - const SkTileMode fTileModeX; - const SkTileMode fTileModeY; - const FilterEnum fFilterEnum; - const bool fClampAsIfUnpremul; - - // only use this if fFilterEnum == kUseFilterOptions - SkFilterOptions fFilterOptions; - // only use this if fFilterEnum == kUseCubicResampler or kHigh - SkCubicResampler fCubic = {1/3.0f, 1/3.0f}; // Default to Mitchell-Netravali. + sk_sp fImage; + const SkSamplingOptions fSampling; + const SkTileMode fTileModeX; + const SkTileMode fTileModeY; + const bool fClampAsIfUnpremul; + const bool fUseSamplingOptions; // else inherit filterquality from paint friend class SkShaderBase; using INHERITED = SkShaderBase; + + // for legacy unflattening + static sk_sp PreSamplingCreate(SkReadBuffer&); }; #endif