Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/gfx/skia/skia/src/core/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 113 kB image not shown  

Quelle  SkCanvas.cpp   Sprache: C

 
/*
 * 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.
 */


#include "include/core/SkCanvas.h"

#include "include/core/SkAlphaType.h"
#include "include/core/SkBitmap.h"
#include "include/core/SkBlendMode.h"
#include "include/core/SkBlender.h"
#include "include/core/SkBlurTypes.h"
#include "include/core/SkColorFilter.h"
#include "include/core/SkColorSpace.h"
#include "include/core/SkColorType.h"
#include "include/core/SkImage.h"
#include "include/core/SkImageFilter.h"
#include "include/core/SkMaskFilter.h"
#include "include/core/SkPath.h"
#include "include/core/SkPathEffect.h"
#include "include/core/SkPicture.h"
#include "include/core/SkPixmap.h"
#include "include/core/SkRRect.h"
#include "include/core/SkRSXform.h"
#include "include/core/SkRasterHandleAllocator.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkRegion.h"
#include "include/core/SkShader.h"
#include "include/core/SkStrokeRec.h"
#include "include/core/SkSurface.h"
#include "include/core/SkTextBlob.h"
#include "include/core/SkTileMode.h"
#include "include/core/SkTypes.h"
#include "include/core/SkVertices.h"
#include "include/private/base/SkDebug.h"
#include "include/private/base/SkFloatingPoint.h"
#include "include/private/base/SkSafe32.h"
#include "include/private/base/SkTPin.h"
#include "include/private/base/SkTemplates.h"
#include "include/private/base/SkTo.h"
#include "include/private/chromium/Slug.h"
#include "include/utils/SkNoDrawCanvas.h"
#include "src/base/SkEnumBitMask.h"
#include "src/base/SkMSAN.h"
#include "src/core/SkBlenderBase.h"
#include "src/core/SkBlurMaskFilterImpl.h"
#include "src/core/SkCanvasPriv.h"
#include "src/core/SkDevice.h"
#include "src/core/SkImageFilterTypes.h"
#include "src/core/SkImageFilter_Base.h"
#include "src/core/SkImagePriv.h"
#include "src/core/SkLatticeIter.h"
#include "src/core/SkMaskFilterBase.h"
#include "src/core/SkMatrixPriv.h"
#include "src/core/SkPaintPriv.h"
#include "src/core/SkSpecialImage.h"
#include "src/core/SkSurfacePriv.h"
#include "src/core/SkTraceEvent.h"
#include "src/core/SkVerticesPriv.h"
#include "src/effects/colorfilters/SkColorFilterBase.h"
#include "src/image/SkSurface_Base.h"
#include "src/text/GlyphRun.h"
#include "src/utils/SkPatchUtils.h"

#include <algorithm>
#include <memory>
#include <new>
#include <optional>
#include <tuple>
#include <utility>

#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);

using Slug = sktext::gpu::Slug;

///////////////////////////////////////////////////////////////////////////////////////////////////

SK_MAKE_BITMASK_OPS(SkCanvas::PredrawFlags)

