Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 16d5b0a

Browse files
lhkbobSkia Commit-Bot
authored andcommitted
Consolidate image filter evaluation into SkBaseDevice
This CL does several things: 1. SkDevice subclasses that implement drawSpecial() now assert that the provided SkPaint does not have an SkImageFilter. 2. The SkGpuDevice implementation of drawSpecial() is simplified to go through the draw_texture_producer code path. This removes duplicate logic for paint and texture handling, and also allows simple drawSpecials to actually use the texture op. 3. SkCanvas' drawInternalDevice and onDrawImage now define the skif::Mappings that define the coord transforms for filter eval and final draw, before calling the new drawFilteredImage on SkDevice. - This is implemented in SkBaseDevice, but subclasses can extend it. - I had originally put it as a static function in SkCanvas, but that meant I had to forward declare more types in SkCanvas.h than I liked. - A lot of the skif::Context properties are dependent on the dst device anyways, so having a drawFilteredImage on device seems okay, now that filter eval is separated from how to draw a special image. - Once drawSpecial can take a matrix transform, and once image filters know how to handle non-(0,0) source origins, this will simplify further. 4. Swapped the behavior of SkDevice::getRelativeTransform. I originally wrote it so that it would be the matrix mapping from arg to caller. After finally using it here (its original purpose), I realized it reads better to be the matrix from caller to arg. - Previously this was never called, so compatibility isn't a problem. Bug: skia:9558 Change-Id: If855903110947a11ae89b1e50cf5848a3c5dbecd Reviewed-on: https://skia-review.googlesource.com/c/skia/+/308768 Commit-Queue: Michael Ludwig <michaelludwig@google.com> Reviewed-by: Brian Salomon <bsalomon@google.com>
1 parent 241f080 commit 16d5b0a

File tree

9 files changed

+160
-175
lines changed

9 files changed

+160
-175
lines changed

