blob: 2618c02cbec059501893b736c05933b22244c722 [file] [log] [blame]
/*
* 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 <cmath>
#include <cstddef>
#include <memory>
#include <utility>
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<int>((sx > 1.f) ? std::ceil(std::log2f(sx))
: std::floor(std::log2f(sx)));
stepsY = static_cast<int>((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<SkSurface> tempSurf;
sk_sp<SkImage> 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<char[]> 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<const char[]> 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<const char[]> fData;
size_t fRowBytes;
};
callback(context, std::make_unique<Result>(std::move(data), rowBytes));
} else {
callback(context, nullptr);
}
}