diff --git a/src/core/SkBitmapController.cpp b/src/core/SkBitmapController.cpp index 75c500d80e..6a44a36645 100644 --- a/src/core/SkBitmapController.cpp +++ b/src/core/SkBitmapController.cpp @@ -40,11 +40,15 @@ SkBitmapController::State* SkBitmapController::requestBitmap(const SkBitmapProvi class SkDefaultBitmapControllerState : public SkBitmapController::State { public: - SkDefaultBitmapControllerState(const SkBitmapProvider&, const SkMatrix& inv, SkFilterQuality); + SkDefaultBitmapControllerState(const SkBitmapProvider&, + const SkMatrix& inv, + SkFilterQuality, + bool canShadeHQ); private: SkBitmap fResultBitmap; sk_sp fCurrMip; + bool fCanShadeHQ; bool processExternalRequest(const SkBitmapProvider&); bool processHQRequest(const SkBitmapProvider&); @@ -128,6 +132,14 @@ bool SkDefaultBitmapControllerState::processHQRequest(const SkBitmapProvider& pr return false; // only use HQ when upsampling } + // If the shader can natively handle HQ filtering, let it do it. + if (fCanShadeHQ) { + fQuality = kHigh_SkFilterQuality; + SkAssertResult(provider.asBitmap(&fResultBitmap)); + fResultBitmap.lockPixels(); + return true; + } + const int dstW = SkScalarRoundToScalar(provider.width() / invScaleX); const int dstH = SkScalarRoundToScalar(provider.height() / invScaleY); const SkBitmapCacheDesc desc = provider.makeCacheDesc(dstW, dstH); @@ -222,9 +234,11 @@ bool SkDefaultBitmapControllerState::processMediumRequest(const SkBitmapProvider SkDefaultBitmapControllerState::SkDefaultBitmapControllerState(const SkBitmapProvider& provider, const SkMatrix& inv, - SkFilterQuality qual) { + SkFilterQuality qual, + bool canShadeHQ) { fInvMatrix = inv; fQuality = qual; + fCanShadeHQ = canShadeHQ; bool processed = this->processExternalRequest(provider); @@ -239,7 +253,7 @@ SkDefaultBitmapControllerState::SkDefaultBitmapControllerState(const SkBitmapPro fResultBitmap.lockPixels(); // lock may fail to give us pixels } - SkASSERT(fQuality <= kLow_SkFilterQuality); + SkASSERT(fCanShadeHQ || fQuality <= kLow_SkFilterQuality); // fResultBitmap.getPixels() may be null, but our caller knows to check fPixmap.addr() // and will destroy us if it is nullptr. @@ -251,5 +265,6 @@ SkBitmapController::State* SkDefaultBitmapController::onRequestBitmap(const SkBi const SkMatrix& inverse, SkFilterQuality quality, void* storage, size_t size) { - return SkInPlaceNewCheck(storage, size, bm, inverse, quality); + return SkInPlaceNewCheck(storage, size, + bm, inverse, quality, fCanShadeHQ); } diff --git a/src/core/SkBitmapController.h b/src/core/SkBitmapController.h index 9eff2d28ff..72fc721c53 100644 --- a/src/core/SkBitmapController.h +++ b/src/core/SkBitmapController.h @@ -57,11 +57,14 @@ protected: class SkDefaultBitmapController : public SkBitmapController { public: - SkDefaultBitmapController() {} + enum class CanShadeHQ { kNo, kYes }; + SkDefaultBitmapController(CanShadeHQ canShadeHQ) + : fCanShadeHQ(canShadeHQ == CanShadeHQ::kYes) {} protected: State* onRequestBitmap(const SkBitmapProvider&, const SkMatrix& inverse, SkFilterQuality, void* storage, size_t storageSize) override; + bool fCanShadeHQ; }; #endif diff --git a/src/core/SkBitmapProcState.cpp b/src/core/SkBitmapProcState.cpp index 3c4f414c23..684555e052 100644 --- a/src/core/SkBitmapProcState.cpp +++ b/src/core/SkBitmapProcState.cpp @@ -109,7 +109,7 @@ bool SkBitmapProcInfo::init(const SkMatrix& inv, const SkPaint& paint) { allow_ignore_fractional_translate = false; } - SkDefaultBitmapController controller; + SkDefaultBitmapController controller(SkDefaultBitmapController::CanShadeHQ::kNo); fBMState = controller.requestBitmap(fProvider, inv, paint.getFilterQuality(), fBMStateStorage.get(), fBMStateStorage.size()); // Note : we allow the controller to return an empty (zero-dimension) result. Should we? diff --git a/src/core/SkRasterPipeline.h b/src/core/SkRasterPipeline.h index 3ecf80f79b..b3f513d69e 100644 --- a/src/core/SkRasterPipeline.h +++ b/src/core/SkRasterPipeline.h @@ -86,7 +86,9 @@ M(clamp_y) M(mirror_y) M(repeat_y) \ M(gather_a8) M(gather_g8) M(gather_i8) \ M(gather_565) M(gather_4444) M(gather_8888) M(gather_f16) \ - M(bilinear_nn) M(bilinear_pn) M(bilinear_np) M(bilinear_pp) \ + M(bilinear_nx) M(bilinear_px) M(bilinear_ny) M(bilinear_py) \ + M(bicubic_n3x) M(bicubic_n1x) M(bicubic_p1x) M(bicubic_p3x) \ + M(bicubic_n3y) M(bicubic_n1y) M(bicubic_p1y) M(bicubic_p3y) \ M(save_xy) M(accumulate) class SkRasterPipeline { diff --git a/src/image/SkImageShader.cpp b/src/image/SkImageShader.cpp index 528c5729d8..7a1522b99a 100644 --- a/src/image/SkImageShader.cpp +++ b/src/image/SkImageShader.cpp @@ -279,7 +279,7 @@ bool SkImageShader::onAppendStages(SkRasterPipeline* p, SkColorSpace* dst, SkFal auto quality = paint.getFilterQuality(); SkBitmapProvider provider(fImage.get(), dst); - SkDefaultBitmapController controller; + SkDefaultBitmapController controller(SkDefaultBitmapController::CanShadeHQ::kYes); std::unique_ptr state { controller.requestBitmap(provider, matrix, quality) }; @@ -293,7 +293,8 @@ bool SkImageShader::onAppendStages(SkRasterPipeline* p, SkColorSpace* dst, SkFal auto info = pm.info(); // When the matrix is just an integer translate, bilerp == nearest neighbor. - if (matrix.getType() <= SkMatrix::kTranslate_Mask && + if (quality == kLow_SkFilterQuality && + matrix.getType() <= SkMatrix::kTranslate_Mask && matrix.getTranslateX() == (int)matrix.getTranslateX() && matrix.getTranslateY() == (int)matrix.getTranslateY()) { quality = kNone_SkFilterQuality; @@ -353,20 +354,48 @@ bool SkImageShader::onAppendStages(SkRasterPipeline* p, SkColorSpace* dst, SkFal } }; - auto sample = [&](SkRasterPipeline::StockStage sampler) { - p->append(sampler, ctx); + 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::bilinear_nn); - sample(SkRasterPipeline::bilinear_np); - sample(SkRasterPipeline::bilinear_pn); - sample(SkRasterPipeline::bilinear_pp); + + 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); } @@ -383,5 +412,10 @@ bool SkImageShader::onAppendStages(SkRasterPipeline* p, SkColorSpace* dst, SkFal 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); } diff --git a/src/image/SkImageShaderContext.h b/src/image/SkImageShaderContext.h index b0652900dd..3dae6c5da7 100644 --- a/src/image/SkImageShaderContext.h +++ b/src/image/SkImageShaderContext.h @@ -30,7 +30,8 @@ struct SkImageShaderContext { float y[8]; float fx[8]; float fy[8]; - float scale[8]; + float scalex[8]; + float scaley[8]; }; #endif//SkImageShaderContext_DEFINED diff --git a/src/opts/SkRasterPipeline_opts.h b/src/opts/SkRasterPipeline_opts.h index 1359cba10b..e61e6cb847 100644 --- a/src/opts/SkRasterPipeline_opts.h +++ b/src/opts/SkRasterPipeline_opts.h @@ -809,42 +809,104 @@ STAGE(save_xy) { r.store(sc->x); g.store(sc->y); + // Whether bilinear or bicubic, all sample points have the same fractional offset (fx,fy). + // They're either the 4 corners of a logical 1x1 pixel or the 16 corners of a 3x3 grid + // surrounding (x,y), all (0.5,0.5) off-center. auto fract = [](const SkNf& v) { return v - v.floor(); }; fract(r + 0.5f).store(sc->fx); fract(g + 0.5f).store(sc->fy); } -template -SI void bilinear(void* ctx, SkNf* x, SkNf* y) { - auto sc = (SkImageShaderContext*)ctx; - - // Bilinear interpolation considers the 4 physical pixels at - // each corner of a logical pixel centered at (sc->x, sc->y). - *x = SkNf::Load(sc->x) + X*0.5f; - *y = SkNf::Load(sc->y) + Y*0.5f; - - // Each corner pixel contributes color in direct proportion to its overlap. - auto fx = SkNf::Load(sc->fx), - fy = SkNf::Load(sc->fy); - auto overlap = (X > 0 ? fx : (1.0f - fx)) - * (Y > 0 ? fy : (1.0f - fy)); - overlap.store(sc->scale); -} -STAGE(bilinear_nn) { bilinear<-1,-1>(ctx, &r, &g); } -STAGE(bilinear_pn) { bilinear<+1,-1>(ctx, &r, &g); } -STAGE(bilinear_np) { bilinear<-1,+1>(ctx, &r, &g); } -STAGE(bilinear_pp) { bilinear<+1,+1>(ctx, &r, &g); } - STAGE(accumulate) { auto sc = (const SkImageShaderContext*)ctx; - auto scale = SkNf::Load(sc->scale); + // Bilinear and bicubic filtering are both separable, so we'll end up with independent + // scale contributions in x and y that we multiply together to get each pixel's scale factor. + auto scale = SkNf::Load(sc->scalex) * SkNf::Load(sc->scaley); dr = SkNf_fma(scale, r, dr); dg = SkNf_fma(scale, g, dg); db = SkNf_fma(scale, b, db); da = SkNf_fma(scale, a, da); } +// In bilinear interpolation, the 4 pixels at +/- 0.5 offsets from the sample pixel center +// are combined in direct proportion to their area overlapping that logical query pixel. +// At positive offsets, the x-axis contribution to that rectangular area is fx; (1-fx) +// at negative x offsets. The y-axis is treated symmetrically. +template +SI void bilinear_x(void* ctx, SkNf* x) { + auto sc = (SkImageShaderContext*)ctx; + + *x = SkNf::Load(sc->x) + Scale*0.5f; + auto fx = SkNf::Load(sc->fx); + (Scale > 0 ? fx : (1.0f - fx)).store(sc->scalex); +} +template +SI void bilinear_y(void* ctx, SkNf* y) { + auto sc = (SkImageShaderContext*)ctx; + + *y = SkNf::Load(sc->y) + Scale*0.5f; + auto fy = SkNf::Load(sc->fy); + (Scale > 0 ? fy : (1.0f - fy)).store(sc->scaley); +} +STAGE(bilinear_nx) { bilinear_x<-1>(ctx, &r); } +STAGE(bilinear_px) { bilinear_x<+1>(ctx, &r); } +STAGE(bilinear_ny) { bilinear_y<-1>(ctx, &g); } +STAGE(bilinear_py) { bilinear_y<+1>(ctx, &g); } + + +// In bilinear interpolation, the 16 pixels at +/- 0.5 and +/- 1.5 offsets from the sample +// pixel center are combined with a non-uniform cubic filter, with high filter values near +// the center and lower values farther away. +// +// We break this filter function into two parts, one for near +/- 0.5 offsets, +// and one for far +/- 1.5 offsets. +// +// See GrBicubicEffect for details about this particular Mitchell-Netravali filter. +SI SkNf bicubic_near(const SkNf& t) { + // 1/18 + 9/18t + 27/18t^2 - 21/18t^3 == t ( t ( -21/18t + 27/18) + 9/18) + 1/18 + return SkNf_fma(t, SkNf_fma(t, SkNf_fma(-21/18.0f, t, 27/18.0f), 9/18.0f), 1/18.0f); +} +SI SkNf bicubic_far(const SkNf& t) { + // 0/18 + 0/18*t - 6/18t^2 + 7/18t^3 == t^2 (7/18t - 6/18) + return (t*t)*SkNf_fma(7/18.0f, t, -6/18.0f); +} + +template +SI void bicubic_x(void* ctx, SkNf* x) { + auto sc = (SkImageShaderContext*)ctx; + + *x = SkNf::Load(sc->x) + Scale*0.5f; + auto fx = SkNf::Load(sc->fx); + if (Scale == -3) { return bicubic_far (1.0f - fx).store(sc->scalex); } + if (Scale == -1) { return bicubic_near(1.0f - fx).store(sc->scalex); } + if (Scale == +1) { return bicubic_near( fx).store(sc->scalex); } + if (Scale == +3) { return bicubic_far ( fx).store(sc->scalex); } + SkDEBUGFAIL("unreachable"); +} +template +SI void bicubic_y(void* ctx, SkNf* y) { + auto sc = (SkImageShaderContext*)ctx; + + *y = SkNf::Load(sc->y) + Scale*0.5f; + auto fy = SkNf::Load(sc->fy); + if (Scale == -3) { return bicubic_far (1.0f - fy).store(sc->scaley); } + if (Scale == -1) { return bicubic_near(1.0f - fy).store(sc->scaley); } + if (Scale == +1) { return bicubic_near( fy).store(sc->scaley); } + if (Scale == +3) { return bicubic_far ( fy).store(sc->scaley); } + SkDEBUGFAIL("unreachable"); +} +STAGE(bicubic_n3x) { bicubic_x<-3>(ctx, &r); } +STAGE(bicubic_n1x) { bicubic_x<-1>(ctx, &r); } +STAGE(bicubic_p1x) { bicubic_x<+1>(ctx, &r); } +STAGE(bicubic_p3x) { bicubic_x<+3>(ctx, &r); } + +STAGE(bicubic_n3y) { bicubic_y<-3>(ctx, &g); } +STAGE(bicubic_n1y) { bicubic_y<-1>(ctx, &g); } +STAGE(bicubic_p1y) { bicubic_y<+1>(ctx, &g); } +STAGE(bicubic_p3y) { bicubic_y<+3>(ctx, &g); } + + template SI SkNi offset_and_ptr(T** ptr, const void* ctx, const SkNf& x, const SkNf& y) { auto sc = (const SkImageShaderContext*)ctx;