Esempio n. 1
0
static void convolve_gaussian_2d(GrDrawContext* drawContext,
                                 const GrClip& clip,
                                 const SkIRect& dstRect,
                                 const SkIPoint& srcOffset,
                                 GrTexture* texture,
                                 int radiusX,
                                 int radiusY,
                                 SkScalar sigmaX,
                                 SkScalar sigmaY,
                                 const SkIRect* srcBounds) {
    SkMatrix localMatrix = SkMatrix::MakeTrans(-SkIntToScalar(srcOffset.x()),
                                               -SkIntToScalar(srcOffset.y()));
    SkISize size = SkISize::Make(2 * radiusX + 1,  2 * radiusY + 1);
    SkIPoint kernelOffset = SkIPoint::Make(radiusX, radiusY);
    GrPaint paint;
    paint.setGammaCorrect(drawContext->isGammaCorrect());
    SkIRect bounds = srcBounds ? *srcBounds : SkIRect::EmptyIRect();

    sk_sp<GrFragmentProcessor> conv(GrMatrixConvolutionEffect::MakeGaussian(
            texture, bounds, size, 1.0, 0.0, kernelOffset,
            srcBounds ? GrTextureDomain::kDecal_Mode : GrTextureDomain::kIgnore_Mode,
            true, sigmaX, sigmaY));
    paint.addColorFragmentProcessor(std::move(conv));
    paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
    drawContext->fillRectWithLocalMatrix(clip, paint, SkMatrix::I(), 
                                         SkRect::Make(dstRect), localMatrix);
}
Esempio n. 2
0
sk_sp<GrTextureProxy> GrTextureProducer::CopyOnGpu(GrContext* context,
                                                   sk_sp<GrTextureProxy> inputProxy,
                                                   const CopyParams& copyParams,
                                                   bool dstWillRequireMipMaps) {
    SkASSERT(context);

    const SkRect dstRect = SkRect::MakeIWH(copyParams.fWidth, copyParams.fHeight);
    GrMipMapped mipMapped = dstWillRequireMipMaps ? GrMipMapped::kYes : GrMipMapped::kNo;

    sk_sp<SkColorSpace> colorSpace;
    if (GrPixelConfigIsSRGB(inputProxy->config())) {
        colorSpace = SkColorSpace::MakeSRGB();
    }
    sk_sp<GrRenderTargetContext> copyRTC =
        context->contextPriv().makeDeferredRenderTargetContextWithFallback(
            SkBackingFit::kExact, dstRect.width(), dstRect.height(), inputProxy->config(),
            std::move(colorSpace), 1, mipMapped, inputProxy->origin());
    if (!copyRTC) {
        return nullptr;
    }

    GrPaint paint;
    paint.setGammaCorrect(true);

    SkRect localRect = SkRect::MakeWH(inputProxy->width(), inputProxy->height());

    bool needsDomain = false;
    if (copyParams.fFilter != GrSamplerState::Filter::kNearest) {
        bool resizing = localRect.width()  != dstRect.width() ||
                        localRect.height() != dstRect.height();
        needsDomain = resizing && !GrProxyProvider::IsFunctionallyExact(inputProxy.get());
    }

    if (needsDomain) {
        const SkRect domain = localRect.makeInset(0.5f, 0.5f);
        // This would cause us to read values from outside the subset. Surely, the caller knows
        // better!
        SkASSERT(copyParams.fFilter != GrSamplerState::Filter::kMipMap);
        paint.addColorFragmentProcessor(
            GrTextureDomainEffect::Make(std::move(inputProxy), SkMatrix::I(), domain,
                                        GrTextureDomain::kClamp_Mode, copyParams.fFilter));
    } else {
        GrSamplerState samplerState(GrSamplerState::WrapMode::kClamp, copyParams.fFilter);
        paint.addColorTextureProcessor(std::move(inputProxy), SkMatrix::I(), samplerState);
    }
    paint.setPorterDuffXPFactory(SkBlendMode::kSrc);

    copyRTC->fillRectToRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), dstRect,
                            localRect);
    return copyRTC->asTextureProxyRef();
}
Esempio n. 3
0
static void convolve_gaussian_1d(GrDrawContext* drawContext,
                                 const GrClip& clip,
                                 const SkIRect& dstRect,
                                 const SkIPoint& srcOffset,
                                 GrTexture* texture,
                                 Gr1DKernelEffect::Direction direction,
                                 int radius,
                                 float sigma,
                                 bool useBounds,
                                 float bounds[2]) {
    GrPaint paint;
    paint.setGammaCorrect(drawContext->isGammaCorrect());
    sk_sp<GrFragmentProcessor> conv(GrConvolutionEffect::MakeGaussian(
        texture, direction, radius, sigma, useBounds, bounds));
    paint.addColorFragmentProcessor(std::move(conv));
    paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
    SkMatrix localMatrix = SkMatrix::MakeTrans(-SkIntToScalar(srcOffset.x()),
                                               -SkIntToScalar(srcOffset.y()));
    drawContext->fillRectWithLocalMatrix(clip, paint, SkMatrix::I(),
                                         SkRect::Make(dstRect), localMatrix);
}
Esempio n. 4
0
static GrTexture* copy_on_gpu(GrTexture* inputTexture, const SkIRect* subset,
                              const CopyParams& copyParams) {
    SkASSERT(!subset || !subset->isEmpty());
    GrContext* context = inputTexture->getContext();
    SkASSERT(context);
    const GrCaps* caps = context->caps();

    // Either it's a cache miss or the original wasn't cached to begin with.
    GrSurfaceDesc rtDesc = inputTexture->desc();
    rtDesc.fFlags = rtDesc.fFlags | kRenderTarget_GrSurfaceFlag;
    rtDesc.fWidth = copyParams.fWidth;
    rtDesc.fHeight = copyParams.fHeight;
    rtDesc.fConfig = GrMakePixelConfigUncompressed(rtDesc.fConfig);

    // If the config isn't renderable try converting to either A8 or an 32 bit config. Otherwise,
    // fail.
    if (!caps->isConfigRenderable(rtDesc.fConfig, false)) {
        if (GrPixelConfigIsAlphaOnly(rtDesc.fConfig)) {
            if (caps->isConfigRenderable(kAlpha_8_GrPixelConfig, false)) {
                rtDesc.fConfig = kAlpha_8_GrPixelConfig;
            } else if (caps->isConfigRenderable(kSkia8888_GrPixelConfig, false)) {
                rtDesc.fConfig = kSkia8888_GrPixelConfig;
            } else {
                return nullptr;
            }
        } else if (kRGB_GrColorComponentFlags ==
                   (kRGB_GrColorComponentFlags & GrPixelConfigComponentMask(rtDesc.fConfig))) {
            if (caps->isConfigRenderable(kSkia8888_GrPixelConfig, false)) {
                rtDesc.fConfig = kSkia8888_GrPixelConfig;
            } else {
                return nullptr;
            }
        } else {
            return nullptr;
        }
    }

    SkAutoTUnref<GrTexture> copy(context->textureProvider()->createTexture(rtDesc,
                                                                           SkBudgeted::kYes));
    if (!copy) {
        return nullptr;
    }

    // TODO: If no scaling is being performed then use copySurface.

    GrPaint paint;
    paint.setGammaCorrect(true);

    // TODO: Initializing these values for no reason cause the compiler is complaining
    SkScalar sx = 0.f;
    SkScalar sy = 0.f;
    if (subset) {
        sx = 1.f / inputTexture->width();
        sy = 1.f / inputTexture->height();
    }

    if (copyParams.fFilter != GrTextureParams::kNone_FilterMode && subset &&
        (subset->width() != copyParams.fWidth || subset->height() != copyParams.fHeight)) {
        SkRect domain;
        domain.fLeft = (subset->fLeft + 0.5f) * sx;
        domain.fTop = (subset->fTop + 0.5f)* sy;
        domain.fRight = (subset->fRight - 0.5f) * sx;
        domain.fBottom = (subset->fBottom - 0.5f) * sy;
        // This would cause us to read values from outside the subset. Surely, the caller knows
        // better!
        SkASSERT(copyParams.fFilter != GrTextureParams::kMipMap_FilterMode);
        paint.addColorFragmentProcessor(
            GrTextureDomainEffect::Make(inputTexture, SkMatrix::I(), domain,
                                        GrTextureDomain::kClamp_Mode,
                                        copyParams.fFilter));
    } else {
        GrTextureParams params(SkShader::kClamp_TileMode, copyParams.fFilter);
        paint.addColorTextureProcessor(inputTexture, SkMatrix::I(), params);
    }
    paint.setPorterDuffXPFactory(SkXfermode::kSrc_Mode);

    SkRect localRect;
    if (subset) {
        localRect = SkRect::Make(*subset);
        localRect.fLeft *= sx;
        localRect.fTop *= sy;
        localRect.fRight *= sx;
        localRect.fBottom *= sy;
    } else {
        localRect = SkRect::MakeWH(1.f, 1.f);
    }

    sk_sp<GrDrawContext> drawContext(context->drawContext(sk_ref_sp(copy->asRenderTarget())));
    if (!drawContext) {
        return nullptr;
    }

    SkRect dstRect = SkRect::MakeWH(SkIntToScalar(rtDesc.fWidth), SkIntToScalar(rtDesc.fHeight));
    drawContext->fillRectToRect(GrNoClip(), paint, SkMatrix::I(), dstRect, localRect);
    return copy.release();
}
Esempio n. 5
0
DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(SRGBMipMaps, reporter, ctxInfo) {
    GrContext* context = ctxInfo.grContext();
    if (!context->caps()->srgbSupport()) {
        return;
    }

    const int rtS = 16;
    const int texS = rtS * 2;

    // Fill texture with a dither of black and 60% sRGB (~ 32.5% linear) gray. Although there is
    // only one likely failure mode (doing a direct downsample of the sRGB values), this pattern
    // maximizes the minimum error across all three conceivable failure modes:
    // 1) Likely incorrect:
    //      (A + B) / 2
    // 2) No input decode, decode output:
    //      linear_to_srgb((A + B) / 2)
    // 3) Decode input, no output encode:
    //      (srgb_to_linear(A) + srgb_to_linear(B)) / 2

    const U8CPU srgb60 = sk_float_round2int(0.6f * 255.0f);
    static const SkPMColor colors[2] = {
        SkPackARGB32(0xFF, srgb60, srgb60, srgb60),
        SkPackARGB32(0xFF, 0x00, 0x00, 0x00)
    };
    uint32_t texData[texS * texS];
    for (int y = 0; y < texS; ++y) {
        for (int x = 0; x < texS; ++x) {
            texData[y * texS + x] = colors[(x + y) % 2];
        }
    }

    // We can be pretty generous with the error detection, thanks to the choice of input.
    // The closest likely failure mode is off by > 0.1, so anything that encodes within
    // 10/255 of optimal is more than good enough for this test.
    const U8CPU expectedSRGB = sk_float_round2int(
        linear_to_srgb(srgb_to_linear(srgb60 / 255.0f) / 2.0f) * 255.0f);
    const U8CPU expectedLinear = srgb60 / 2;
    const U8CPU error = 10;

    // Create our test texture
    GrSurfaceDesc desc;
    desc.fFlags = kNone_GrSurfaceFlags;
    desc.fConfig = kSRGBA_8888_GrPixelConfig;
    desc.fWidth = texS;
    desc.fHeight = texS;

    GrTextureProvider* texProvider = context->textureProvider();
    SkAutoTUnref<GrTexture> texture(texProvider->createTexture(desc, SkBudgeted::kNo, texData, 0));

    // Create two render target contexts (L32 and S32)
    sk_sp<SkColorSpace> srgbColorSpace = SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named);
    sk_sp<GrRenderTargetContext> l32RenderTargetContext = context->makeRenderTargetContext(
        SkBackingFit::kExact, rtS, rtS, kRGBA_8888_GrPixelConfig, nullptr);
    sk_sp<GrRenderTargetContext> s32RenderTargetContext = context->makeRenderTargetContext(
        SkBackingFit::kExact, rtS, rtS, kSRGBA_8888_GrPixelConfig, std::move(srgbColorSpace));

    SkRect rect = SkRect::MakeWH(SkIntToScalar(rtS), SkIntToScalar(rtS));
    GrNoClip noClip;
    GrPaint paint;
    paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
    GrTextureParams mipMapParams(SkShader::kRepeat_TileMode, GrTextureParams::kMipMap_FilterMode);
    paint.addColorTextureProcessor(texture, nullptr, SkMatrix::MakeScale(0.5f), mipMapParams);

    // 1) Draw texture to S32 surface (should generate/use sRGB mips)
    paint.setGammaCorrect(true);
    s32RenderTargetContext->drawRect(noClip, paint, SkMatrix::I(), rect);
    read_and_check_pixels(reporter, s32RenderTargetContext->asTexture().get(), expectedSRGB, error,
                          "first render of sRGB");

    // 2) Draw texture to L32 surface (should generate/use linear mips)
    paint.setGammaCorrect(false);
    l32RenderTargetContext->drawRect(noClip, paint, SkMatrix::I(), rect);
    read_and_check_pixels(reporter, l32RenderTargetContext->asTexture().get(), expectedLinear,
                          error, "re-render as linear");

    // 3) Go back to sRGB
    paint.setGammaCorrect(true);
    s32RenderTargetContext->drawRect(noClip, paint, SkMatrix::I(), rect);
    read_and_check_pixels(reporter, s32RenderTargetContext->asTexture().get(), expectedSRGB, error,
                          "re-render as sRGB");
}
static GrTexture* copy_on_gpu(GrTexture* inputTexture, const SkIRect* subset,
                              const CopyParams& copyParams) {
    SkASSERT(!subset || !subset->isEmpty());
    GrContext* context = inputTexture->getContext();
    SkASSERT(context);

    GrPixelConfig config = GrMakePixelConfigUncompressed(inputTexture->config());

    sk_sp<GrDrawContext> copyDC = context->makeDrawContextWithFallback(SkBackingFit::kExact,
                                                                       copyParams.fWidth,
                                                                       copyParams.fHeight,
                                                                       config, nullptr);
    if (!copyDC) {
        return nullptr;
    }

    GrPaint paint;
    paint.setGammaCorrect(true);

    SkScalar sx SK_INIT_TO_AVOID_WARNING;
    SkScalar sy SK_INIT_TO_AVOID_WARNING;
    if (subset) {
        sx = 1.f / inputTexture->width();
        sy = 1.f / inputTexture->height();
    }

    if (copyParams.fFilter != GrTextureParams::kNone_FilterMode && subset &&
        (subset->width() != copyParams.fWidth || subset->height() != copyParams.fHeight)) {
        SkRect domain;
        domain.fLeft = (subset->fLeft + 0.5f) * sx;
        domain.fTop = (subset->fTop + 0.5f)* sy;
        domain.fRight = (subset->fRight - 0.5f) * sx;
        domain.fBottom = (subset->fBottom - 0.5f) * sy;
        // This would cause us to read values from outside the subset. Surely, the caller knows
        // better!
        SkASSERT(copyParams.fFilter != GrTextureParams::kMipMap_FilterMode);
        paint.addColorFragmentProcessor(
            GrTextureDomainEffect::Make(inputTexture, nullptr, SkMatrix::I(), domain,
                                        GrTextureDomain::kClamp_Mode,
                                        copyParams.fFilter));
    } else {
        GrTextureParams params(SkShader::kClamp_TileMode, copyParams.fFilter);
        paint.addColorTextureProcessor(inputTexture, nullptr, SkMatrix::I(), params);
    }
    paint.setPorterDuffXPFactory(SkBlendMode::kSrc);

    SkRect localRect;
    if (subset) {
        localRect = SkRect::Make(*subset);
        localRect.fLeft *= sx;
        localRect.fTop *= sy;
        localRect.fRight *= sx;
        localRect.fBottom *= sy;
    } else {
        localRect = SkRect::MakeWH(1.f, 1.f);
    }

    SkRect dstRect = SkRect::MakeIWH(copyParams.fWidth, copyParams.fHeight);
    copyDC->fillRectToRect(GrNoClip(), paint, SkMatrix::I(), dstRect, localRect);
    return copyDC->asTexture().release();
}
Esempio n. 7
0
sk_sp<GrDrawContext> GaussianBlur(GrContext* context,
                                  GrTexture* origSrc,
                                  sk_sp<SkColorSpace> colorSpace,
                                  const SkIRect& dstBounds,
                                  const SkIRect* srcBounds,
                                  float sigmaX,
                                  float sigmaY,
                                  SkBackingFit fit) {
    SkASSERT(context);
    SkIRect clearRect;
    int scaleFactorX, radiusX;
    int scaleFactorY, radiusY;
    int maxTextureSize = context->caps()->maxTextureSize();
    sigmaX = adjust_sigma(sigmaX, maxTextureSize, &scaleFactorX, &radiusX);
    sigmaY = adjust_sigma(sigmaY, maxTextureSize, &scaleFactorY, &radiusY);
    SkASSERT(sigmaX || sigmaY);

    SkIPoint srcOffset = SkIPoint::Make(-dstBounds.x(), -dstBounds.y());
    SkIRect localDstBounds = SkIRect::MakeWH(dstBounds.width(), dstBounds.height());
    SkIRect localSrcBounds;
    SkIRect srcRect;
    if (srcBounds) {
        srcRect = localSrcBounds = *srcBounds;
        srcRect.offset(srcOffset);
        srcBounds = &localSrcBounds;
    } else {
        srcRect = localDstBounds;
    }

    scale_irect_roundout(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY);
    scale_irect(&srcRect, scaleFactorX, scaleFactorY);

    // setup new clip
    GrFixedClip clip(localDstBounds);

    sk_sp<GrTexture> srcTexture(sk_ref_sp(origSrc));

    SkASSERT(kBGRA_8888_GrPixelConfig == srcTexture->config() ||
             kRGBA_8888_GrPixelConfig == srcTexture->config() ||
             kSRGBA_8888_GrPixelConfig == srcTexture->config() ||
             kSBGRA_8888_GrPixelConfig == srcTexture->config() ||
             kRGBA_half_GrPixelConfig == srcTexture->config() ||
             kAlpha_8_GrPixelConfig == srcTexture->config());

    const int width = dstBounds.width();
    const int height = dstBounds.height();
    const GrPixelConfig config = srcTexture->config();

    sk_sp<GrDrawContext> dstDrawContext(context->makeDrawContext(fit,
                                                                 width, height, config, colorSpace,
                                                                 0, kDefault_GrSurfaceOrigin));
    if (!dstDrawContext) {
        return nullptr;
    }

    // For really small blurs (certainly no wider than 5x5 on desktop gpus) it is faster to just
    // launch a single non separable kernel vs two launches
    if (sigmaX > 0.0f && sigmaY > 0.0f &&
            (2 * radiusX + 1) * (2 * radiusY + 1) <= MAX_KERNEL_SIZE) {
        // We shouldn't be scaling because this is a small size blur
        SkASSERT((1 == scaleFactorX) && (1 == scaleFactorY));

        convolve_gaussian_2d(dstDrawContext.get(), clip, localDstBounds, srcOffset,
                             srcTexture.get(), radiusX, radiusY, sigmaX, sigmaY, srcBounds);

        return dstDrawContext;
    } 

    sk_sp<GrDrawContext> tmpDrawContext(context->makeDrawContext(fit,
                                                                 width, height, config, colorSpace,
                                                                 0, kDefault_GrSurfaceOrigin));
    if (!tmpDrawContext) {
        return nullptr;
    }

    sk_sp<GrDrawContext> srcDrawContext;

    SkASSERT(SkIsPow2(scaleFactorX) && SkIsPow2(scaleFactorY));

    for (int i = 1; i < scaleFactorX || i < scaleFactorY; i *= 2) {
        GrPaint paint;
        paint.setGammaCorrect(dstDrawContext->isGammaCorrect());
        SkMatrix matrix;
        matrix.setIDiv(srcTexture->width(), srcTexture->height());
        SkIRect dstRect(srcRect);
        if (srcBounds && i == 1) {
            SkRect domain;
            matrix.mapRect(&domain, SkRect::Make(*srcBounds));
            domain.inset((i < scaleFactorX) ? SK_ScalarHalf / srcTexture->width() : 0.0f,
                         (i < scaleFactorY) ? SK_ScalarHalf / srcTexture->height() : 0.0f);
            sk_sp<GrFragmentProcessor> fp(GrTextureDomainEffect::Make(
                                                        srcTexture.get(),
                                                        nullptr,
                                                        matrix,
                                                        domain,
                                                        GrTextureDomain::kDecal_Mode,
                                                        GrTextureParams::kBilerp_FilterMode));
            paint.addColorFragmentProcessor(std::move(fp));
            srcRect.offset(-srcOffset);
            srcOffset.set(0, 0);
        } else {
            GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode);
            paint.addColorTextureProcessor(srcTexture.get(), nullptr, matrix, params);
        }
        paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
        shrink_irect_by_2(&dstRect, i < scaleFactorX, i < scaleFactorY);

        dstDrawContext->fillRectToRect(clip, paint, SkMatrix::I(),
                                       SkRect::Make(dstRect), SkRect::Make(srcRect));

        srcDrawContext = dstDrawContext;
        srcRect = dstRect;
        srcTexture = srcDrawContext->asTexture();
        dstDrawContext.swap(tmpDrawContext);
        localSrcBounds = srcRect;
    }

    srcRect = localDstBounds;
    scale_irect_roundout(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY);
    if (sigmaX > 0.0f) {
        if (scaleFactorX > 1) {
            SkASSERT(srcDrawContext);

            // Clear out a radius to the right of the srcRect to prevent the
            // X convolution from reading garbage.
            clearRect = SkIRect::MakeXYWH(srcRect.fRight, srcRect.fTop,
                                          radiusX, srcRect.height());
            srcDrawContext->clear(&clearRect, 0x0, false);
        }

        convolve_gaussian(dstDrawContext.get(), clip, srcRect,
                          srcTexture.get(), Gr1DKernelEffect::kX_Direction, radiusX, sigmaX,
                          srcBounds, srcOffset);
        srcDrawContext = dstDrawContext;
        srcTexture = srcDrawContext->asTexture();
        srcRect.offsetTo(0, 0);
        dstDrawContext.swap(tmpDrawContext);
        localSrcBounds = srcRect;
        srcOffset.set(0, 0);
    }

    if (sigmaY > 0.0f) {
        if (scaleFactorY > 1 || sigmaX > 0.0f) {
            SkASSERT(srcDrawContext);

            // Clear out a radius below the srcRect to prevent the Y
            // convolution from reading garbage.
            clearRect = SkIRect::MakeXYWH(srcRect.fLeft, srcRect.fBottom,
                                          srcRect.width(), radiusY);
            srcDrawContext->clear(&clearRect, 0x0, false);
        }

        convolve_gaussian(dstDrawContext.get(), clip, srcRect,
                          srcTexture.get(), Gr1DKernelEffect::kY_Direction, radiusY, sigmaY,
                          srcBounds, srcOffset);

        srcDrawContext = dstDrawContext;
        srcRect.offsetTo(0, 0);
        dstDrawContext.swap(tmpDrawContext);
    }

    SkASSERT(srcDrawContext);
    srcTexture = nullptr;     // we don't use this from here on out

    if (scaleFactorX > 1 || scaleFactorY > 1) {
        // Clear one pixel to the right and below, to accommodate bilinear upsampling.
        clearRect = SkIRect::MakeXYWH(srcRect.fLeft, srcRect.fBottom, srcRect.width() + 1, 1);
        srcDrawContext->clear(&clearRect, 0x0, false);
        clearRect = SkIRect::MakeXYWH(srcRect.fRight, srcRect.fTop, 1, srcRect.height());
        srcDrawContext->clear(&clearRect, 0x0, false);

        SkMatrix matrix;
        matrix.setIDiv(srcDrawContext->width(), srcDrawContext->height());

        GrPaint paint;
        paint.setGammaCorrect(dstDrawContext->isGammaCorrect());
        // FIXME:  this should be mitchell, not bilinear.
        GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode);
        sk_sp<GrTexture> tex(srcDrawContext->asTexture());
        paint.addColorTextureProcessor(tex.get(), nullptr, matrix, params);
        paint.setPorterDuffXPFactory(SkBlendMode::kSrc);

        SkIRect dstRect(srcRect);
        scale_irect(&dstRect, scaleFactorX, scaleFactorY);

        dstDrawContext->fillRectToRect(clip, paint, SkMatrix::I(),
                                       SkRect::Make(dstRect), SkRect::Make(srcRect));

        srcDrawContext = dstDrawContext;
        srcRect = dstRect;
        dstDrawContext.swap(tmpDrawContext);
    }

    return srcDrawContext;
}