src/core/SkBitmapDevice.cpp

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -590,36 +590,13 @@ void SkBitmapDevice::drawAtlas(const SkImage* atlas, const SkRSXform xform[],
590590

591591
///////////////////////////////////////////////////////////////////////////////
592592

593-
void SkBitmapDevice::drawSpecial(SkSpecialImage* src, int x, int y, const SkPaint& origPaint) {
593+
void SkBitmapDevice::drawSpecial(SkSpecialImage* src, int x, int y, const SkPaint& paint) {
594594
SkASSERT(!src->isTextureBacked());
595-
SkASSERT(!origPaint.getMaskFilter());
596-
597-
sk_sp<SkSpecialImage> filteredImage;
598-
SkTCopyOnFirstWrite<SkPaint> paint(origPaint);
599-
600-
if (SkImageFilter* filter = paint->getImageFilter()) {
601-
SkIPoint offset = SkIPoint::Make(0, 0);
602-
const SkMatrix matrix = SkMatrix::Concat(
603-
SkMatrix::Translate(SkIntToScalar(-x), SkIntToScalar(-y)), this->localToDevice());
604-
const SkIRect clipBounds = fRCStack.rc().getBounds().makeOffset(-x, -y);
605-
sk_sp<SkImageFilterCache> cache(this->getImageFilterCache());
606-
SkImageFilter_Base::Context ctx(matrix, clipBounds, cache.get(), fBitmap.colorType(),
607-
fBitmap.colorSpace(), src);
608-
609-
filteredImage = as_IFB(filter)->filterImage(ctx).imageAndOffset(&offset);
610-
if (!filteredImage) {
611-
return;
612-
}
613-
614-
src = filteredImage.get();
615-
paint.writable()->setImageFilter(nullptr);
616-
x += offset.x();
617-
y += offset.y();
618-
}
595+
SkASSERT(!paint.getMaskFilter() && !paint.getImageFilter());
619596

620597
SkBitmap resultBM;
621598
if (src->getROPixels(&resultBM)) {
622-
BDDraw(this).drawSprite(resultBM, x, y, *paint);
599+
BDDraw(this).drawSprite(resultBM, x, y, paint);
623600
}
624601
}
625602

src/core/SkCanvas.cpp

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1411,21 +1411,47 @@ void SkCanvas::internalDrawDevice(SkBaseDevice* srcDev, const SkPaint* paint) {
14111411
SkBaseDevice* dstDev = iter.fDevice;
14121412
check_drawdevice_colorspaces(dstDev->imageInfo().colorSpace(),
14131413
srcDev->imageInfo().colorSpace());
1414-
paint = &draw.paint();
1414+
1415+
SkTCopyOnFirstWrite<SkPaint> noFilterPaint(*paint);
14151416
SkImageFilter* filter = paint->getImageFilter();
1416-
// TODO(michaelludwig) - Devices aren't created with complex coordinate systems yet,
1417-
// so it should always be possible to use the relative origin. Once drawDevice() and
1418-
// drawSpecial() take an SkMatrix, this can switch to getRelativeTransform() instead.
1419-
SkIPoint pos = srcDev->getOrigin() - dstDev->getOrigin();
14201417
if (filter) {
1421-
sk_sp<SkSpecialImage> specialImage = srcDev->snapSpecial();
1422-
if (specialImage) {
1423-
check_drawdevice_colorspaces(dstDev->imageInfo().colorSpace(),
1424-
specialImage->getColorSpace());
1425-
dstDev->drawSpecial(specialImage.get(), pos.x(), pos.y(), *paint);
1418+
// Check if the image filter was just a color filter (this is the same optimization
1419+
// we apply in AutoLayerForImageFilter but handles explicitly saved layers).
1420+
sk_sp<SkColorFilter> cf = image_to_color_filter(*paint);
1421+
if (cf) {
1422+
noFilterPaint.writable()->setColorFilter(std::move(cf));
1423+
filter = nullptr;
14261424
}
1425+
1426+
noFilterPaint.writable()->setImageFilter(nullptr);
1427+
}
1428+
1429+
SkASSERT(!noFilterPaint->getImageFilter());
1430+
if (!filter) {
1431+
// Can draw the src device's buffer w/o any extra image filter evaluation
1432+
// (although this draw may include color filter processing extracted from the IF DAG).
1433+
// TODO (michaelludwig) - Once drawSpecial can take a matrix, drawDevice should take
1434+
// no extra arguments and internally just use the relative transform from src to dst.
1435+
SkIPoint pos = srcDev->getOrigin() - dstDev->getOrigin();
1436+
dstDev->drawDevice(srcDev, pos.x(), pos.y(), *noFilterPaint);
14271437
} else {
1428-
dstDev->drawDevice(srcDev, pos.x(), pos.y(), *paint);
1438+
// Use the whole device buffer, presumably it was sized appropriately to match the
1439+
// desired output size of the destination when the layer was first saved.
1440+
sk_sp<SkSpecialImage> srcBuffer = srcDev->snapSpecial();
1441+
if (!srcBuffer) {
1442+
return;
1443+
}
1444+
1445+
// Evaluate the image filter DAG on the src device's buffer. The filter processes an
1446+
// image in the src's device space. However, the filter parameters need to respect the
1447+
// dst's local matrix (this reflects the CTM that was set when the layer was first
1448+
// saved). We can achieve this by concatenating the dst's local-to-device matrix with
1449+
// the relative transform from dst to src. Then the final result is drawn to dst using
1450+
// the relative transform from src to dst.
1451+
SkMatrix srcToDst = srcDev->getRelativeTransform(*dstDev);
1452+
SkMatrix dstToSrc = dstDev->getRelativeTransform(*srcDev);
1453+
skif::Mapping mapping(srcToDst, SkMatrix::Concat(dstToSrc, dstDev->localToDevice()));
1454+
dstDev->drawFilteredImage(mapping, srcBuffer.get(), filter, *noFilterPaint);
14291455
}
14301456
}
14311457

@@ -2497,11 +2523,15 @@ void SkCanvas::onDrawImage(const SkImage* image, SkScalar x, SkScalar y, const S
24972523
paint = &realPaint;
24982524

24992525
sk_sp<SkSpecialImage> special;
2526+
sk_sp<SkImageFilter> filter;
25002527
bool drawAsSprite = this->canDrawBitmapAsSprite(x, y, image->width(), image->height(),
25012528
*paint);
25022529
if (drawAsSprite && paint->getImageFilter()) {
25032530
special = this->getDevice()->makeSpecial(image);
2504-
if (!special) {
2531+
if (special) {
2532+
filter = paint->refImageFilter();
2533+
realPaint.setImageFilter(nullptr); // This modifies 'paint' but is not const
2534+
} else {
25052535
drawAsSprite = false;
25062536
}
25072537
}
@@ -2511,11 +2541,18 @@ void SkCanvas::onDrawImage(const SkImage* image, SkScalar x, SkScalar y, const S
25112541
while (iter.next()) {
25122542
const SkPaint& pnt = draw.paint();
25132543
if (special) {
2514-
SkPoint pt;
2515-
iter.fDevice->localToDevice().mapXY(x, y, &pt);
2516-
iter.fDevice->drawSpecial(special.get(),
2517-
SkScalarRoundToInt(pt.fX),
2518-
SkScalarRoundToInt(pt.fY), pnt);
2544+
SkASSERT(!pnt.getImageFilter());
2545+
2546+
// TODO(michaelludwig) - Many filters could probably be evaluated like this even if the
2547+
// CTM is not translate-only; the post-transformation of the filtered image by the CTM
2548+
// will probably look just as good and not require an extra layer.
2549+
// TODO(michaelludwig) - Once image filter implementations can support source images
2550+
// with non-(0,0) origins, we can just mark the origin as (x,y) instead of doing a
2551+
// pre-concat here.
2552+
SkMatrix layerToDevice = iter.fDevice->localToDevice();
2553+
layerToDevice.preTranslate(x, y);
2554+
skif::Mapping mapping(layerToDevice, SkMatrix::Translate(-x, -y));
2555+
iter.fDevice->drawFilteredImage(mapping, special.get(), filter.get(), pnt);
25192556
} else {
25202557
iter.fDevice->drawImageRect(
25212558
image, nullptr, SkRect::MakeXYWH(x, y, image->width(), image->height()), pnt,

src/core/SkDevice.cpp

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "src/core/SkDraw.h"
1919
#include "src/core/SkGlyphRun.h"
2020
#include "src/core/SkImageFilterCache.h"
21+
#include "src/core/SkImageFilter_Base.h"
2122
#include "src/core/SkImagePriv.h"
2223
#include "src/core/SkLatticeIter.h"
2324
#include "src/core/SkMarkerStack.h"
@@ -86,10 +87,10 @@ SkIPoint SkBaseDevice::getOrigin() const {
8687
SkScalarFloorToInt(fDeviceToGlobal.getTranslateY()));
8788
}
8889

89-
SkMatrix SkBaseDevice::getRelativeTransform(const SkBaseDevice& inputDevice) const {
90-
// To get the transform from the input's space to this space, transform from the input space to
91-
// the global space, and then from the global space back to this space.
92-
return SkMatrix::Concat(fGlobalToDevice, inputDevice.fDeviceToGlobal);
90+
SkMatrix SkBaseDevice::getRelativeTransform(const SkBaseDevice& dstDevice) const {
91+
// To get the transform from this space to the other device's, transform from our space to
92+
// global and then from global to the other device.
93+
return SkMatrix::Concat(dstDevice.fGlobalToDevice, fDeviceToGlobal);
9394
}
9495

9596
bool SkBaseDevice::getLocalToMarker(uint32_t id, SkM44* localToMarker) const {
@@ -324,6 +325,41 @@ sk_sp<SkSpecialImage> SkBaseDevice::snapSpecial() {
324325
return this->snapSpecial(SkIRect::MakeWH(this->width(), this->height()));
325326
}
326327

328+
void SkBaseDevice::drawFilteredImage(const skif::Mapping& mapping, SkSpecialImage* src,
329+
const SkImageFilter* filter, const SkPaint& paint) {
330+
SkASSERT(!paint.getImageFilter() && !paint.getMaskFilter());
331+
using For = skif::Usage;
332+
333+
skif::LayerSpace<SkIRect> targetOutput = mapping.deviceToLayer(
334+
skif::DeviceSpace<SkIRect>(this->devClipBounds()));
335+
336+
// FIXME If the saved layer (so src) was created to use F16, should we do all image filtering
337+
// in F16 and then only flatten to the destination color encoding at the end?
338+
// Currently, this context converts everything to the dst color type ASAP.
339+
SkColorType colorType = this->imageInfo().colorType();
340+
if (colorType == kUnknown_SkColorType) {
341+
colorType = kRGBA_8888_SkColorType;
342+
}
343+
344+
// getImageFilterCache returns a bare image filter cache pointer that must be ref'ed until the
345+
// filter's filterImage(ctx) function returns.
346+
sk_sp<SkImageFilterCache> cache(this->getImageFilterCache());
347+
skif::Context ctx(mapping, targetOutput, cache.get(), colorType, this->imageInfo().colorSpace(),
348+
skif::FilterResult<For::kInput>(sk_ref_sp(src)));
349+
350+
SkIPoint offset;
351+
sk_sp<SkSpecialImage> result = as_IFB(filter)->filterImage(ctx).imageAndOffset(&offset);
352+
if (result) {
353+
// TODO(michaelludwig) - Eventually drawSpecial will take a matrix and we can just
354+
// draw using mapping.deviceMatrix() directly. For now, all devices are relative to each
355+
// other by a translation, or its a translation-only sprite draw.
356+
SkASSERT(mapping.deviceMatrix().isTranslate());
357+
offset.fX += SkScalarRoundToInt(mapping.deviceMatrix().getTranslateX());
358+
offset.fY += SkScalarRoundToInt(mapping.deviceMatrix().getTranslateY());
359+
this->drawSpecial(result.get(), offset.fX, offset.fY, paint);
360+
}
361+
}
362+
327363
///////////////////////////////////////////////////////////////////////////////////////////////////
328364

329365
bool SkBaseDevice::readPixels(const SkPixmap& pm, int x, int y) {

src/core/SkDevice.h

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,18 @@ class SkBitmap;
2222
struct SkDrawShadowRec;
2323
class SkGlyphRun;
2424
class SkGlyphRunList;
25+
class SkImageFilter;
2526
class SkImageFilterCache;
2627
struct SkIRect;
2728
class SkMarkerStack;
2829
class SkMatrix;
2930
class SkRasterHandleAllocator;
3031
class SkSpecialImage;
3132

33+
namespace skif {
34+
class Mapping;
35+
} // namespace skif
36+
3237
class SkBaseDevice : public SkRefCnt, public SkMatrixProvider {
3338
public:
3439
SkBaseDevice(const SkImageInfo&, const SkSurfaceProps&);
@@ -126,10 +131,10 @@ class SkBaseDevice : public SkRefCnt, public SkMatrixProvider {
126131
*/
127132
bool isPixelAlignedToGlobal() const;
128133
/**
129-
* Get the transformation from the input device's to this device's coordinate space. This
130-
* transform can be used to draw the input device into this device, such that once this device
131-
* is drawn to the root device, the net effect will have the input device's content drawn
132-
* transformed by the global CTM.
134+
* Get the transformation from this device's coordinate system to the provided device space.
135+
* This transform can be used to draw this device into the provided device, such that once
136+
* that device is drawn to the root device, the net effect will be that this device's contents
137+
* have been transformed by the global CTM.
133138
*/
134139
SkMatrix getRelativeTransform(const SkBaseDevice&) const;
135140

@@ -289,19 +294,33 @@ class SkBaseDevice : public SkRefCnt, public SkMatrixProvider {
289294
const SkPoint dstClips[], const SkMatrix preViewMatrices[],
290295
const SkPaint& paint, SkCanvas::SrcRectConstraint);
291296

297+
void drawGlyphRunRSXform(const SkFont&, const SkGlyphID[], const SkRSXform[], int count,
298+
SkPoint origin, const SkPaint& paint);
299+
300+
virtual void drawDrawable(SkDrawable*, const SkMatrix*, SkCanvas*);
301+
292302
/** The SkDevice passed will be an SkDevice which was returned by a call to
293303
onCreateDevice on this device with kNeverTile_TileExpectation.
294304
*/
295305
virtual void drawDevice(SkBaseDevice*, int x, int y, const SkPaint&) = 0;
296306

297-
void drawGlyphRunRSXform(const SkFont&, const SkGlyphID[], const SkRSXform[], int count,
298-
SkPoint origin, const SkPaint& paint);
307+
virtual void drawSpecial(SkSpecialImage*, int x, int y, const SkPaint&);
299308

300-
virtual void drawDrawable(SkDrawable*, const SkMatrix*, SkCanvas*);
309+
/**
310+
* Evaluate 'filter' and draw the final output into this device using 'paint'. The 'mapping'
311+
* defines the parameter-to-layer space transform used to evaluate the image filter on 'src',
312+
* and the layer-to-device space transform that is used to draw the result into this device.
313+
* Since 'mapping' fully specifies the transform, this draw function ignores the current
314+
* local-to-device matrix (i.e. just like drawSpecial and drawDevice).
315+
*
316+
* The final paint must not have an image filter or mask filter set on it; a shader is ignored.
317+
*/
318+
virtual void drawFilteredImage(const skif::Mapping& mapping, SkSpecialImage* src,
319+
const SkImageFilter* filter, const SkPaint& paint);
301320

302-
virtual void drawSpecial(SkSpecialImage*, int x, int y, const SkPaint&);
303321
virtual sk_sp<SkSpecialImage> makeSpecial(const SkBitmap&);
304322
virtual sk_sp<SkSpecialImage> makeSpecial(const SkImage*);
323+
305324
// Get a view of the entire device's current contents as an image.
306325
sk_sp<SkSpecialImage> snapSpecial();
307326
// Snap the 'subset' contents from this device, possibly as a read-only view. If 'forceCopy'
@@ -502,6 +521,9 @@ class SkNoPixelsDevice : public SkBaseDevice {
502521
void drawGlyphRunList(const SkGlyphRunList& glyphRunList) override {}
503522
void drawVertices(const SkVertices*, SkBlendMode, const SkPaint&) override {}
504523

524+
void drawFilteredImage(const skif::Mapping& mapping, SkSpecialImage* src,
525+
const SkImageFilter* filter, const SkPaint& paint) override {}
526+
505527
private:
506528
typedef SkBaseDevice INHERITED;
507529
};

src/core/SkImageFilterTypes.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,9 @@ class FilterResult {
519519
FilterResult(sk_sp<SkSpecialImage> image, const LayerSpace<SkIPoint>& origin)
520520
: fImage(std::move(image))
521521
, fOrigin(origin) {}
522+
explicit FilterResult(sk_sp<SkSpecialImage> image)
523+
: fImage(std::move(image))
524+
, fOrigin{{0, 0}} {}
522525

523526
// Allow explicit moves/copies in order to cast from one use type to another, except kInput0
524527
// and kInput1 can only be cast to kOutput (e.g. as part of a noop image filter).

0 commit comments

Comments
 (0)