/*
 *  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;

    const SkISize size = this->getBaseLayerSize();
    const SkRect bounds = SkRect::MakeIWH(size.width(), size.height());

    // 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) {
            return false;   // we're in a saveLayer, so conservatively don't assume we'll overwrite
        }
        if (!root->isClipWideOpen()) {
            return false;
        }
    }

    if (rect) {
        if (!this->getTotalMatrix().isScaleTranslate()) {
            return false// conservative
        }

        SkRect devRect;
        this->getTotalMatrix().mapRectScaleTranslate(&devRect, *rect);
        if (!devRect.contains(bounds)) {
            return false;
        }
    }

    if (paint) {
        SkPaint::Style paintStyle = paint->getStyle();
        if (!(paintStyle == SkPaint::kFill_Style ||
              paintStyle == SkPaint::kStrokeAndFill_Style)) {
            return false;
        }
        if (paint->getMaskFilter() || paint->getPathEffect() || paint->getImageFilter()) {
            return false// conservative
        }
    }
    return SkPaintPriv::Overwrites(paint, overrideOpacity);
}

///////////////////////////////////////////////////////////////////////////////////////////////////

bool SkCanvas::predrawNotify(bool willOverwritesEntireSurface) {
    if (fSurfaceBase) {
        if (!fSurfaceBase->aboutToDraw(willOverwritesEntireSurface
                                       ? SkSurface::kDiscard_ContentChangeMode
                                       : SkSurface::kRetain_ContentChangeMode)) {
            return false;
        }
    }
    return true;
}

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)) {
            return false;
        }
    }
    return true;
}

///////////////////////////////////////////////////////////////////////////////

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());
}

SkCanvas::BackImage::BackImage(sk_sp<SkSpecialImage> img, SkIPoint loc)
                               :fImage(img), fLoc(loc) {}
SkCanvas::BackImage::BackImage(const BackImage&) = default;
SkCanvas::BackImage::BackImage(BackImage&&) = default;
SkCanvas::BackImage& SkCanvas::BackImage::operator=(const BackImage&) = default;
SkCanvas::BackImage::~BackImage() = default;

SkCanvas::MCRec::MCRec(SkDevice* device) : fDevice(device) {
    SkASSERT(fDevice);
}

SkCanvas::MCRec::MCRec(const MCRec* prev) : fDevice(prev->fDevice), fMatrix(prev->fMatrix) {
    SkASSERT(fDevice);
}

SkCanvas::MCRec::~MCRec() {}

void SkCanvas::MCRec::newLayer(sk_sp<SkDevice> layerDevice,
                               FilterSpan filters,
                               const SkPaint& restorePaint,
                               bool layerIsCoverage,
                               bool includesPadding) {
    SkASSERT(!fBackImage);
    fLayer = std::make_unique<Layer>(std::move(layerDevice),
                                     filters,
                                     restorePaint,
                                     layerIsCoverage,
                                     includesPadding);
    fDevice = fLayer->fDevice.get();
}

void SkCanvas::MCRec::reset(SkDevice* device) {
    SkASSERT(!fLayer);
    SkASSERT(device);
    SkASSERT(fDeferredSaveCount == 0);
    fDevice = device;
    fMatrix.setIdentity();
}

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();
    }

private:
    SkCanvas* fCanvas;

    AutoUpdateQRBounds(AutoUpdateQRBounds&&) = delete;
    AutoUpdateQRBounds(const AutoUpdateQRBounds&) = delete;
    AutoUpdateQRBounds& operator=(AutoUpdateQRBounds&&) = delete;
    AutoUpdateQRBounds& operator=(const AutoUpdateQRBounds&) = delete;
};

/////////////////////////////////////////////////////////////////////////////

std::optional<AutoLayerForImageFilter> SkCanvas::aboutToDraw(
        const SkPaint& paint,
        const SkRect* rawBounds,
        SkEnumBitMask<PredrawFlags> flags) {
    if (flags & PredrawFlags::kCheckForOverwrite) {
        if (!this->predrawNotify(rawBounds, &paint, flags)) {
            return std::nullopt;
        }
    } else {
        if (!this->predrawNotify()) {
            return std::nullopt;
        }
    }

    // TODO: Eventually all devices will use this code path and this will just test 'flags'.
    const bool skipMaskFilterLayer = (flags & PredrawFlags::kSkipMaskFilterAutoLayer) ||
                                     !this->topDevice()->useDrawCoverageMaskForMaskFilters();
    return std::optional<AutoLayerForImageFilter>(
            std::in_place, this, paint, rawBounds, skipMaskFilterLayer);
}

std::optional<AutoLayerForImageFilter> SkCanvas::aboutToDraw(
        const SkPaint& paint,
        const SkRect* rawBounds) {
    return this->aboutToDraw(paint, rawBounds, PredrawFlags::kNone);
}

////////////////////////////////////////////////////////////////////////////

void SkCanvas::resetForNextPicture(const SkIRect& bounds) {
    this->restoreToCount(1);

    // 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());
    }

    fMCRec->reset(fRootDevice.get());
    fQuickRejectBounds = this->computeDeviceClipBounds();
}

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());

    fSurfaceBase = nullptr;
    fRootDevice = std::move(device);
    fScratchGlyphRunBuilder = std::make_unique<sktext::GlyphRunBuilder>();
    fQuickRejectBounds = this->computeDeviceClipBounds();
}

SkCanvas::SkCanvas() : fMCStack(sizeof(MCRec), fMCRecStorage, sizeof(fMCRecStorage)) {
    this->init(nullptr);
}

SkCanvas::SkCanvas(int width, int height, const SkSurfaceProps* props)
        : fMCStack(sizeof(MCRec), fMCRecStorage, sizeof(fMCRecStorage))
        , fProps(SkSurfacePropsCopyOrDefault(props)) {
    this->init(sk_make_sp<SkNoPixelsDevice>(
            SkIRect::MakeWH(std::max(width, 0), std::max(height, 0)), fProps));
}

SkCanvas::SkCanvas(const SkIRect& bounds)
        : fMCStack(sizeof(MCRec), fMCRecStorage, sizeof(fMCRecStorage)) {
    SkIRect r = bounds.isEmpty() ? SkIRect::MakeEmpty() : bounds;
    this->init(sk_make_sp<SkNoPixelsDevice>(r, fProps));
}

SkCanvas::SkCanvas(sk_sp<SkDevice> device)
        : fMCStack(sizeof(MCRec), fMCRecStorage, sizeof(fMCRecStorage))
        , fProps(device->surfaceProps()) {
    this->init(std::move(device));
}

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
}

SkSurface* SkCanvas::getSurface() const {
    return fSurfaceBase;
}

SkISize SkCanvas::getBaseLayerSize() const {
    return this->rootDevice()->imageInfo().dimensions();
}

SkDevice* SkCanvas::topDevice() const {
    SkASSERT(fMCRec->fDevice);
    return fMCRec->fDevice;
}

bool SkCanvas::readPixels(const SkPixmap& pm, int x, int y) {
    return pm.addr() && this->rootDevice()->readPixels(pm, x, y);
}

bool SkCanvas::readPixels(const SkImageInfo& dstInfo, void* dstP, size_t rowBytes, int x, int y) {
    return this->readPixels({ dstInfo, dstP, rowBytes}, x, y);
}

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);
    }
    return false;
}

bool SkCanvas::writePixels(const SkImageInfo& srcInfo, const void* 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()})) {
        return false;
    }

    // Tell our owning surface to bump its generation ID.
    const bool completeOverwrite = srcRect.size() == device->imageInfo().dimensions();
    if (!this->predrawNotify(completeOverwrite)) {
        return false;
    }

    // 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);
}

//////////////////////////////////////////////////////////////////////////////

void SkCanvas::checkForDeferredSave() {
    if (fMCRec->fDeferredSaveCount > 0) {
        this->doSave();
    }
}

int SkCanvas::getSaveCount() const {
#ifdef SK_DEBUG
    int count = 0;
    SkDeque::Iter iter(fMCStack, SkDeque::Iter::kFront_IterStart);
    for (;;) {
        const MCRec* rec = (const MCRec*)iter.next();
        if (!rec) {
            break;
        }
        count += 1 + rec->fDeferredSaveCount;
    }
    SkASSERT(count == fSaveCount);
#endif
    return fSaveCount;
}

int SkCanvas::save() {
    fSaveCount += 1;
    fMCRec->fDeferredSaveCount += 1;
    return this->getSaveCount() - 1;  // return our prev value
}

void SkCanvas::doSave() {
    this->willSave();

    SkASSERT(fMCRec->fDeferredSaveCount > 0);
    fMCRec->fDeferredSaveCount -= 1;
    this->internalSave();
}

void SkCanvas::restore() {
    if (fMCRec->fDeferredSaveCount > 0) {
        SkASSERT(fSaveCount > 1);
        fSaveCount -= 1;
        fMCRec->fDeferredSaveCount -= 1;
    } else {
        // check for underflow
        if (fMCStack.count() > 1) {
            this->willRestore();
            SkASSERT(fSaveCount > 1);
            fSaveCount -= 1;
            this->internalRestore();
            this->didRestore();
        }
    }
}

void SkCanvas::restoreToCount(int count) {
    // safety check
    if (count < 1) {
        count = 1;
    }

    int n = this->getSaveCount() - count;
    for (int i = 0; i < n; ++i) {
        this->restore();
    }
}

void SkCanvas::internalSave() {
    fMCRec = new (fMCStack.push_back()) MCRec(fMCRec);

    this->topDevice()->pushClipStack();
}

int SkCanvas::saveLayer(const SkRect* bounds, const SkPaint* paint) {
    return this->saveLayer(SaveLayerRec(bounds, paint, 0));
}

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)) {}

    operator SkCanvas::FilterSpan() {
        return fFilter ? SkCanvas::FilterSpan{&fFilter, 1} : SkCanvas::FilterSpan{};
    }

    sk_sp<SkImageFilter> fFilter;
};

// 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).
    static const int 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;
    }

    if (layerBounds.width() > maxLayerDim || layerBounds.height() > maxLayerDim) {
        skif::LayerSpace<SkIRect> newLayerBounds(
                SkIRect::MakeWH(std::min(layerBounds.width(), maxLayerDim),
                                std::min(layerBounds.height(), maxLayerDim)));
        SkMatrix adjust = SkMatrix::MakeRectToRect(SkRect::Make(SkIRect(layerBounds)),
                                                   SkRect::Make(SkIRect(newLayerBounds)),
                                                   SkMatrix::kFill_ScaleToFit);
        if (!mapping.adjustLayerSpace(adjust)) {
            return {};
        } else {
            layerBounds = newLayerBounds;
        }
    }

    return std::make_pair(mapping, layerBounds);
}

// 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);

    sk_sp<SkColorSpace> filterColorSpace = filterColorInfo.refColorSpace();

    const SkColorType filterColorType =
            srcIsCoverageLayer ? kAlpha_8_SkColorType : image_filter_color_type(filterColorInfo);

    // '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();
        } else if (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
        } else if (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);
        } else if (!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);

    sk_sp<SkImageFilter> paintFilter = rec.fPaint ? rec.fPaint->refImageFilter() : nullptr;
    FilterSpan filters = paintFilter ? FilterSpan{&paintFilter, 1} : rec.fFilters;
    if (filters.size() > kMaxFiltersPerLayer) {
        filters = filters.first(kMaxFiltersPerLayer);
    }
    const SkColorFilter* cf = restorePaint.getColorFilter();
    const SkBlender* blender = restorePaint.getBlender();

    // 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.
    const bool drawDeviceMustFillClip = filters.empty() &&
            ((cf && as_CFB(cf)->affectsTransparentBlack()) ||
                (blender && as_BB(blender)->affectsTransparentBlack()));
    const bool 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);
    };

    if (!mappingAndBounds) {
        abortLayer();
        return;
    }

    std::tie(newLayerMapping, layerBounds) = *mappingAndBounds;

    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());

        SkColorType layerColorType;
        if (coverageOnly) {
            layerColorType = kAlpha_8_SkColorType;
        } else {
            layerColorType = SkToBool(rec.fSaveLayerFlags & kF16ColorType)
                                    ? kRGBA_F16_SkColorType
                                    : image_filter_color_type(priorDevice->imageInfo().colorInfo());
        }
        SkImageInfo info =
                SkImageInfo::Make(layerBounds.width(),
                                  layerBounds.height(),
                                  layerColorType,
                                  kPremul_SkAlphaType,
                                  rec.fColorSpace ? sk_ref_sp(rec.fColorSpace)
                                                  : priorDevice->imageInfo().refColorSpace());

        SkPixelGeometry geo = rec.fSaveLayerFlags & kPreserveLCDText_SaveLayerFlag
                                      ? fProps.pixelGeometry()
                                      : kUnknown_SkPixelGeometry;
        const auto 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);
    }

    fMCRec->newLayer(std::move(newDevice), filters, restorePaint, coverageOnly, paddedLayer);
    fQuickRejectBounds = this->computeDeviceClipBounds();
}

int SkCanvas::saveLayerAlphaf(const SkRect* bounds, float alpha) {
    if (alpha >= 1.0f) {
        return this->saveLayer(bounds, nullptr);
    } else {
        SkPaint tmpPaint;
        tmpPaint.setAlphaf(alpha);
        return this->saveLayer(bounds, &tmpPaint);
    }
}

void SkCanvas::internalSaveBehind(const SkRect* localBounds) {
    SkDevice* device = this->topDevice();

    // 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();

    fMCRec->fBackImage =
            std::make_unique<BackImage>(BackImage{std::move(backImage), devBounds.topLeft()});

    SkPaint paint;
    paint.setBlendMode(SkBlendMode::kClear);
    this->drawClippedToSaveBehind(paint);
}

void SkCanvas::internalRestore() {
    SkASSERT(!fMCStack.empty());

    // 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;
    }

    this->topDevice()->popClipStack();
    this->topDevice()->setGlobalCTM(fMCRec->fMatrix);

    if (backImage) {
        SkPaint paint;
        paint.setBlendMode(SkBlendMode::kDstOver);
        this->topDevice()->drawSpecial(backImage->fImage.get(),
                                       SkMatrix::Translate(backImage->fLoc),
                                       SkSamplingOptions(),
                                       paint);
    }

    // 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();
}

sk_sp<SkSurface> SkCanvas::makeSurface(const SkImageInfo& info, const SkSurfaceProps* props) {
    if (nullptr == props) {
        props = &fProps;
    }
    return this->onNewSurface(info, *props);
}

sk_sp<SkSurface> SkCanvas::onNewSurface(const SkImageInfo& info, const SkSurfaceProps& props) {
    return this->rootDevice()->makeSurface(info, props);
}

SkImageInfo SkCanvas::imageInfo() const {
    return this->onImageInfo();
}

SkImageInfo SkCanvas::onImageInfo() const {
    return this->rootDevice()->imageInfo();
}

bool SkCanvas::getProps(SkSurfaceProps* props) const {
    return this->onGetProps(props, /*top=*/false);
}

