blob: dab64de05b2fc87835591b7365986f1e1080c9fc [file] [log] [blame]
/*
* Copyright 2023 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/SkImage_Base.h"
#include "include/core/SkBitmap.h"
#include "include/core/SkColorSpace.h"
#include "include/core/SkColorType.h"
#include "include/core/SkImage.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkPixmap.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRect.h"
#include "include/core/SkSize.h"
#include "include/core/SkSurfaceProps.h"
#include "include/core/SkTypes.h"
#include "include/private/base/SkDebug.h"
#include "src/core/SkBitmapCache.h"
#include "src/core/SkColorSpacePriv.h"
#include "src/core/SkImageFilterCache.h"
#include "src/core/SkImageFilterTypes.h"
#include "src/core/SkImageFilter_Base.h"
#include "src/core/SkSpecialImage.h"
#include "src/image/SkRescaleAndReadPixels.h"
#include <atomic>
#include <utility>
class SkImageFilter;
SkImage_Base::SkImage_Base(const SkImageInfo& info, uint32_t uniqueID)
: SkImage(info, uniqueID), fAddedToRasterCache(false) {}
SkImage_Base::~SkImage_Base() {
if (fAddedToRasterCache.load()) {
SkNotifyBitmapGenIDIsStale(this->uniqueID());
}
}
void SkImage_Base::onAsyncRescaleAndReadPixels(const SkImageInfo& info,
SkIRect origSrcRect,
RescaleGamma rescaleGamma,
RescaleMode rescaleMode,
ReadPixelsCallback callback,
ReadPixelsContext context) const {
SkBitmap src;
SkPixmap peek;
SkIRect srcRect;
if (this->peekPixels(&peek)) {
src.installPixels(peek);
srcRect = origSrcRect;
} else {
// Context TODO: Elevate GrDirectContext requirement to public API.
auto dContext = as_IB(this)->directContext();
src.setInfo(this->imageInfo().makeDimensions(origSrcRect.size()));
src.allocPixels();
if (!this->readPixels(dContext, src.pixmap(), origSrcRect.x(), origSrcRect.y())) {
callback(context, nullptr);
return;
}
srcRect = SkIRect::MakeSize(src.dimensions());
}
return SkRescaleAndReadPixels(src, info, srcRect, rescaleGamma, rescaleMode, callback, context);
}
bool SkImage_Base::onAsLegacyBitmap(GrDirectContext* dContext, SkBitmap* bitmap) const {
// As the base-class, all we can do is make a copy (regardless of mode).
// Subclasses that want to be more optimal should override.
SkImageInfo info = fInfo.makeColorType(kN32_SkColorType).makeColorSpace(nullptr);
if (!bitmap->tryAllocPixels(info)) {
return false;
}
if (!this->readPixels(
dContext, bitmap->info(), bitmap->getPixels(), bitmap->rowBytes(), 0, 0)) {
bitmap->reset();
return false;
}
bitmap->setImmutable();
return true;
}
sk_sp<SkImage> SkImage_Base::makeSubset(GrDirectContext* direct, const SkIRect& subset) const {
if (subset.isEmpty()) {
return nullptr;
}
const SkIRect bounds = SkIRect::MakeWH(this->width(), this->height());
if (!bounds.contains(subset)) {
return nullptr;
}
// optimization : return self if the subset == our bounds
if (bounds == subset) {
return sk_ref_sp(const_cast<SkImage_Base*>(this));
}
return this->onMakeSubset(direct, subset);
}
sk_sp<SkImage> SkImage_Base::makeSubset(skgpu::graphite::Recorder* recorder,
const SkIRect& subset,
RequiredProperties requiredProps) const {
if (subset.isEmpty()) {
return nullptr;
}
const SkIRect bounds = SkIRect::MakeWH(this->width(), this->height());
if (!bounds.contains(subset)) {
return nullptr;
}
return this->onMakeSubset(recorder, subset, requiredProps);
}
sk_sp<SkImage> SkImage_Base::makeWithFilter(GrRecordingContext*,
const SkImageFilter* filter,
const SkIRect& subset,
const SkIRect& clipBounds,
SkIRect* outSubset,
SkIPoint* offset) const {
if (!filter || !outSubset || !offset || !this->bounds().contains(subset)) {
return nullptr;
}
auto srcSpecialImage = SkSpecialImage::MakeFromImage(
nullptr, subset, sk_ref_sp(const_cast<SkImage_Base*>(this)), SkSurfaceProps());
if (!srcSpecialImage) {
return nullptr;
}
sk_sp<SkImageFilterCache> cache(
SkImageFilterCache::Create(SkImageFilterCache::kDefaultTransientSize));
// The filters operate in the local space of the src image, where (0,0) corresponds to the
// subset's top left corner. But the clip bounds and any crop rects on the filters are in the
// original coordinate system, so configure the CTM to correct crop rects and explicitly adjust
// the clip bounds (since it is assumed to already be in image space).
// TODO: Once all image filters support it, we can just use the subset's top left corner as
// the source FilterResult's origin.
skif::ContextInfo ctxInfo = {
skif::Mapping(SkMatrix::Translate(-subset.x(), -subset.y())),
skif::LayerSpace<SkIRect>(clipBounds.makeOffset(-subset.topLeft())),
skif::FilterResult(srcSpecialImage),
fInfo.colorType(),
fInfo.colorSpace(),
/*fSurfaceProps=*/{},
cache.get()};
skif::Context context = skif::Context::MakeRaster(ctxInfo);
return this->filterSpecialImage(
context, as_IFB(filter), srcSpecialImage.get(), subset, clipBounds, outSubset, offset);
}
sk_sp<SkImage> SkImage_Base::filterSpecialImage(skif::Context context,
const SkImageFilter_Base* filter,
const SkSpecialImage* specialImage,
const SkIRect& subset,
const SkIRect& clipBounds,
SkIRect* outSubset,
SkIPoint* offset) const {
sk_sp<SkSpecialImage> result = filter->filterImage(context).imageAndOffset(context, offset);
if (!result) {
return nullptr;
}
// The output image and offset are relative to the subset rectangle, so the offset needs to
// be shifted to put it in the correct spot with respect to the original coordinate system
offset->fX += subset.x();
offset->fY += subset.y();
// Final clip against the exact clipBounds (the clip provided in the context gets adjusted
// to account for pixel-moving filters so doesn't always exactly match when finished). The
// clipBounds are translated into the clippedDstRect coordinate space, including the
// result->subset() ensures that the result's image pixel origin does not affect results.
SkIRect dstRect = result->subset();
SkIRect clippedDstRect = dstRect;
if (!clippedDstRect.intersect(clipBounds.makeOffset(result->subset().topLeft() - *offset))) {
return nullptr;
}
// Adjust the geometric offset if the top-left corner moved as well
offset->fX += (clippedDstRect.x() - dstRect.x());
offset->fY += (clippedDstRect.y() - dstRect.y());
*outSubset = clippedDstRect;
return result->asImage();
}
void SkImage_Base::onAsyncRescaleAndReadPixelsYUV420(SkYUVColorSpace,
sk_sp<SkColorSpace> dstColorSpace,
SkIRect srcRect,
SkISize dstSize,
RescaleGamma,
RescaleMode,
ReadPixelsCallback callback,
ReadPixelsContext context) const {
// TODO: Call non-YUV asyncRescaleAndReadPixels and then make our callback convert to YUV and
// call client's callback.
callback(context, nullptr);
}
sk_sp<SkImage> SkImage_Base::makeColorSpace(GrDirectContext* direct,
sk_sp<SkColorSpace> target) const {
return this->makeColorTypeAndColorSpace(direct, this->colorType(), std::move(target));
}
sk_sp<SkImage> SkImage_Base::makeColorSpace(skgpu::graphite::Recorder* recorder,
sk_sp<SkColorSpace> target,
RequiredProperties props) const {
return this->makeColorTypeAndColorSpace(recorder, this->colorType(), std::move(target), props);
}
sk_sp<SkImage> SkImage_Base::makeColorTypeAndColorSpace(GrDirectContext* dContext,
SkColorType targetColorType,
sk_sp<SkColorSpace> targetCS) const {
if (kUnknown_SkColorType == targetColorType || !targetCS) {
return nullptr;
}
SkColorType colorType = this->colorType();
SkColorSpace* colorSpace = this->colorSpace();
if (!colorSpace) {
colorSpace = sk_srgb_singleton();
}
if (colorType == targetColorType &&
(SkColorSpace::Equals(colorSpace, targetCS.get()) || this->isAlphaOnly())) {
return sk_ref_sp(const_cast<SkImage_Base*>(this));
}
return this->onMakeColorTypeAndColorSpace(targetColorType, std::move(targetCS), dContext);
}
sk_sp<SkImage> SkImage_Base::makeColorTypeAndColorSpace(skgpu::graphite::Recorder*,
SkColorType ct,
sk_sp<SkColorSpace> cs,
RequiredProperties) const {
// Default to the ganesh version which should be backend agnostic if this
// image is, for example, a raster backed image. The graphite subclass overrides
// this method and things work correctly.
return this->makeColorTypeAndColorSpace(nullptr, ct, std::move(cs));
}