/* * Copyright 2020 Google LLC * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "src/image/SkRescaleAndReadPixels.h" #include "include/core/SkBitmap.h" #include "include/core/SkBlendMode.h" #include "include/core/SkCanvas.h" #include "include/core/SkColorSpace.h" #include "include/core/SkColorType.h" #include "include/core/SkImageInfo.h" #include "include/core/SkPaint.h" #include "include/core/SkPixmap.h" #include "include/core/SkRect.h" #include "include/core/SkRefCnt.h" #include "include/core/SkSamplingOptions.h" #include "include/core/SkSurface.h" #include #include #include #include void SkRescaleAndReadPixels(SkBitmap bmp, const SkImageInfo& resultInfo, const SkIRect& srcRect, SkImage::RescaleGamma rescaleGamma, SkImage::RescaleMode rescaleMode, SkImage::ReadPixelsCallback callback, SkImage::ReadPixelsContext context) { int srcW = srcRect.width(); int srcH = srcRect.height(); float sx = (float)resultInfo.width() / srcW; float sy = (float)resultInfo.height() / srcH; // How many bilerp/bicubic steps to do in X and Y. + means upscaling, - means downscaling. int stepsX; int stepsY; if (rescaleMode != SkImage::RescaleMode::kNearest) { stepsX = static_cast((sx > 1.f) ? std::ceil(std::log2f(sx)) : std::floor(std::log2f(sx))); stepsY = static_cast((sy > 1.f) ? std::ceil(std::log2f(sy)) : std::floor(std::log2f(sy))); } else { stepsX = sx != 1.f; stepsY = sy != 1.f; } SkPaint paint; paint.setBlendMode(SkBlendMode::kSrc); if (stepsX < 0 || stepsY < 0) { // Don't trigger MIP generation. We don't currently have a way to trigger bicubic for // downscaling draws. // TODO: should we trigger cubic now that we can? if (rescaleMode != SkImage::RescaleMode::kNearest) { rescaleMode = SkImage::RescaleMode::kRepeatedLinear; } } auto rescaling_to_sampling = [](SkImage::RescaleMode rescaleMode) { SkSamplingOptions sampling; if (rescaleMode == SkImage::RescaleMode::kRepeatedLinear) { sampling = SkSamplingOptions(SkFilterMode::kLinear); } else if (rescaleMode == SkImage::RescaleMode::kRepeatedCubic) { sampling = SkSamplingOptions({1.0f/3, 1.0f/3}); } return sampling; }; SkSamplingOptions sampling = rescaling_to_sampling(rescaleMode); sk_sp tempSurf; sk_sp srcImage; int srcX = srcRect.fLeft; int srcY = srcRect.fTop; SkCanvas::SrcRectConstraint constraint = SkCanvas::kStrict_SrcRectConstraint; // Assume we should ignore the rescale linear request if the surface has no color space since // it's unclear how we'd linearize from an unknown color space. if (rescaleGamma == SkSurface::RescaleGamma::kLinear && bmp.info().colorSpace() && !bmp.info().colorSpace()->gammaIsLinear()) { auto cs = bmp.info().colorSpace()->makeLinearGamma(); // Promote to F16 color type to preserve precision. auto ii = SkImageInfo::Make(srcW, srcH, kRGBA_F16_SkColorType, bmp.info().alphaType(), std::move(cs)); auto linearSurf = SkSurfaces::Raster(ii); if (!linearSurf) { callback(context, nullptr); return; } linearSurf->getCanvas()->drawImage(bmp.asImage().get(), -srcX, -srcY, sampling, &paint); tempSurf = std::move(linearSurf); srcImage = tempSurf->makeImageSnapshot(); srcX = 0; srcY = 0; constraint = SkCanvas::kFast_SrcRectConstraint; } else { // MakeFromBitmap would trigger a copy if bmp is mutable. srcImage = SkImages::RasterFromPixmap(bmp.pixmap(), nullptr, nullptr); } while (stepsX || stepsY) { int nextW = resultInfo.width(); int nextH = resultInfo.height(); if (stepsX < 0) { nextW = resultInfo.width() << (-stepsX - 1); stepsX++; } else if (stepsX != 0) { if (stepsX > 1) { nextW = srcW * 2; } --stepsX; } if (stepsY < 0) { nextH = resultInfo.height() << (-stepsY - 1); stepsY++; } else if (stepsY != 0) { if (stepsY > 1) { nextH = srcH * 2; } --stepsY; } auto ii = srcImage->imageInfo().makeWH(nextW, nextH); if (!stepsX && !stepsY) { // Might as well fold conversion to final info in the last step. ii = resultInfo; } auto next = SkSurfaces::Raster(ii); if (!next) { callback(context, nullptr); return; } next->getCanvas()->drawImageRect( srcImage.get(), SkRect::Make(SkIRect::MakeXYWH(srcX, srcY, srcW, srcH)), SkRect::MakeIWH(nextW, nextH), sampling, &paint, constraint); tempSurf = std::move(next); srcImage = tempSurf->makeImageSnapshot(); srcX = srcY = 0; srcW = nextW; srcH = nextH; constraint = SkCanvas::kFast_SrcRectConstraint; } size_t rowBytes = resultInfo.minRowBytes(); std::unique_ptr data(new char[resultInfo.height() * rowBytes]); SkPixmap pm(resultInfo, data.get(), rowBytes); if (srcImage->readPixels(nullptr, pm, srcX, srcY)) { class Result : public SkImage::AsyncReadResult { public: Result(std::unique_ptr data, size_t rowBytes) : fData(std::move(data)), fRowBytes(rowBytes) {} int count() const override { return 1; } const void* data(int i) const override { return fData.get(); } size_t rowBytes(int i) const override { return fRowBytes; } private: std::unique_ptr fData; size_t fRowBytes; }; callback(context, std::make_unique(std::move(data), rowBytes)); } else { callback(context, nullptr); } }