SkSurfaceProps SkCanvas::getBaseProps() const {
    SkSurfaceProps props;
    this->onGetProps(&props, /*top=*/false);
    return props;
}

SkSurfaceProps SkCanvas::getTopProps() const {
    SkSurfaceProps props;
    this->onGetProps(&props, /*top=*/true);
    return props;
}

bool SkCanvas::onGetProps(SkSurfaceProps* props, bool top) const {
    if (props) {
        *props = top ? topDevice()->surfaceProps() : fProps;
    }
    return true;
}

bool SkCanvas::peekPixels(SkPixmap* pmap) {
    return this->onPeekPixels(pmap);
}

bool SkCanvas::onPeekPixels(SkPixmap* pmap) {
    return this->rootDevice()->peekPixels(pmap);
}

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();
}

bool SkCanvas::onAccessTopLayerPixels(SkPixmap* pmap) {
    return this->topDevice()->accessPixels(pmap);
}

/////////////////////////////////////////////////////////////////////////////

void SkCanvas::translate(SkScalar dx, SkScalar dy) {
    if (dx || dy) {
        this->checkForDeferredSave();
        fMCRec->fMatrix.preTranslate(dx, dy);

        this->topDevice()->setGlobalCTM(fMCRec->fMatrix);

        this->didTranslate(dx,dy);
    }
}

