blob: 51f681ffcafa0d641a8a99df33437c2f0aaf4232 [file] [log] [blame]
* Copyright 2010 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.
#ifndef SkDevice_DEFINED
#define SkDevice_DEFINED
#include "include/core/SkBlender.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkCustomMesh.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkRegion.h"
#include "include/core/SkShader.h"
#include "include/core/SkSurfaceProps.h"
#include "include/private/SkNoncopyable.h"
#include "src/core/SkMatrixPriv.h"
#include "src/core/SkMatrixProvider.h"
#include "src/core/SkRasterClip.h"
#include "src/shaders/SkShaderBase.h"
class SkBitmap;
struct SkDrawShadowRec;
class SkGlyphRun;
class SkGlyphRunList;
class SkImageFilter;
class SkImageFilterCache;
struct SkIRect;
class SkRasterHandleAllocator;
class SkSpecialImage;
namespace skif { class Mapping; }
namespace skgpu { class BaseDevice; }
class SkBaseDevice : public SkRefCnt, public SkMatrixProvider {
SkBaseDevice(const SkImageInfo&, const SkSurfaceProps&);
* Return ImageInfo for this device. If the canvas is not backed by pixels
* (cpu or gpu), then the info's ColorType will be kUnknown_SkColorType.
const SkImageInfo& imageInfo() const { return fInfo; }
* Return SurfaceProps for this device.
const SkSurfaceProps& surfaceProps() const {
return fSurfaceProps;
SkIRect bounds() const { return SkIRect::MakeWH(this->width(), this->height()); }
* Return the bounds of the device in the coordinate space of the root
* canvas. The root device will have its top-left at 0,0, but other devices
* such as those associated with saveLayer may have a non-zero origin.
void getGlobalBounds(SkIRect* bounds) const {
*bounds = SkMatrixPriv::MapRect(fDeviceToGlobal, SkRect::Make(this->bounds())).roundOut();
SkIRect getGlobalBounds() const {
SkIRect bounds;
return bounds;
* Returns the bounding box of the current clip, in this device's
* coordinate space. No pixels outside of these bounds will be touched by
* draws unless the clip is further modified (at which point this will
* return the updated bounds).
SkIRect devClipBounds() const { return this->onDevClipBounds(); }
int width() const {
return this->imageInfo().width();
int height() const {
return this->imageInfo().height();
bool isOpaque() const {
return this->imageInfo().isOpaque();
bool writePixels(const SkPixmap&, int x, int y);
* Try to get write-access to the pixels behind the device. If successful, this returns true
* and fills-out the pixmap parameter. On success it also bumps the genID of the underlying
* bitmap.
* On failure, returns false and ignores the pixmap parameter.
bool accessPixels(SkPixmap* pmap);
* Try to get read-only-access to the pixels behind the device. If successful, this returns
* true and fills-out the pixmap parameter.
* On failure, returns false and ignores the pixmap parameter.
bool peekPixels(SkPixmap*);
* Return the device's coordinate space transform: this maps from the device's coordinate space
* into the global canvas' space (or root device space). This includes the translation
* necessary to account for the device's origin.
const SkM44& deviceToGlobal() const { return fDeviceToGlobal; }
* Return the inverse of getDeviceToGlobal(), mapping from the global canvas' space (or root
* device space) into this device's coordinate space.
const SkM44& globalToDevice() const { return fGlobalToDevice; }
* DEPRECATED: This asserts that 'getDeviceToGlobal' is a translation matrix with integer
* components. In the future some SkDevices will have more complex device-to-global transforms,
* so getDeviceToGlobal() or getRelativeTransform() should be used instead.
SkIPoint getOrigin() const;
* Returns true when this device's pixel grid is axis aligned with the global coordinate space,
* and any relative translation between the two spaces is in integer pixel units.
bool isPixelAlignedToGlobal() const;
* Get the transformation from this device's coordinate system to the provided device space.
* This transform can be used to draw this device into the provided device, such that once
* that device is drawn to the root device, the net effect will be that this device's contents
* have been transformed by the global CTM.
SkMatrix getRelativeTransform(const SkBaseDevice&) const;
virtual void* getRasterHandle() const { return nullptr; }
const SkMatrixProvider& asMatrixProvider() const { return *this; }
void save() { this->onSave(); }
void restore(const SkM44& ctm) {
void restoreLocal(const SkM44& localToDevice) {
void clipRect(const SkRect& rect, SkClipOp op, bool aa) {
this->onClipRect(rect, op, aa);
void clipRRect(const SkRRect& rrect, SkClipOp op, bool aa) {
this->onClipRRect(rrect, op, aa);
void clipPath(const SkPath& path, SkClipOp op, bool aa) {
this->onClipPath(path, op, aa);
void clipShader(sk_sp<SkShader> sh, SkClipOp op) {
sh = as_SB(sh)->makeWithCTM(this->localToDevice());
if (op == SkClipOp::kDifference) {
sh = as_SB(sh)->makeInvertAlpha();
void clipRegion(const SkRegion& region, SkClipOp op) {
this->onClipRegion(region, op);
void replaceClip(const SkIRect& rect) {
bool clipIsWideOpen() const {
return this->onClipIsWideOpen();
void setLocalToDevice(const SkM44& localToDevice) {
fLocalToDevice = localToDevice;
fLocalToDevice33 = fLocalToDevice.asM33();
void setGlobalCTM(const SkM44& ctm);
virtual void validateDevBounds(const SkIRect&) {}
virtual bool android_utils_clipWithStencil() { return false; }
virtual skgpu::BaseDevice* asGpuDevice() { return nullptr; }
// Ensure that non-RSXForm runs are passed to onDrawGlyphRunList.
void drawGlyphRunList(const SkGlyphRunList& glyphRunList, const SkPaint& paint);
enum TileUsage {
kPossible_TileUsage, //!< the created device may be drawn tiled
kNever_TileUsage, //!< the created device will never be drawn tiled
struct TextFlags {
uint32_t fFlags; // SkPaint::getFlags()
virtual void onSave() {}
virtual void onRestore() {}
virtual void onClipRect(const SkRect& rect, SkClipOp, bool aa) {}
virtual void onClipRRect(const SkRRect& rrect, SkClipOp, bool aa) {}
virtual void onClipPath(const SkPath& path, SkClipOp, bool aa) {}
virtual void onClipShader(sk_sp<SkShader>) {}
virtual void onClipRegion(const SkRegion& deviceRgn, SkClipOp) {}
virtual void onReplaceClip(const SkIRect& rect) {}
virtual bool onClipIsAA() const = 0;
virtual bool onClipIsWideOpen() const = 0;
virtual void onAsRgnClip(SkRegion*) const = 0;
enum class ClipType {
virtual ClipType onGetClipType() const = 0;
// This should strive to be as tight as possible, ideally not just mapping
// the global clip bounds by fToGlobal^-1.
virtual SkIRect onDevClipBounds() const = 0;
/** These are called inside the per-device-layer loop for each draw call.
When these are called, we have already applied any saveLayer operations,
and are handling any looping from the paint.
virtual void drawPaint(const SkPaint& paint) = 0;
virtual void drawPoints(SkCanvas::PointMode mode, size_t count,
const SkPoint[], const SkPaint& paint) = 0;
virtual void drawRect(const SkRect& r,
const SkPaint& paint) = 0;
virtual void drawRegion(const SkRegion& r,
const SkPaint& paint);
virtual void drawOval(const SkRect& oval,
const SkPaint& paint) = 0;
/** By the time this is called we know that abs(sweepAngle) is in the range [0, 360). */
virtual void drawArc(const SkRect& oval, SkScalar startAngle,
SkScalar sweepAngle, bool useCenter, const SkPaint& paint);
virtual void drawRRect(const SkRRect& rr,
const SkPaint& paint) = 0;
// Default impl calls drawPath()
virtual void drawDRRect(const SkRRect& outer,
const SkRRect& inner, const SkPaint&);
* If pathIsMutable, then the implementation is allowed to cast path to a
* non-const pointer and modify it in place (as an optimization). Canvas
* may do this to implement helpers such as drawOval, by placing a temp
* path on the stack to hold the representation of the oval.
virtual void drawPath(const SkPath& path,
const SkPaint& paint,
bool pathIsMutable = false) = 0;
virtual void drawImageRect(const SkImage*, const SkRect* src, const SkRect& dst,
const SkSamplingOptions&, const SkPaint&,
SkCanvas::SrcRectConstraint) = 0;
virtual void drawImageLattice(const SkImage*, const SkCanvas::Lattice&,
const SkRect& dst, SkFilterMode, const SkPaint&);
virtual void drawVertices(const SkVertices*, sk_sp<SkBlender>, const SkPaint&) = 0;
virtual void drawCustomMesh(SkCustomMesh cm, sk_sp<SkBlender>, const SkPaint&) = 0;
virtual void drawShadow(const SkPath&, const SkDrawShadowRec&);
// default implementation calls drawVertices
virtual void drawPatch(const SkPoint cubics[12], const SkColor colors[4],
const SkPoint texCoords[4], sk_sp<SkBlender>, const SkPaint& paint);
// default implementation calls drawVertices
virtual void drawAtlas(const SkRSXform[], const SkRect[], const SkColor[], int count,
sk_sp<SkBlender>, const SkPaint&);
virtual void drawAnnotation(const SkRect&, const char[], SkData*) {}
// Default impl always calls drawRect() with a solid-color paint, setting it to anti-aliased
// only when all edge flags are set. If there's a clip region, it draws that using drawPath,
// or uses clipPath().
virtual void drawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4],
SkCanvas::QuadAAFlags aaFlags, const SkColor4f& color,
SkBlendMode mode);
// Default impl uses drawImageRect per entry, being anti-aliased only when an entry's edge flags
// are all set. If there's a clip region, it will be applied using clipPath().
virtual void drawEdgeAAImageSet(const SkCanvas::ImageSetEntry[], int count,
const SkPoint dstClips[], const SkMatrix preViewMatrices[],
const SkSamplingOptions&, const SkPaint&,
virtual void drawDrawable(SkDrawable*, const SkMatrix*, SkCanvas*);
// Only called with glyphRunLists that do not contain RSXForm.
virtual void onDrawGlyphRunList(const SkGlyphRunList& glyphRunList, const SkPaint& paint) = 0;
// GrSlug handling routines.
virtual sk_sp<GrSlug> convertGlyphRunListToSlug(
const SkGlyphRunList& glyphRunList,
const SkPaint& paint) const;
virtual void drawSlug(GrSlug* slug);
* The SkDevice passed will be an SkDevice which was returned by a call to
* onCreateDevice on this device with kNeverTile_TileExpectation.
* The default implementation calls snapSpecial() and drawSpecial() with the relative transform
* from the input device to this device. The provided SkPaint cannot have a mask filter or
* image filter, and any shader is ignored.
virtual void drawDevice(SkBaseDevice*, const SkSamplingOptions&, const SkPaint&);
* Draw the special image's subset to this device, subject to the given matrix transform instead
* of the device's current local to device matrix.
virtual void drawSpecial(SkSpecialImage*, const SkMatrix& localToDevice,
const SkSamplingOptions&, const SkPaint&);
* Evaluate 'filter' and draw the final output into this device using 'paint'. The 'mapping'
* defines the parameter-to-layer space transform used to evaluate the image filter on 'src',
* and the layer-to-device space transform that is used to draw the result into this device.
* Since 'mapping' fully specifies the transform, this draw function ignores the current
* local-to-device matrix (i.e. just like drawSpecial and drawDevice).
* The final paint must not have an image filter or mask filter set on it; a shader is ignored.
virtual void drawFilteredImage(const skif::Mapping& mapping, SkSpecialImage* src,
const SkImageFilter*, const SkSamplingOptions&, const SkPaint&);
virtual sk_sp<SkSpecialImage> makeSpecial(const SkBitmap&);
virtual sk_sp<SkSpecialImage> makeSpecial(const SkImage*);
// Get a view of the entire device's current contents as an image.
sk_sp<SkSpecialImage> snapSpecial();
// Snap the 'subset' contents from this device, possibly as a read-only view. If 'forceCopy'
// is true then the returned image's pixels must not be affected by subsequent draws into the
// device. When 'forceCopy' is false, the image can be a view into the device's pixels
// (avoiding a copy for performance, at the expense of safety). Default returns null.
virtual sk_sp<SkSpecialImage> snapSpecial(const SkIRect& subset, bool forceCopy = false);
virtual void setImmutable() {}
bool readPixels(const SkPixmap&, int x, int y);
virtual sk_sp<SkSurface> makeSurface(const SkImageInfo&, const SkSurfaceProps&);
virtual bool onPeekPixels(SkPixmap*) { return false; }
* The caller is responsible for "pre-clipping" the dst. The impl can assume that the dst
* image at the specified x,y offset will fit within the device's bounds.
* This is explicitly asserted in readPixels(), the public way to call this.
virtual bool onReadPixels(const SkPixmap&, int x, int y);
* The caller is responsible for "pre-clipping" the src. The impl can assume that the src
* image at the specified x,y offset will fit within the device's bounds.
* This is explicitly asserted in writePixelsDirect(), the public way to call this.
virtual bool onWritePixels(const SkPixmap&, int x, int y);
virtual bool onAccessPixels(SkPixmap*) { return false; }
struct CreateInfo {
CreateInfo(const SkImageInfo& info,
SkPixelGeometry geo,
TileUsage tileUsage,
SkRasterHandleAllocator* allocator)
: fInfo(info)
, fTileUsage(tileUsage)
, fPixelGeometry(geo)
, fAllocator(allocator)
const SkImageInfo fInfo;
const TileUsage fTileUsage;
const SkPixelGeometry fPixelGeometry;
SkRasterHandleAllocator* fAllocator = nullptr;
* Create a new device based on CreateInfo. If the paint is not null, then it represents a
* preview of how the new device will be composed with its creator device (this).
* The subclass may be handed this device in drawDevice(), so it must always return
* a device that it knows how to draw, and that it knows how to identify if it is not of the
* same subclass (since drawDevice is passed a SkBaseDevice*). If the subclass cannot fulfill
* that contract (e.g. PDF cannot support some settings on the paint) it should return NULL,
* and the caller may then decide to explicitly create a bitmapdevice, knowing that later
* it could not call drawDevice with it (but it could call drawSprite or drawBitmap).
virtual SkBaseDevice* onCreateDevice(const CreateInfo&, const SkPaint*) {
return nullptr;
// SkCanvas uses NoPixelsDevice when onCreateDevice fails; but then it needs to be able to
// inspect a layer's device to know if calling drawDevice() later is allowed.
virtual bool isNoPixelsDevice() const { return false; }
friend class SkAndroidFrameworkUtils;
friend class SkCanvas;
friend class SkDraw;
friend class SkSurface_Raster;
friend class DeviceTestingAccess;
void simplifyGlyphRunRSXFormAndRedraw(const SkGlyphRunList& glyphRunList, const SkPaint& paint);
// used to change the backend's pixels (and possibly config/rowbytes)
// but cannot change the width/height, so there should be no change to
// any clip information.
// TODO: move to SkBitmapDevice
virtual void replaceBitmapBackendForRasterSurface(const SkBitmap&) {}
virtual bool forceConservativeRasterClip() const { return false; }
// Configure the device's coordinate spaces, specifying both how its device image maps back to
// the global space (via 'deviceToGlobal') and the initial CTM of the device (via
// 'localToDevice', i.e. what geometry drawn into this device will be transformed with).
// (bufferOriginX, bufferOriginY) defines where the (0,0) pixel the device's backing buffer
// is anchored in the device space. The final device-to-global matrix stored by the SkDevice
// will include a pre-translation by T(deviceOriginX, deviceOriginY), and the final
// local-to-device matrix will have a post-translation of T(-deviceOriginX, -deviceOriginY).
// Returns false if the final device coordinate space is invalid, in which case the canvas
// should discard the device
bool SK_WARN_UNUSED_RESULT setDeviceCoordinateSystem(const SkM44& deviceToGlobal,
const SkM44& localToDevice,
int bufferOriginX, int bufferOriginY);
// Convenience to configure the device to be axis-aligned with the root canvas, but with a
// unique origin.
void setOrigin(const SkM44& globalCTM, int x, int y) {
SkAssertResult(this->setDeviceCoordinateSystem(SkM44(), globalCTM, x, y));
virtual SkImageFilterCache* getImageFilterCache() { return nullptr; }
friend class SkNoPixelsDevice;
friend class SkBitmapDevice;
void privateResize(int w, int h) {
*const_cast<SkImageInfo*>(&fInfo) = fInfo.makeWH(w, h);
const SkImageInfo fInfo;
const SkSurfaceProps fSurfaceProps;
// fDeviceToGlobal and fGlobalToDevice are inverses of each other; there are never that many
// SkDevices, so pay the memory cost to avoid recalculating the inverse.
SkM44 fDeviceToGlobal;
SkM44 fGlobalToDevice;
// fLocalToDevice (inherited from SkMatrixProvider) is the device CTM, not the global CTM
// It maps from local space to the device's coordinate space.
// fDeviceToGlobal * fLocalToDevice will match the canvas' CTM.
using INHERITED = SkRefCnt;
class SkNoPixelsDevice : public SkBaseDevice {
SkNoPixelsDevice(const SkIRect& bounds, const SkSurfaceProps& props,
sk_sp<SkColorSpace> colorSpace = nullptr)
: SkBaseDevice(SkImageInfo::Make(bounds.size(), kUnknown_SkColorType,
kUnknown_SkAlphaType, std::move(colorSpace)),
props) {
// this fails if we enable this assert: DiscardableImageMapTest.GetDiscardableImagesInRectMaxImage
//SkASSERT(bounds.width() >= 0 && bounds.height() >= 0);
this->setOrigin(SkM44(), bounds.left(),;
void resetForNextPicture(const SkIRect& bounds) {
//SkASSERT(bounds.width() >= 0 && bounds.height() >= 0);
this->privateResize(bounds.width(), bounds.height());
this->setOrigin(SkM44(), bounds.left(),;
// SkNoPixelsDevice tracks the clip conservatively in order to respond to some queries as
// accurately as possible while emphasizing performance
void onSave() override;
void onRestore() override;
void onClipRect(const SkRect& rect, SkClipOp op, bool aa) override;
void onClipRRect(const SkRRect& rrect, SkClipOp op, bool aa) override;
void onClipPath(const SkPath& path, SkClipOp op, bool aa) override;
void onClipRegion(const SkRegion& globalRgn, SkClipOp op) override;
void onClipShader(sk_sp<SkShader> shader) override;
void onReplaceClip(const SkIRect& rect) override;
bool onClipIsAA() const override { return this->clip().fIsAA; }
bool onClipIsWideOpen() const override {
return this->clip().fIsRect &&
this->onDevClipBounds() == this->bounds();
void onAsRgnClip(SkRegion* rgn) const override {
ClipType onGetClipType() const override;
SkIRect onDevClipBounds() const override { return this->clip().fClipBounds; }
void drawPaint(const SkPaint& paint) override {}
void drawPoints(SkCanvas::PointMode, size_t, const SkPoint[], const SkPaint&) override {}
void drawImageRect(const SkImage*, const SkRect*, const SkRect&,
const SkSamplingOptions&, const SkPaint&,
SkCanvas::SrcRectConstraint) override {}
void drawRect(const SkRect&, const SkPaint&) override {}
void drawOval(const SkRect&, const SkPaint&) override {}
void drawRRect(const SkRRect&, const SkPaint&) override {}
void drawPath(const SkPath&, const SkPaint&, bool) override {}
void drawDevice(SkBaseDevice*, const SkSamplingOptions&, const SkPaint&) override {}
void drawVertices(const SkVertices*, sk_sp<SkBlender>, const SkPaint&) override {}
void drawCustomMesh(SkCustomMesh, sk_sp<SkBlender>, const SkPaint&) override {}
void drawFilteredImage(const skif::Mapping&, SkSpecialImage* src, const SkImageFilter*,
const SkSamplingOptions&, const SkPaint&) override {}
void onDrawGlyphRunList(const SkGlyphRunList& glyphRunList, const SkPaint& paint) override {}
bool isNoPixelsDevice() const override { return true; }
struct ClipState {
SkIRect fClipBounds;
int fDeferredSaveCount;
bool fIsAA;
bool fIsRect;
ClipState(const SkIRect& bounds, bool isAA, bool isRect)
: fClipBounds(bounds)
, fDeferredSaveCount(0)
, fIsAA(isAA)
, fIsRect(isRect) {}
void op(SkClipOp op, const SkM44& transform, const SkRect& bounds,
bool isAA, bool fillsBounds);
const ClipState& clip() const { return fClipStack.back(); }
ClipState& writableClip();
void resetClipStack() {
fClipStack.emplace_back(this->bounds(), /*isAA=*/false, /*isRect=*/true);
SkSTArray<4, ClipState> fClipStack;
using INHERITED = SkBaseDevice;
class SkAutoDeviceTransformRestore : SkNoncopyable {
SkAutoDeviceTransformRestore(SkBaseDevice* device, const SkMatrix& localToDevice)
: fDevice(device)
, fPrevLocalToDevice(device->localToDevice())
~SkAutoDeviceTransformRestore() {
SkBaseDevice* fDevice;
const SkM44 fPrevLocalToDevice;