/* * Copyright 2008 The Android Open Source Project * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file.
*/
#define RETURN_ON_NULL(ptr) do { if (nullptr == (ptr)) return; } while (0) #define RETURN_ON_FALSE(pred) do { if (!(pred)) return; } while (0)
// This is a test: static_assert with no message is a c++17 feature, // and std::max() is constexpr only since the c++14 stdlib.
static_assert(std::max(3,4) == 4);
/* * Return true if the drawing this rect would hit every pixels in the canvas. * * Returns false if * - rect does not contain the canvas' bounds * - paint is not fill * - paint would blur or otherwise change the coverage of the rect
*/ bool SkCanvas::wouldOverwriteEntireSurface(const SkRect* rect, const SkPaint* paint,
SkEnumBitMask<PredrawFlags> flags) const { // Convert flags to a ShaderOverrideOpacity enum auto overrideOpacity = (flags & PredrawFlags::kOpaqueShaderOverride) ?
SkPaintPriv::kOpaque_ShaderOverrideOpacity :
(flags & PredrawFlags::kNonOpaqueShaderOverride) ?
SkPaintPriv::kNotOpaque_ShaderOverrideOpacity :
SkPaintPriv::kNone_ShaderOverrideOpacity;
// if we're clipped at all, we can't overwrite the entire surface
{ const SkDevice* root = this->rootDevice(); const SkDevice* top = this->topDevice(); if (root != top) { returnfalse; // we're in a saveLayer, so conservatively don't assume we'll overwrite
} if (!root->isClipWideOpen()) { returnfalse;
}
}
if (rect) { if (!this->getTotalMatrix().isScaleTranslate()) { returnfalse; // conservative
}
SkRect devRect;
this->getTotalMatrix().mapRectScaleTranslate(&devRect, *rect); if (!devRect.contains(bounds)) { returnfalse;
}
}
bool SkCanvas::predrawNotify(bool willOverwritesEntireSurface) { if (fSurfaceBase) { if (!fSurfaceBase->aboutToDraw(willOverwritesEntireSurface
? SkSurface::kDiscard_ContentChangeMode
: SkSurface::kRetain_ContentChangeMode)) { returnfalse;
}
} returntrue;
}
bool SkCanvas::predrawNotify(const SkRect* rect, const SkPaint* paint,
SkEnumBitMask<PredrawFlags> flags) { if (fSurfaceBase) {
SkSurface::ContentChangeMode mode = SkSurface::kRetain_ContentChangeMode; // Since willOverwriteAllPixels() may not be complete free to call, we only do so if // there is an outstanding snapshot, since w/o that, there will be no copy-on-write // and therefore we don't care which mode we're in. // if (fSurfaceBase->outstandingImageSnapshot()) { if (this->wouldOverwriteEntireSurface(rect, paint, flags)) {
mode = SkSurface::kDiscard_ContentChangeMode;
}
} if (!fSurfaceBase->aboutToDraw(mode)) { returnfalse;
}
} returntrue;
}
SkCanvas::Layer::Layer(sk_sp<SkDevice> device,
FilterSpan imageFilters, const SkPaint& paint, bool isCoverage, bool includesPadding)
: fDevice(std::move(device))
, fImageFilters(imageFilters.data(), imageFilters.size())
, fPaint(paint)
, fIsCoverage(isCoverage)
, fDiscard(false)
, fIncludesPadding(includesPadding) {
SkASSERT(fDevice); // Any image filter should have been pulled out and stored in 'imageFilter' so that 'paint' // can be used as-is to draw the result of the filter to the dst device.
SkASSERT(!fPaint.getImageFilter());
}
class SkCanvas::AutoUpdateQRBounds { public: explicit AutoUpdateQRBounds(SkCanvas* canvas) : fCanvas(canvas) { // pre-condition, fQuickRejectBounds and other state should be valid before anything // modifies the device's clip.
fCanvas->validateClip();
}
~AutoUpdateQRBounds() {
fCanvas->fQuickRejectBounds = fCanvas->computeDeviceClipBounds(); // post-condition, we should remain valid after re-computing the bounds
fCanvas->validateClip();
}
// TODO: Eventually all devices will use this code path and this will just test 'flags'. constbool skipMaskFilterLayer = (flags & PredrawFlags::kSkipMaskFilterAutoLayer) ||
!this->topDevice()->useDrawCoverageMaskForMaskFilters(); return std::optional<AutoLayerForImageFilter>(
std::in_place, this, paint, rawBounds, skipMaskFilterLayer);
}
// We're peering through a lot of structs here. Only at this scope do we know that the device // is a SkNoPixelsDevice.
SkASSERT(fRootDevice->isNoPixelsDevice());
SkNoPixelsDevice* asNoPixelsDevice = static_cast<SkNoPixelsDevice*>(fRootDevice.get()); if (!asNoPixelsDevice->resetForNextPicture(bounds)) {
fRootDevice = sk_make_sp<SkNoPixelsDevice>(bounds,
fRootDevice->surfaceProps(),
fRootDevice->imageInfo().refColorSpace());
}
void SkCanvas::init(sk_sp<SkDevice> device) { // SkCanvas.h declares internal storage for the hidden struct MCRec, and this // assert ensure it's sufficient. <= is used because the struct has pointer fields, so the // declared size is an upper bound across architectures. When the size is smaller, more stack
static_assert(sizeof(MCRec) <= kMCRecSize);
if (!device) {
device = sk_make_sp<SkNoPixelsDevice>(SkIRect::MakeEmpty(), fProps);
}
// From this point on, SkCanvas will always have a device
SkASSERT(device);
fSaveCount = 1;
fMCRec = new (fMCStack.push_back()) MCRec(device.get());
// The root device and the canvas should always have the same pixel geometry
SkASSERT(fProps.pixelGeometry() == device->surfaceProps().pixelGeometry());
SkCanvas::~SkCanvas() { // Mark all pending layers to be discarded during restore (rather than drawn)
SkDeque::Iter iter(fMCStack, SkDeque::Iter::kFront_IterStart); for (;;) {
MCRec* rec = (MCRec*)iter.next(); if (!rec) { break;
} if (rec->fLayer) {
rec->fLayer->fDiscard = true;
}
}
// free up the contents of our deque
this->restoreToCount(1); // restore everything but the last
this->internalRestore(); // restore the last, since we're going away
}
bool SkCanvas::readPixels(const SkBitmap& bm, int x, int y) {
SkPixmap pm; return bm.peekPixels(&pm) && this->readPixels(pm, x, y);
}
bool SkCanvas::writePixels(const SkBitmap& bitmap, int x, int y) {
SkPixmap pm; if (bitmap.peekPixels(&pm)) { return this->writePixels(pm.info(), pm.addr(), pm.rowBytes(), x, y);
} returnfalse;
}
bool SkCanvas::writePixels(const SkImageInfo& srcInfo, constvoid* pixels, size_t rowBytes, int x, int y) {
SkDevice* device = this->rootDevice();
// This check gives us an early out and prevents generation ID churn on the surface. // This is purely optional: it is a subset of the checks performed by SkWritePixelsRec.
SkIRect srcRect = SkIRect::MakeXYWH(x, y, srcInfo.width(), srcInfo.height()); if (!srcRect.intersect({0, 0, device->width(), device->height()})) { returnfalse;
}
// Tell our owning surface to bump its generation ID. constbool completeOverwrite = srcRect.size() == device->imageInfo().dimensions(); if (!this->predrawNotify(completeOverwrite)) { returnfalse;
}
// This can still fail, most notably in the case of a invalid color type or alpha type // conversion. We could pull those checks into this function and avoid the unnecessary // generation ID bump. But then we would be performing those checks twice, since they // are also necessary at the bitmap/pixmap entry points. return device->writePixels({srcInfo, pixels, rowBytes}, x, y);
}
int SkCanvas::saveLayer(const SaveLayerRec& rec) {
TRACE_EVENT0("skia", TRACE_FUNC); if (rec.fPaint && rec.fPaint->nothingToDraw()) { // no need for the layer (or any of the draws until the matching restore()
this->save();
this->clipRect({0,0,0,0});
} else {
SaveLayerStrategy strategy = this->getSaveLayerStrategy(rec);
fSaveCount += 1;
this->internalSaveLayer(rec, strategy);
} return this->getSaveCount() - 1;
}
int SkCanvas::only_axis_aligned_saveBehind(const SkRect* bounds) { if (bounds && !this->getLocalClipBounds().intersects(*bounds)) { // Assuming clips never expand, if the request bounds is outside of the current clip // there is no need to copy/restore the area, so just devolve back to a regular save.
this->save();
} else { bool doTheWork = this->onDoSaveBehind(bounds);
fSaveCount += 1;
this->internalSave(); if (doTheWork) {
this->internalSaveBehind(bounds);
}
} return this->getSaveCount() - 1;
}
// Helper function to compute the center reference point used for scale decomposition under // non-linear transformations. static skif::ParameterSpace<SkPoint> compute_decomposition_center( const SkMatrix& dstToLocal,
std::optional<skif::ParameterSpace<SkRect>> contentBounds, const skif::DeviceSpace<SkIRect>& targetOutput) { // Will use the inverse and center of the device bounds if the content bounds aren't provided.
SkRect rect = contentBounds ? SkRect(*contentBounds) : SkRect::Make(SkIRect(targetOutput));
SkPoint center = {rect.centerX(), rect.centerY()}; if (!contentBounds) { // Theoretically, the inverse transform could put center's homogeneous coord behind W = 0, // but that case is handled automatically in Mapping::decomposeCTM later.
dstToLocal.mapPoints(¢er, 1);
}
return skif::ParameterSpace<SkPoint>(center);
}
// Helper when we need to upgrade a single filter to a FilterSpan struct FilterToSpan {
FilterToSpan(const SkImageFilter* filter) : fFilter(sk_ref_sp(filter)) {}
// Compute suitable transformations and layer bounds for a new layer that will be used as the source // input into 'filter' before being drawn into 'dst' via the returned skif::Mapping. // Null filters are permitted and act as the identity. The returned mapping will be compatible with // the image filter. // // An empty optional is returned if the layer mapping and bounds couldn't be determined, in which // case the layer should be skipped. An instantiated optional can have an empty layer bounds rect // if the image filter doesn't require an input image to produce a valid output. static std::optional<std::pair<skif::Mapping, skif::LayerSpace<SkIRect>>>
get_layer_mapping_and_bounds(
SkCanvas::FilterSpan filters, const SkMatrix& localToDst, const skif::DeviceSpace<SkIRect>& targetOutput,
std::optional<skif::ParameterSpace<SkRect>> contentBounds = {},
SkScalar scaleFactor = 1.0f) {
SkMatrix dstToLocal; if (!localToDst.isFinite() ||
!localToDst.invert(&dstToLocal)) { return {};
}
skif::ParameterSpace<SkPoint> center =
compute_decomposition_center(dstToLocal, contentBounds, targetOutput);
// Determine initial mapping and a reasonable maximum dimension to prevent layer-to-device // transforms with perspective and skew from triggering excessive buffer allocations.
skif::Mapping mapping;
skif::MatrixCapability capability = skif::MatrixCapability::kComplex; for (const sk_sp<SkImageFilter>& filter : filters) { if (filter) {
capability = std::min(capability, as_IFB(filter)->getCTMCapability());
}
} if (!mapping.decomposeCTM(localToDst, capability, center)) { return {};
} // Push scale factor into layer matrix and device matrix (net no change, but the layer will have // its resolution adjusted in comparison to the final device). if (scaleFactor != 1.0f &&
!mapping.adjustLayerSpace(SkMatrix::Scale(scaleFactor, scaleFactor))) { return {};
}
// Perspective and skew could exceed this since mapping.deviceToLayer(targetOutput) is // theoretically unbounded under those conditions. Under a 45 degree rotation, a layer needs to // be 2X larger per side of the prior device in order to fully cover it. We use the max of that // and 2048 for a reasonable upper limit (this allows small layers under extreme transforms to // use more relative resolution than a larger layer). staticconstint kMinDimThreshold = 2048; int maxLayerDim = std::max(Sk64_pin_to_s32(2 * std::max(SkIRect(targetOutput).width64(),
SkIRect(targetOutput).height64())),
kMinDimThreshold);
auto baseLayerBounds = mapping.deviceToLayer(targetOutput); if (contentBounds) { // For better or for worse, user bounds currently act as a hard clip on the layer's // extent (i.e., they implement the CSS filter-effects 'filter region' feature).
skif::LayerSpace<SkIRect> knownBounds = mapping.paramToLayer(*contentBounds).roundOut(); if (!baseLayerBounds.intersect(knownBounds)) {
baseLayerBounds = skif::LayerSpace<SkIRect>::Empty();
}
}
skif::LayerSpace<SkIRect> layerBounds; if (!filters.empty()) {
layerBounds = skif::LayerSpace<SkIRect>::Union(filters.size(), [&](int i) { return filters[i] ? as_IFB(filters[i])
->getInputBounds(mapping, targetOutput, contentBounds)
: baseLayerBounds;
}); // When a filter is involved, the layer size may be larger than the default maxLayerDim due // to required inputs for filters (e.g. a displacement map with a large radius). if (layerBounds.width() > maxLayerDim || layerBounds.height() > maxLayerDim) {
skif::Mapping idealMapping{mapping.layerMatrix()}; for (const sk_sp<SkImageFilter>& filter : filters) { if (filter) { auto idealLayerBounds = as_IFB(filter)->getInputBounds(
idealMapping, targetOutput, contentBounds);
maxLayerDim = std::max(std::max(idealLayerBounds.width(),
idealLayerBounds.height()),
maxLayerDim);
}
}
}
} else { if (baseLayerBounds.isEmpty()) { return {};
}
layerBounds = baseLayerBounds;
}
// Ideally image filters operate in the dst color type, but if there is insufficient alpha bits // we move some bits from color channels into the alpha channel since that can greatly improve // the quality of blurs and other filters. static SkColorType image_filter_color_type(const SkColorInfo& dstInfo) { if (dstInfo.bytesPerPixel() <= 4 &&
dstInfo.colorType() != kRGBA_8888_SkColorType &&
dstInfo.colorType() != kBGRA_8888_SkColorType) { // "Upgrade" A8, G8, 565, 4444, 1010102, 101010x, and 888x to 8888 return kN32_SkColorType;
} else { return dstInfo.colorType();
}
}
static skif::FilterResult apply_alpha_and_colorfilter(const skif::Context& ctx, const skif::FilterResult& image, const SkPaint& paint) { // The only effects that apply to layers (other than the SkImageFilter that made this image in // the first place) are transparency and color filters.
skif::FilterResult result = image; if (paint.getAlphaf() < 1.f) {
result = result.applyColorFilter(ctx, SkColorFilters::Blend(paint.getColor4f(), /*colorSpace=*/nullptr,
SkBlendMode::kDstIn));
} if (paint.getColorFilter()) {
result = result.applyColorFilter(ctx, paint.refColorFilter());
} return result;
}
void SkCanvas::internalDrawDeviceWithFilter(SkDevice* src,
SkDevice* dst,
FilterSpan filters, const SkPaint& paint,
DeviceCompatibleWithFilter compat, const SkColorInfo& filterColorInfo,
SkScalar scaleFactor,
SkTileMode srcTileMode, bool srcIsCoverageLayer) { // The dst is always required, the src can be null if 'filter' is non-null and does not require // a source image. For regular filters, 'src' is the layer and 'dst' is the parent device. For // backdrop filters, 'src' is the parent device and 'dst' is the layer.
SkASSERT(dst);
// 'filter' sees the src device's buffer as the implicit input image, and processes the image // in this device space (referred to as the "layer" space). However, the filter // parameters need to respect the current matrix, which is not necessarily the local matrix that // was set on 'src' (e.g. because we've popped src off the stack already). // TODO (michaelludwig): Stay in SkM44 once skif::Mapping supports SkM44 instead of SkMatrix.
SkMatrix localToSrc = src ? (src->globalToDevice() * fMCRec->fMatrix).asM33() : SkMatrix::I();
SkISize srcDims = src ? src->imageInfo().dimensions() : SkISize::Make(0, 0);
// Whether or not we need to make a transformed tmp image from 'src', and what that transform is
skif::LayerSpace<SkMatrix> srcToLayer;
skif::Mapping mapping;
skif::LayerSpace<SkIRect> requiredInput;
skif::DeviceSpace<SkIRect> outputBounds{dst->devClipBounds()}; if (compat != DeviceCompatibleWithFilter::kUnknown) { // Just use the relative transform from src to dst and the src's whole image, since // internalSaveLayer should have already determined what was necessary. We explicitly // construct the inverse (dst->src) to avoid the case where src's and dst's coord transforms // were individually invertible by SkM44::invert() but their product is considered not // invertible by SkMatrix::invert(). When this happens the matrices are already poorly // conditioned so getRelativeTransform() gives us something reasonable.
SkASSERT(src);
SkASSERT(scaleFactor == 1.0f);
SkASSERT(!srcDims.isEmpty());
mapping = skif::Mapping(src->getRelativeTransform(*dst),
dst->getRelativeTransform(*src),
localToSrc);
requiredInput = skif::LayerSpace<SkIRect>(SkIRect::MakeSize(srcDims));
srcToLayer = skif::LayerSpace<SkMatrix>(SkMatrix::I());
} else { // Compute the image filter mapping by decomposing the local->device matrix of dst and // re-determining the required input. auto mappingAndBounds = get_layer_mapping_and_bounds(
filters, dst->localToDevice(), outputBounds, {}, SkTPin(scaleFactor, 0.f, 1.f)); if (!mappingAndBounds) { return;
}
std::tie(mapping, requiredInput) = *mappingAndBounds; if (src) { if (!requiredInput.isEmpty()) { // The above mapping transforms from local to dst's device space, where the layer // space represents the intermediate buffer. Now we need to determine the transform // from src to intermediate to prepare the input to the filter.
SkMatrix srcToLocal; if (!localToSrc.invert(&srcToLocal)) { return;
}
srcToLayer = skif::LayerSpace<SkMatrix>(SkMatrix::Concat(mapping.layerMatrix(),
srcToLocal));
} // Else no input is needed which can happen if a backdrop filter that doesn't use src
} else { // Trust the caller that no input was required, but keep the calculated mapping
requiredInput = skif::LayerSpace<SkIRect>::Empty();
}
}
// Start out with an empty source image, to be replaced with the snapped 'src' device. auto backend = dst->createImageFilteringBackend(src ? src->surfaceProps() : dst->surfaceProps(),
filterColorType);
skif::Stats stats;
skif::Context ctx{std::move(backend),
mapping,
requiredInput,
skif::FilterResult{},
filterColorSpace.get(),
&stats};
skif::FilterResult source; if (src && !requiredInput.isEmpty()) {
skif::LayerSpace<SkIRect> srcSubset; if (!srcToLayer.inverseMapRect(requiredInput, &srcSubset)) { return;
}
// Include the layer in the offscreen count
ctx.markNewSurface();
auto availSrc = skif::LayerSpace<SkIRect>(src->size()).relevantSubset(
srcSubset, srcTileMode);
if (SkMatrix(srcToLayer).isScaleTranslate()) { // Apply the srcToLayer transformation directly while snapping an image from the src // device. Calculate the subset of requiredInput that corresponds to srcSubset that was // restricted to the actual src dimensions. auto requiredSubset = srcToLayer.mapRect(availSrc); if (requiredSubset.width() == availSrc.width() &&
requiredSubset.height() == availSrc.height()) { // Unlike snapSpecialScaled(), snapSpecial() can avoid a copy when the underlying // representation permits it.
source = {src->snapSpecial(SkIRect(availSrc)), requiredSubset.topLeft()};
} else {
SkASSERT(compat == DeviceCompatibleWithFilter::kUnknown);
source = {src->snapSpecialScaled(SkIRect(availSrc),
SkISize(requiredSubset.size())),
requiredSubset.topLeft()};
ctx.markNewSurface();
}
}
if (compat == DeviceCompatibleWithFilter::kYesWithPadding) { // Padding was added to the source image when the 'src' SkDevice was created, so inset // to allow bounds tracking to skip shader-based tiling when possible.
SkASSERT(!filters.empty());
source = source.insetForSaveLayer();
} elseif (compat == DeviceCompatibleWithFilter::kYes) { // Do nothing, leave `source` as-is; FilterResult will automatically augment the image // sampling as needed to be visually equivalent to the more optimal kYesWithPadding case
} elseif (source) { // A backdrop filter that succeeded in snapSpecial() or snapSpecialScaled(), but since // the 'src' device wasn't prepared with 'requiredInput' in mind, add clamping.
source = source.applyCrop(ctx, source.layerBounds(), srcTileMode);
} elseif (!requiredInput.isEmpty()) { // Otherwise snapSpecialScaled() failed or the transform was complex, so snap the source // image at its original resolution and then apply srcToLayer to map to the effective // layer coordinate space.
source = {src->snapSpecial(SkIRect(availSrc)), availSrc.topLeft()}; // We adjust the desired output of the applyCrop() because ctx was original set to // fulfill 'requiredInput', which is valid *after* we apply srcToLayer. Use the original // 'srcSubset' for the desired output so that the tilemode applied to the available // subset is not discarded as a no-op.
source = source.applyCrop(ctx.withNewDesiredOutput(srcSubset),
source.layerBounds(),
srcTileMode)
.applyTransform(ctx, srcToLayer, SkFilterMode::kLinear);
}
} // else leave 'source' as the empty image
// Evaluate the image filter, with a context pointing to the source snapped from 'src' and // possibly transformed into the intermediate layer coordinate space.
ctx = ctx.withNewDesiredOutput(mapping.deviceToLayer(outputBounds))
.withNewSource(source);
// Here, we allow a single-element FilterSpan with a null entry, to simplify the loop:
sk_sp<SkImageFilter> nullFilter;
FilterSpan filtersOrNull = filters.empty() ? FilterSpan{&nullFilter, 1} : filters;
for (const sk_sp<SkImageFilter>& filter : filtersOrNull) { auto result = filter ? as_IFB(filter)->filterImage(ctx) : source;
if (srcIsCoverageLayer) {
SkASSERT(dst->useDrawCoverageMaskForMaskFilters()); // TODO: Can FilterResult optimize this in any meaningful way if it still has to go // through drawCoverageMask that requires an image (vs a coverage shader)? auto [coverageMask, origin] = result.imageAndOffset(ctx); if (coverageMask) {
SkMatrix deviceMatrixWithOffset = mapping.layerToDevice();
deviceMatrixWithOffset.preTranslate(origin.x(), origin.y());
dst->drawCoverageMask(
coverageMask.get(), deviceMatrixWithOffset, result.sampling(), paint);
}
} else {
result = apply_alpha_and_colorfilter(ctx, result, paint);
result.draw(ctx, dst, paint.getBlender());
}
}
stats.reportStats();
}
void SkCanvas::internalSaveLayer(const SaveLayerRec& rec,
SaveLayerStrategy strategy, bool coverageOnly) {
TRACE_EVENT0("skia", TRACE_FUNC); // Do this before we create the layer. We don't call the public save() since that would invoke a // possibly overridden virtual.
this->internalSave();
if (this->isClipEmpty()) { // Early out if the layer wouldn't draw anything return;
}
// Build up the paint for restoring the layer, taking only the pieces of rec.fPaint that are // relevant. Filtering is automatically chosen in internalDrawDeviceWithFilter based on the // device's coordinate space.
SkPaint restorePaint(rec.fPaint ? *rec.fPaint : SkPaint());
restorePaint.setStyle(SkPaint::kFill_Style); // a layer is filled out "infinitely"
restorePaint.setPathEffect(nullptr); // path effects are ignored for saved layers
restorePaint.setMaskFilter(nullptr); // mask filters are ignored for saved layers
restorePaint.setImageFilter(nullptr); // the image filter is held separately // Smooth non-axis-aligned layer edges; this automatically downgrades to non-AA for aligned // layer restores. This is done to match legacy behavior where the post-applied MatrixTransform // bilerp also smoothed cropped edges. See skbug.com/11252
restorePaint.setAntiAlias(true);
// When this is false, restoring the layer filled with unmodified prior contents should be // identical to the prior contents, so we can restrict the layer even more than just the // clip bounds. bool filtersPriorDevice = rec.fBackdrop; #if !defined(SK_LEGACY_INITWITHPREV_LAYER_SIZING) // A regular filter applied to a layer initialized with prior contents is somewhat // analogous to a backdrop filter so they are treated the same. // TODO(b/314968012): Chrome needs to be updated to clip saveAlphaLayer bounds explicitly when // it uses kInitWithPrevious and LCD text.
filtersPriorDevice |= ((rec.fSaveLayerFlags & kInitWithPrevious_SaveLayerFlag) &&
(!filters.empty() || cf || blender || restorePaint.getAlphaf() < 1.f)); #endif // If the restorePaint has a transparency-affecting colorfilter or blender, the output is // unbounded during restore(). `internalDrawDeviceWithFilter` automatically applies these // effects. When there's no image filter, SkDevice::drawDevice is used, which does // not apply effects beyond the layer's image so we mark `trivialRestore` as false too. // TODO: drawDevice() could be updated to apply transparency-affecting effects to a content- // clipped image, but this is the simplest solution when considering document-based SkDevices. constbool drawDeviceMustFillClip = filters.empty() &&
((cf && as_CFB(cf)->affectsTransparentBlack()) ||
(blender && as_BB(blender)->affectsTransparentBlack())); constbool trivialRestore = !filtersPriorDevice && !drawDeviceMustFillClip;
// Size the new layer relative to the prior device, which may already be aligned for filters.
SkDevice* priorDevice = this->topDevice();
skif::Mapping newLayerMapping;
skif::LayerSpace<SkIRect> layerBounds;
skif::DeviceSpace<SkIRect> outputBounds{priorDevice->devClipBounds()};
std::optional<skif::ParameterSpace<SkRect>> contentBounds; // Set the bounds hint if provided and there's no further effects on prior device content if (rec.fBounds && trivialRestore) {
contentBounds = skif::ParameterSpace<SkRect>(*rec.fBounds);
}
auto mappingAndBounds = get_layer_mapping_and_bounds(
filters, priorDevice->localToDevice(), outputBounds, contentBounds);
auto abortLayer = [this]() { // The filtered content would not draw anything, or the new device space has an invalid // coordinate system, in which case we mark the current top device as empty so that nothing // draws until the canvas is restored past this saveLayer.
AutoUpdateQRBounds aqr(this);
this->topDevice()->clipRect(SkRect::MakeEmpty(), SkClipOp::kIntersect, /* aa */ false);
};
bool paddedLayer = false; if (layerBounds.isEmpty()) { // The image filter graph does not require any input, so we don't need to actually render // a new layer for the source image. This could be because the image filter itself will not // produce output, or that the filter DAG has no references to the dynamic source image. // In this case it still has an output that we need to render, but do so now since there is // no new layer pushed on the stack and the paired restore() will be a no-op. if (!filters.empty() && !priorDevice->isNoPixelsDevice()) {
SkColorInfo filterColorInfo = priorDevice->imageInfo().colorInfo(); if (rec.fColorSpace) {
filterColorInfo = filterColorInfo.makeColorSpace(sk_ref_sp(rec.fColorSpace));
}
this->internalDrawDeviceWithFilter(/*src=*/nullptr, priorDevice, filters, restorePaint,
DeviceCompatibleWithFilter::kUnknown,
filterColorInfo);
}
// Regardless of if we drew the "restored" image filter or not, mark the layer as empty // until the restore() since we don't care about any of its content.
abortLayer(); return;
} else { // TODO(b/329700315): Once dithers can be anchored more flexibly, we can return to // universally adding padding even for layers w/o filters. This change would simplify layer // prep and restore logic and allow us to flexibly switch the sampling to linear if NN has // issues on certain hardware. if (!filters.empty()) { // Add a buffer of padding so that image filtering can avoid accessing unitialized data // and switch from shader-decal'ing to clamping. auto paddedLayerBounds = layerBounds;
paddedLayerBounds.outset(skif::LayerSpace<SkISize>({1, 1})); if (paddedLayerBounds.left() < layerBounds.left() &&
paddedLayerBounds.top() < layerBounds.top() &&
paddedLayerBounds.right() > layerBounds.right() &&
paddedLayerBounds.bottom() > layerBounds.bottom()) { // The outset was not saturated to INT_MAX, so the transparent pixels can be // preserved.
layerBounds = paddedLayerBounds;
paddedLayer = true;
}
}
}
sk_sp<SkDevice> newDevice; if (strategy == kFullLayer_SaveLayerStrategy) {
SkASSERT(!layerBounds.isEmpty());
SkPixelGeometry geo = rec.fSaveLayerFlags & kPreserveLCDText_SaveLayerFlag
? fProps.pixelGeometry()
: kUnknown_SkPixelGeometry; constauto createInfo = SkDevice::CreateInfo(info, geo, fAllocator.get()); // Use the original paint as a hint so that it includes the image filter
newDevice = priorDevice->createDevice(createInfo, rec.fPaint);
}
bool initBackdrop = (rec.fSaveLayerFlags & kInitWithPrevious_SaveLayerFlag) || rec.fBackdrop; if (!newDevice) { // Either we weren't meant to allocate a full layer, or the full layer creation failed. // Using an explicit NoPixelsDevice lets us reflect what the layer state would have been // on success (or kFull_LayerStrategy) while squashing draw calls that target something that // doesn't exist.
newDevice = sk_make_sp<SkNoPixelsDevice>(SkIRect::MakeWH(layerBounds.width(),
layerBounds.height()),
fProps, this->imageInfo().refColorSpace());
initBackdrop = false;
}
// Clip while the device coordinate space is the identity so it's easy to define the rect that // excludes the added padding pixels. This ensures they remain cleared to transparent black. if (paddedLayer) {
newDevice->clipRect(SkRect::Make(newDevice->devClipBounds().makeInset(1, 1)),
SkClipOp::kIntersect, /*aa=*/false);
}
// Configure device to match determined mapping for any image filters. // The setDeviceCoordinateSystem applies the prior device's global transform since // 'newLayerMapping' only defines the transforms between the two devices and it must be updated // to the global coordinate system.
newDevice->setDeviceCoordinateSystem(
priorDevice->deviceToGlobal() * SkM44(newLayerMapping.layerToDevice()),
SkM44(newLayerMapping.deviceToLayer()) * priorDevice->globalToDevice(),
SkM44(newLayerMapping.layerMatrix()),
layerBounds.left(),
layerBounds.top());
if (initBackdrop) {
SkASSERT(!coverageOnly);
SkPaint backdropPaint;
FilterToSpan backdropAsSpan(rec.fBackdrop); // The new device was constructed to be compatible with 'filter', not necessarily // 'rec.fBackdrop', so allow DrawDeviceWithFilter to transform the prior device contents // if necessary to evaluate the backdrop filter. If no filters are involved, then the // devices differ by integer translations and are always compatible. bool scaleBackdrop = rec.fExperimentalBackdropScale != 1.0f; auto compat = (!filters.empty() || rec.fBackdrop || scaleBackdrop)
? DeviceCompatibleWithFilter::kUnknown : DeviceCompatibleWithFilter::kYes; // Using the color info of 'newDevice' is equivalent to using 'rec.fColorSpace'.
this->internalDrawDeviceWithFilter(priorDevice, // src
newDevice.get(), // dst
backdropAsSpan,
backdropPaint,
compat,
newDevice->imageInfo().colorInfo(),
rec.fExperimentalBackdropScale,
rec.fBackdropTileMode);
}
// Map the local bounds into the top device's coordinate space (this is not // necessarily the full global CTM transform).
SkIRect devBounds; if (localBounds) {
SkRect tmp;
device->localToDevice().mapRect(&tmp, *localBounds); if (!devBounds.intersect(tmp.round(), device->devClipBounds())) {
devBounds.setEmpty();
}
} else {
devBounds = device->devClipBounds();
} if (devBounds.isEmpty()) { return;
}
// This is getting the special image from the current device, which is then drawn into (both by // a client, and the drawClippedToSaveBehind below). Since this is not saving a layer, with its // own device, we need to explicitly copy the back image contents so that its original content // is available when we splat it back later during restore. auto backImage = device->snapSpecial(devBounds, /* forceCopy= */ true); if (!backImage) { return;
}
// we really need the save, so we can wack the fMCRec
this->checkForDeferredSave();
// now detach these from fMCRec so we can pop(). Gets freed after its drawn
std::unique_ptr<Layer> layer = std::move(fMCRec->fLayer);
std::unique_ptr<BackImage> backImage = std::move(fMCRec->fBackImage);
// now do the normal restore()
fMCRec->~MCRec(); // balanced in save()
fMCStack.pop_back();
fMCRec = (MCRec*) fMCStack.back();
if (!fMCRec) { // This was the last record, restored during the destruction of the SkCanvas return;
}
// Draw the layer's device contents into the now-current older device. We can't call public // draw functions since we don't want to record them. if (layer && !layer->fDevice->isNoPixelsDevice() && !layer->fDiscard) {
layer->fDevice->setImmutable();
// Don't go through AutoLayerForImageFilter since device draws are so closely tied to // internalSaveLayer and internalRestore. if (this->predrawNotify()) {
SkDevice* dstDev = this->topDevice(); if (!layer->fImageFilters.empty()) { auto compat = layer->fIncludesPadding ? DeviceCompatibleWithFilter::kYesWithPadding
: DeviceCompatibleWithFilter::kYes;
this->internalDrawDeviceWithFilter(layer->fDevice.get(), // src
dstDev, // dst
layer->fImageFilters,
layer->fPaint,
compat,
layer->fDevice->imageInfo().colorInfo(), /*scaleFactor=*/1.0f, /*srcTileMode=*/SkTileMode::kDecal,
layer->fIsCoverage);
} else { // NOTE: We don't just call internalDrawDeviceWithFilter with a null filter // because we want to take advantage of overridden drawDevice functions for // document-based devices.
SkASSERT(!layer->fIsCoverage && !layer->fIncludesPadding);
SkSamplingOptions sampling;
dstDev->drawDevice(layer->fDevice.get(), sampling, layer->fPaint);
}
}
}
// Reset the clip restriction if the restore went past the save point that had added it. if (this->getSaveCount() < fClipRestrictionSaveCount) {
fClipRestrictionRect.setEmpty();
fClipRestrictionSaveCount = -1;
} // Update the quick-reject bounds in case the restore changed the top device or the // removed save record had included modifications to the clip stack.
fQuickRejectBounds = this->computeDeviceClipBounds();
this->validateClip();
}
void* SkCanvas::accessTopLayerPixels(SkImageInfo* info, size_t* rowBytes, SkIPoint* origin) {
SkPixmap pmap; if (!this->onAccessTopLayerPixels(&pmap)) { return nullptr;
} if (info) {
*info = pmap.info();
} if (rowBytes) {
*rowBytes = pmap.rowBytes();
} if (origin) { // If the caller requested the origin, they presumably are expecting the returned pixels to // be axis-aligned with the root canvas. If the top level device isn't axis aligned, that's // not the case. Until we update accessTopLayerPixels() to accept a coord space matrix // instead of an origin, just don't expose the pixels in that case. Note that this means // that layers with complex coordinate spaces can still report their pixels if the caller // does not ask for the origin (e.g. just to dump its output to a file, etc). if (this->topDevice()->isPixelAlignedToGlobal()) {
*origin = this->topDevice()->getOrigin();
} else { return nullptr;
}
} return pmap.writable_addr();
}
void SkCanvas::androidFramework_setDeviceClipRestriction(const SkIRect& rect) { // The device clip restriction is a surface-space rectangular intersection that cannot be // drawn outside of. The rectangle is remembered so that subsequent resetClip calls still // respect the restriction. Other than clip resetting, all clip operations restrict the set // of renderable pixels, so once set, the restriction will be respected until the canvas // save stack is restored past the point this function was invoked. Unfortunately, the current // implementation relies on the clip stack of the underyling SkDevices, which leads to some // awkward behavioral interactions (see skbug.com/12252). // // Namely, a canvas restore() could undo the clip restriction's rect, and if // setDeviceClipRestriction were called at a nested save level, there's no way to undo just the // prior restriction and re-apply the new one. It also only makes sense to apply to the base // device; any other device for a saved layer will be clipped back to the base device during its // matched restore. As such, we: // - Remember the save count that added the clip restriction and reset the rect to empty when // we've restored past that point to keep our state in sync with the device's clip stack. // - We assert that we're on the base device when this is invoked. // - We assert that setDeviceClipRestriction() is only called when there was no prior // restriction (cannot re-restrict, and prior state must have been reset by restoring the // canvas state). // - Historically, the empty rect would reset the clip restriction but it only could do so // partially since the device's clips wasn't adjusted. Resetting is now handled // automatically via SkCanvas::restore(), so empty input rects are skipped.
SkASSERT(this->topDevice() == this->rootDevice()); // shouldn't be in a nested layer // and shouldn't already have a restriction
SkASSERT(fClipRestrictionSaveCount < 0 && fClipRestrictionRect.isEmpty());
// A non-empty clip restriction immediately applies an intersection op (ignoring the ctm). // so we have to resolve the save.
this->checkForDeferredSave();
AutoUpdateQRBounds aqr(this); // Use clipRegion() since that operates in canvas-space, whereas clipRect() would apply the // device's current transform first.
this->topDevice()->clipRegion(SkRegion(rect), SkClipOp::kIntersect);
}
}
void SkCanvas::onResetClip() {
SkIRect deviceRestriction = this->topDevice()->imageInfo().bounds(); if (fClipRestrictionSaveCount >= 0 && this->topDevice() == this->rootDevice()) { // Respect the device clip restriction when resetting the clip if we're on the base device. // If we're not on the base device, then the "reset" applies to the top device's clip stack, // and the clip restriction will be respected automatically during a restore of the layer. if (!deviceRestriction.intersect(fClipRestrictionRect)) {
deviceRestriction = SkIRect::MakeEmpty();
}
}
void SkCanvas::clipShader(sk_sp<SkShader> sh, SkClipOp op) { if (sh) { if (sh->isOpaque()) { if (op == SkClipOp::kIntersect) { // we don't occlude anything, so skip this call
} else {
SkASSERT(op == SkClipOp::kDifference); // we occlude everything, so set the clip to empty
this->clipRect({0,0,0,0});
}
} else {
this->checkForDeferredSave();
this->onClipShader(std::move(sh), op);
}
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.