void SkCanvas::scale(SkScalar sx, SkScalar sy) {
    if (sx != 1 || sy != 1) {
        this->checkForDeferredSave();
        fMCRec->fMatrix.preScale(sx, sy);

        this->topDevice()->setGlobalCTM(fMCRec->fMatrix);

        this->didScale(sx, sy);
    }
}

void SkCanvas::rotate(SkScalar degrees) {
    SkMatrix m;
    m.setRotate(degrees);
    this->concat(m);
}

void SkCanvas::rotate(SkScalar degrees, SkScalar px, SkScalar py) {
    SkMatrix m;
    m.setRotate(degrees, px, py);
    this->concat(m);
}

void SkCanvas::skew(SkScalar sx, SkScalar sy) {
    SkMatrix m;
    m.setSkew(sx, sy);
    this->concat(m);
}

void SkCanvas::concat(const SkMatrix& matrix) {
    if (matrix.isIdentity()) {
        return;
    }
    this->concat(SkM44(matrix));
}

void SkCanvas::internalConcat44(const SkM44& m) {
    this->checkForDeferredSave();

    fMCRec->fMatrix.preConcat(m);

    this->topDevice()->setGlobalCTM(fMCRec->fMatrix);
}

void SkCanvas::concat(const SkM44& m) {
    this->internalConcat44(m);
    // notify subclasses
    this->didConcat44(m);
}

void SkCanvas::internalSetMatrix(const SkM44& m) {
    fMCRec->fMatrix = m;

    this->topDevice()->setGlobalCTM(fMCRec->fMatrix);
}

void SkCanvas::setMatrix(const SkMatrix& matrix) {
    this->setMatrix(SkM44(matrix));
}

void SkCanvas::setMatrix(const SkM44& m) {
    this->checkForDeferredSave();
    this->internalSetMatrix(m);
    this->didSetM44(m);
}

void SkCanvas::resetMatrix() {
    this->setMatrix(SkM44());
}

//////////////////////////////////////////////////////////////////////////////

void SkCanvas::clipRect(const SkRect& rect, SkClipOp op, bool doAA) {
    if (!rect.isFinite()) {
        return;
    }
    this->checkForDeferredSave();
    ClipEdgeStyle edgeStyle = doAA ? kSoft_ClipEdgeStyle : kHard_ClipEdgeStyle;
    this->onClipRect(rect.makeSorted(), op, edgeStyle);
}

void SkCanvas::onClipRect(const SkRect& rect, SkClipOp op, ClipEdgeStyle edgeStyle) {
    SkASSERT(rect.isSorted());
    const bool isAA = kSoft_ClipEdgeStyle == edgeStyle;

    AutoUpdateQRBounds aqr(this);
    this->topDevice()->clipRect(rect, op, isAA);
}

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());

    if (fClipRestrictionSaveCount < 0 && !rect.isEmpty()) {
        fClipRestrictionRect = rect;
        fClipRestrictionSaveCount = this->getSaveCount();

        // 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::internal_private_resetClip() {
    this->checkForDeferredSave();
    this->onResetClip();
}

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();
        }
    }

    AutoUpdateQRBounds aqr(this);
    this->topDevice()->replaceClip(deviceRestriction);
}

void SkCanvas::clipRRect(const SkRRect& rrect, SkClipOp op, bool doAA) {
    this->checkForDeferredSave();
    ClipEdgeStyle edgeStyle = doAA ? kSoft_ClipEdgeStyle : kHard_ClipEdgeStyle;
    if (rrect.isRect()) {
        this->onClipRect(rrect.getBounds(), op, edgeStyle);
    } else {
        this->onClipRRect(rrect, op, edgeStyle);
    }
}

void SkCanvas::onClipRRect(const SkRRect& rrect, SkClipOp op, ClipEdgeStyle edgeStyle) {
    bool isAA = kSoft_ClipEdgeStyle == edgeStyle;

    AutoUpdateQRBounds aqr(this);
    this->topDevice()->clipRRect(rrect, op, isAA);
}

void SkCanvas::clipPath(const SkPath& path, SkClipOp op, bool doAA) {
    this->checkForDeferredSave();
    ClipEdgeStyle edgeStyle = doAA ? kSoft_ClipEdgeStyle : kHard_ClipEdgeStyle;

    if (!path.isInverseFillType() && fMCRec->fMatrix.asM33().rectStaysRect()) {
        SkRect r;
        if (path.isRect(&r)) {
            this->onClipRect(r, op, edgeStyle);
            return;
        }
        SkRRect rrect;
        if (path.isOval(&r)) {
            rrect.setOval(r);
            this->onClipRRect(rrect, op, edgeStyle);
            return;
        }
        if (path.isRRect(&rrect)) {
            this->onClipRRect(rrect, op, edgeStyle);
            return;
        }
    }

    this->onClipPath(path, op, edgeStyle);
}

void SkCanvas::onClipPath(const SkPath& path, SkClipOp op, ClipEdgeStyle edgeStyle) {
    bool isAA = kSoft_ClipEdgeStyle == edgeStyle;

    AutoUpdateQRBounds aqr(this);
    this->topDevice()->clipPath(path, op, isAA);
}

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);
        }
    }
}

void SkCanvas::onClipShader(sk_sp<SkShader> sh, SkClipOp op) {
    AutoUpdateQRBounds aqr(this);
    this->topDevice()->clipShader(sh, op);
}

void SkCanvas::clipRegion(const SkRegion& rgn, SkClipOp op) {
    this->checkForDeferredSave();
    this->onClipRegion(rgn, op);
}

void SkCanvas::onClipRegion(const SkRegion& rgn, SkClipOp op) {
    AutoUpdateQRBounds aqr(this);
    this->topDevice()->clipRegion(rgn, op);
}

void SkCanvas::validateClip() const {
#ifdef SK_DEBUG
#ifndef SK_DISABLE_SLOW_DEBUG_VALIDATION
    SkRect tmp = this->computeDeviceClipBounds();
    if (this->isClipEmpty()) {
        SkASSERT(fQuickRejectBounds.isEmpty());
    } else {
        SkASSERT(tmp == fQuickRejectBounds);
    }
#endif
#endif
}

bool SkCanvas::androidFramework_isClipAA() const {
    return this->topDevice()->isClipAntiAliased();
}

void SkCanvas::temporary_internal_getRgnClip(SkRegion* rgn) {
    rgn->setEmpty();
    SkDevice* device = this->topDevice();
    if (device && device->isPixelAlignedToGlobal()) {
        device->android_utils_clipAsRgn(rgn);
        SkIPoint origin = device->getOrigin();
        if (origin.x() | origin.y()) {
            rgn->translate(origin.x(), origin.y());
        }
    }
}

--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=92 H=94 G=92

¤ Dauer der Verarbeitung: 0.26 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

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.