First pass at ImageFilter DAG visualizer sample
Change-Id: I04493af0a6ce1425c4acf68365135722dd3c218b Reviewed-on: https://skia-review.googlesource.com/c/skia/+/227857 Commit-Queue: Michael Ludwig <michaelludwig@google.com> Reviewed-by: Robert Phillips <robertphillips@google.com>
This commit is contained in:
parent
275157f7d2
commit
43c6da2a15
@ -50,6 +50,7 @@ samples_sources = [
|
||||
"$_samplecode/SampleHairModes.cpp",
|
||||
"$_samplecode/SampleHT.cpp",
|
||||
"$_samplecode/SampleIdentityScale.cpp",
|
||||
"$_samplecode/SampleImageFilterDAG.cpp",
|
||||
"$_samplecode/SampleLayerMask.cpp",
|
||||
"$_samplecode/SampleLayers.cpp",
|
||||
"$_samplecode/SampleLCD.cpp",
|
||||
|
@ -453,6 +453,9 @@ protected:
|
||||
private:
|
||||
friend class SkGraphics;
|
||||
friend bool SkIsSameFilter(const SkImageFilter* a, const SkImageFilter* b);
|
||||
// Helper method to inspect onFilterNodeBounds() without going through filterBounds()
|
||||
friend SkIRect SkFilterNodeBounds(const SkImageFilter*, const SkIRect& srcRect, const SkMatrix&,
|
||||
MapDirection, const SkIRect* inputRect);
|
||||
|
||||
static void PurgeCache();
|
||||
|
||||
|
478
samplecode/SampleImageFilterDAG.cpp
Normal file
478
samplecode/SampleImageFilterDAG.cpp
Normal file
@ -0,0 +1,478 @@
|
||||
/*
|
||||
* Copyright 2019 Google LLC
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "samplecode/Sample.h"
|
||||
|
||||
#include "include/core/SkCanvas.h"
|
||||
#include "include/core/SkColor.h"
|
||||
#include "include/core/SkFont.h"
|
||||
#include "include/core/SkImage.h"
|
||||
#include "include/core/SkImageFilter.h"
|
||||
#include "include/core/SkImageInfo.h"
|
||||
#include "include/core/SkPaint.h"
|
||||
#include "include/core/SkPoint.h"
|
||||
#include "include/core/SkRect.h"
|
||||
#include "include/core/SkSurface.h"
|
||||
|
||||
#include "include/effects/SkBlurImageFilter.h"
|
||||
#include "include/effects/SkColorFilterImageFilter.h"
|
||||
#include "include/effects/SkDashPathEffect.h"
|
||||
#include "include/effects/SkDropShadowImageFilter.h"
|
||||
#include "include/effects/SkGradientShader.h"
|
||||
#include "include/effects/SkMergeImageFilter.h"
|
||||
#include "include/effects/SkOffsetImageFilter.h"
|
||||
|
||||
#include "src/core/SkImageFilterPriv.h"
|
||||
#include "src/core/SkSpecialImage.h"
|
||||
|
||||
namespace {
|
||||
|
||||
struct FilterNode {
|
||||
// Pointer to the actual filter in the DAG, so it still contains its input filters and
|
||||
// may be used as an input in an earlier node. Null when this represents the "source" input
|
||||
sk_sp<SkImageFilter> fFilter;
|
||||
|
||||
// FilterNodes wrapping each of fFilter's inputs. Leaf node when fInputNodes is empty.
|
||||
SkTArray<FilterNode> fInputNodes;
|
||||
|
||||
// Distance from root filter
|
||||
int fDepth;
|
||||
|
||||
// The source content rect (this is the same for all nodes, but is stored here for convenience)
|
||||
SkRect fContent;
|
||||
// The portion of the original CTM that is kept as the local matrix/ctm when filtering
|
||||
SkMatrix fLocalCTM;
|
||||
// The portion of the original CTM that the results should be drawn with (or given current
|
||||
// canvas impl., the portion of the CTM that is baked into a new DAG)
|
||||
SkMatrix fRemainingCTM;
|
||||
|
||||
// Cached reverse bounds using device-space clip bounds (e.g. SkCanvas::clipRectBounds with
|
||||
// null first argument). This represents the layer calculated in SkCanvas for the filtering.
|
||||
// FIXME: SkCanvas (and this sample), currently do not transform the device clip bounds into the
|
||||
// filter local space, which is where it ought to be defined.
|
||||
SkIRect fLayerBounds;
|
||||
|
||||
// Cached reverse bounds using the local draw bounds (e.g. SkCanvas::clipRectBounds with the
|
||||
// draw bounds provided as first argument). For intermediate nodes in a DAG, this is calculated
|
||||
// to match what the filter would compute when being evaluated as part of the original DAG.
|
||||
// fReverseLocalIsolatedBounds is the same, except it represents what would be calculated if
|
||||
// only this node were being applied as the image filter.
|
||||
SkIRect fReverseLocalBounds;
|
||||
SkIRect fReverseLocalIsolatedBounds;
|
||||
|
||||
// Cached forward bounds based on local draw bounds. For intermediate nodes in a DAG, this is
|
||||
// calculated to match what the filter computes as part of the whole DAG. fForwardIsolatedBounds
|
||||
// is the same but represents what would be calculated if only this node were applied.
|
||||
SkIRect fForwardBounds;
|
||||
SkIRect fForwardIsolatedBounds;
|
||||
|
||||
// Should be called after the input nodes have been created since this will complete the
|
||||
// entire tree.
|
||||
void computeBounds() {
|
||||
// In normal usage, forward bounds are filter-space bounds of the geometry content, so
|
||||
// fContent mapped by the local matrix, since we assume the layer content is made by
|
||||
// concat(localCTM) -> clipRect(content) -> drawRect(content).
|
||||
// Similarly, in normal usage, reverse bounds are the filter-space bounds of the space to
|
||||
// be filled by image filter results. Since the clip rect is set to the same as the content,
|
||||
// it's the same bounds forward or reverse in this contrived case.
|
||||
SkIRect inputRect;
|
||||
fLocalCTM.mapRect(fContent).roundOut(&inputRect);
|
||||
|
||||
this->computeForwardBounds(inputRect);
|
||||
this->computeReverseLocalIsolatedBounds(inputRect);
|
||||
this->computeReverseBounds(inputRect, false);
|
||||
|
||||
// The layer bounds (matching what SkCanvas computes), use the content rect mapped by the
|
||||
// entire CTM as its input rect.
|
||||
// FIXME (michaelludwig) - Once SkCanvas' filter handling is fixed, this should be
|
||||
// (remainder * local)
|
||||
SkIRect incorrectCanvasInputRect;
|
||||
SkMatrix ctm = SkMatrix::Concat(fLocalCTM, fRemainingCTM);
|
||||
ctm.mapRect(fContent).roundOut(&incorrectCanvasInputRect);
|
||||
this->computeReverseBounds(incorrectCanvasInputRect, true);
|
||||
}
|
||||
|
||||
private:
|
||||
void computeForwardBounds(const SkIRect srcRect) {
|
||||
if (fFilter) {
|
||||
// For forward filtering, the leaves of the DAG are evaluated first and are then
|
||||
// propagated to the root. This means that every filter's filterBounds() function sees
|
||||
// the original src rect. It is never dependent on the parent node (unlike reverse
|
||||
// filtering), so calling filterBounds() on an intermediate node gives us the correct
|
||||
// intermediate values.
|
||||
fForwardBounds = fFilter->filterBounds(
|
||||
srcRect, fLocalCTM, SkImageFilter::kForward_MapDirection, nullptr);
|
||||
|
||||
// For isolated forward filtering, it uses the same input but should not be propagated
|
||||
// to the inputs, so get the filter node bounds directly.
|
||||
fForwardIsolatedBounds = SkFilterNodeBounds(
|
||||
fFilter.get(), srcRect, fLocalCTM,
|
||||
SkImageFilter::kForward_MapDirection, nullptr);
|
||||
} else {
|
||||
fForwardBounds = srcRect;
|
||||
fForwardIsolatedBounds = srcRect;
|
||||
}
|
||||
|
||||
// Fill in children
|
||||
for (int i = 0; i < fInputNodes.count(); ++i) {
|
||||
fInputNodes[i].computeForwardBounds(srcRect);
|
||||
}
|
||||
}
|
||||
|
||||
void computeReverseLocalIsolatedBounds(const SkIRect& srcRect) {
|
||||
if (fFilter) {
|
||||
fReverseLocalIsolatedBounds = SkFilterNodeBounds(
|
||||
fFilter.get(), srcRect, fLocalCTM,
|
||||
SkImageFilter::kReverse_MapDirection, &srcRect);
|
||||
} else {
|
||||
fReverseLocalIsolatedBounds = srcRect;
|
||||
}
|
||||
|
||||
// Fill in children. Unlike regular reverse bounds mapping, the input nodes see the original
|
||||
// bounds. Normally, the bounds that the child nodes see have already been mapped processed
|
||||
// by this node.
|
||||
for (int i = 0; i < fInputNodes.count(); ++i) {
|
||||
fInputNodes[i].computeReverseLocalIsolatedBounds(srcRect);
|
||||
}
|
||||
}
|
||||
|
||||
// fReverseLocalBounds and fLayerBounds are computed the same, except they differ in what the
|
||||
// initial bounding rectangle was. It is assumed that the 'srcRect' has already been processed
|
||||
// by the parent node's onFilterNodeBounds() function, as in SkImageFilter::filterBounds().
|
||||
void computeReverseBounds(const SkIRect& srcRect, bool writeToLayerBounds) {
|
||||
SkIRect reverseBounds = srcRect;
|
||||
|
||||
if (fFilter) {
|
||||
// Since srcRect has been through parent's onFilterNodeBounds(), calling filterBounds()
|
||||
// directly on this node will calculate the same rectangle that this filter would report
|
||||
// during the parent node's onFilterBounds() recursion.
|
||||
|
||||
reverseBounds = fFilter->filterBounds(
|
||||
srcRect, fLocalCTM, SkImageFilter::kReverse_MapDirection, &srcRect);
|
||||
|
||||
// To calculate the appropriate intermediate reverse bounds for the children, we need
|
||||
// this node's onFilterNodeBounds() results based on its parents' bounds (the current
|
||||
// 'srcRect')
|
||||
SkIRect nextSrcRect = SkFilterNodeBounds(
|
||||
fFilter.get(), srcRect, fLocalCTM,
|
||||
SkImageFilter::kReverse_MapDirection, &srcRect);
|
||||
|
||||
// Fill in the children. The union of these bounds should equal the value calculated
|
||||
// for reverseBounds already.
|
||||
SkDEBUGCODE(SkIRect netReverseBounds = SkIRect::MakeEmpty();)
|
||||
for (int i = 0; i < fInputNodes.count(); ++i) {
|
||||
fInputNodes[i].computeReverseBounds(nextSrcRect, writeToLayerBounds);
|
||||
SkDEBUGCODE(netReverseBounds.join(
|
||||
writeToLayerBounds ? fInputNodes[i].fLayerBounds
|
||||
: fInputNodes[i].fReverseLocalBounds);)
|
||||
}
|
||||
SkASSERT(netReverseBounds == reverseBounds);
|
||||
}
|
||||
|
||||
if (writeToLayerBounds) {
|
||||
fLayerBounds = reverseBounds;
|
||||
} else {
|
||||
fReverseLocalBounds = reverseBounds;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
static FilterNode build_dag(const SkMatrix& local, const SkMatrix& remainder, const SkRect& rect,
|
||||
const SkImageFilter* filter, int depth) {
|
||||
FilterNode node;
|
||||
node.fFilter = sk_ref_sp(filter);
|
||||
node.fDepth = depth;
|
||||
node.fContent = rect;
|
||||
|
||||
node.fLocalCTM = local;
|
||||
node.fRemainingCTM = remainder;
|
||||
|
||||
if (node.fFilter) {
|
||||
if (depth > 0) {
|
||||
// We don't visit children when at the root because the real child filters are replaced
|
||||
// with the internalSaveLayer decomposition emulation, which then cycles back to the
|
||||
// original filter but with an updated matrix (and then we process the children).
|
||||
node.fInputNodes.reserve(node.fFilter->countInputs());
|
||||
for (int i = 0; i < node.fFilter->countInputs(); ++i) {
|
||||
node.fInputNodes.push_back() =
|
||||
build_dag(local, remainder, rect, node.fFilter->getInput(i), depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
static FilterNode build_dag(const SkMatrix& ctm, const SkRect& rect,
|
||||
const SkImageFilter* rootFilter) {
|
||||
// Emulate SkCanvas::internalSaveLayer's decomposition of the CTM.
|
||||
SkMatrix local;
|
||||
sk_sp<SkImageFilter> finalFilter = SkApplyCTMToFilter(rootFilter, ctm, &local);
|
||||
|
||||
// FIXME (michaelludwig)
|
||||
// decomposeScale computes scale * remaining, and SkApplyCTMToFilter puts the remaining
|
||||
// matrix into the DAG. This is actually incorrect, since the input content for filtering
|
||||
// should be transformed by the local matrix ('scale' in this case) first, and then the matrix
|
||||
// filter applies 'remaining'. Once they are applied properly, update this code to reflect
|
||||
// the proper matrix multiplication order (currently, this is written to match SkCanvas).
|
||||
SkMatrix invLocal, embedded;
|
||||
if (local.invert(&invLocal)) {
|
||||
// Currently, CTM = local * embedded, so embedded = local^-1 * CTM
|
||||
embedded = SkMatrix::Concat(invLocal, ctm);
|
||||
} else {
|
||||
embedded = SkMatrix::I();
|
||||
}
|
||||
|
||||
// Create a root node that represents the full result
|
||||
FilterNode rootNode = build_dag(ctm, SkMatrix::I(), rect, rootFilter, 0);
|
||||
// Set its only child as the modified DAG that handles the CTM decomposition
|
||||
rootNode.fInputNodes.push_back() =
|
||||
build_dag(local, embedded, rect, finalFilter.get(), 1);
|
||||
// Fill in bounds information that requires the entire node DAG to have been extracted first.
|
||||
rootNode.fInputNodes[0].computeBounds();
|
||||
return rootNode;
|
||||
}
|
||||
|
||||
static void draw_node(SkCanvas* canvas, const FilterNode& node) {
|
||||
canvas->clear(SK_ColorTRANSPARENT);
|
||||
|
||||
SkPaint filterPaint;
|
||||
filterPaint.setImageFilter(node.fFilter);
|
||||
|
||||
SkPaint paint;
|
||||
static const SkPoint kPoints[2] = {{0, 0}, {5, 5}};
|
||||
static const SkColor kColors[2] = {SK_ColorGREEN, SK_ColorWHITE};
|
||||
paint.setShader(SkGradientShader::MakeLinear(kPoints, kColors, nullptr, SK_ARRAY_COUNT(kPoints),
|
||||
SkTileMode::kMirror));
|
||||
|
||||
SkPaint line;
|
||||
line.setStrokeWidth(1.f);
|
||||
line.setStyle(SkPaint::kStroke_Style);
|
||||
|
||||
if (node.fDepth == 0) {
|
||||
// The root node, so draw this one the canonical way through SkCanvas to show current
|
||||
// net behavior. Will not include bounds visualization.
|
||||
canvas->save();
|
||||
canvas->concat(node.fLocalCTM);
|
||||
SkASSERT(node.fRemainingCTM.isIdentity());
|
||||
|
||||
canvas->clipRect(node.fContent, false);
|
||||
canvas->saveLayer(nullptr, &filterPaint);
|
||||
canvas->drawRect(node.fContent, paint);
|
||||
canvas->restore(); // Completes the image filter
|
||||
canvas->restore(); // Undoes matrix and clip
|
||||
} else {
|
||||
// Once the remaining CTM is handled by a draw instead of a matrix image filter, this can
|
||||
// go away since there won't be any implicit node that embeds the remaining CTM.
|
||||
bool implicitMatrixNode = node.fDepth == 1 && !node.fRemainingCTM.isIdentity();
|
||||
|
||||
canvas->save();
|
||||
canvas->concat(node.fLocalCTM);
|
||||
if (!implicitMatrixNode) {
|
||||
// The matrix transform node currently bakes remainingCTm into its image,
|
||||
// so don't push it into the canvas
|
||||
canvas->concat(node.fRemainingCTM);
|
||||
}
|
||||
|
||||
canvas->saveLayer(nullptr, &filterPaint);
|
||||
canvas->drawRect(node.fContent, paint);
|
||||
canvas->restore(); // Completes the image filter
|
||||
|
||||
// Draw content-rect bounds
|
||||
line.setColor(SK_ColorBLACK);
|
||||
if (implicitMatrixNode) {
|
||||
// The matrix transform node imagery doesn't need the remaining CTM, but the content
|
||||
// bounds do, so push it on now that the filtering has finished.
|
||||
canvas->concat(node.fRemainingCTM);
|
||||
}
|
||||
canvas->drawRect(node.fContent, line);
|
||||
canvas->restore(); // Undoes the matrix
|
||||
|
||||
// Bounding boxes have all been mapped by the local matrix already, but draw them in
|
||||
// the final coordinate transform.
|
||||
// FIXME (michaelludwig) - These bounds don't line up as expected when the CTM has been
|
||||
// decomposed because this code matches the correct (remaining * (local * rect)) equation,
|
||||
// but the current decomposition produces (local * remaining * rect), so translations aren't
|
||||
// scaled properly.
|
||||
canvas->save();
|
||||
if (!implicitMatrixNode) {
|
||||
canvas->concat(node.fRemainingCTM);
|
||||
}
|
||||
|
||||
line.setColor(SK_ColorRED);
|
||||
canvas->drawRect(SkRect::Make(node.fLayerBounds).makeOutset(5.f, 5.f), line);
|
||||
|
||||
line.setColor(SK_ColorBLUE);
|
||||
canvas->drawRect(SkRect::Make(node.fReverseLocalBounds).makeOutset(4.f, 4.f), line);
|
||||
line.setColor(SK_ColorYELLOW);
|
||||
canvas->drawRect(SkRect::Make(node.fForwardBounds).makeOutset(2.f, 2.f), line);
|
||||
|
||||
// Dashed lines for the isolated shapes
|
||||
static const SkScalar kDashParams[] = {6.f, 12.f};
|
||||
line.setPathEffect(SkDashPathEffect::Make(kDashParams, 2, 0.f));
|
||||
|
||||
line.setColor(SK_ColorBLUE);
|
||||
canvas->drawRect(SkRect::Make(node.fReverseLocalIsolatedBounds).makeOutset(3.f, 3.f), line);
|
||||
line.setColor(SK_ColorYELLOW);
|
||||
canvas->drawRect(SkRect::Make(node.fForwardIsolatedBounds).makeOutset(1.f, 1.f), line); // FIXME dashed
|
||||
|
||||
canvas->restore();
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr float kLineHeight = 16.f;
|
||||
static constexpr float kLineInset = 8.f;
|
||||
|
||||
static void print_matrix(SkCanvas* canvas, const char* prefix, const SkMatrix& matrix,
|
||||
float x, float y, const SkFont& font, const SkPaint& paint) {
|
||||
canvas->drawString(prefix, x, y, font, paint);
|
||||
y += kLineHeight;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
SkString row;
|
||||
row.appendf("[%.2f %.2f %.2f]",
|
||||
matrix.get(i * 3), matrix.get(i * 3 + 1), matrix.get(i * 3 + 2));
|
||||
canvas->drawString(row, x, y, font, paint);
|
||||
y += kLineHeight;
|
||||
}
|
||||
}
|
||||
|
||||
static void print_size(SkCanvas* canvas, const char* prefix, const SkIRect& rect,
|
||||
float x, float y, const SkFont& font, const SkPaint& paint) {
|
||||
canvas->drawString(prefix, x, y, font, paint);
|
||||
y += kLineHeight;
|
||||
SkString sz;
|
||||
sz.appendf("%d x %d", rect.width(), rect.height());
|
||||
canvas->drawString(sz, x, y, font, paint);
|
||||
}
|
||||
|
||||
static void print_info(SkCanvas* canvas, const FilterNode& node) {
|
||||
SkFont font(nullptr, 12);
|
||||
SkPaint text;
|
||||
text.setAntiAlias(true);
|
||||
|
||||
if (node.fDepth == 0) {
|
||||
canvas->drawString("Final Results", kLineInset, kLineHeight, font, text);
|
||||
// The actual interesting matrices are in the root node's first child
|
||||
print_matrix(canvas, "Local", node.fInputNodes[0].fLocalCTM,
|
||||
kLineInset, 2 * kLineHeight, font, text);
|
||||
print_matrix(canvas, "Embedded", node.fInputNodes[0].fRemainingCTM,
|
||||
16 * kLineInset, 2 * kLineHeight, font, text);
|
||||
} else if (node.fFilter) {
|
||||
canvas->drawString(node.fFilter->getTypeName(), kLineInset, kLineHeight, font, text);
|
||||
print_size(canvas, "Layer Size", node.fLayerBounds, kLineInset, 2 * kLineHeight,
|
||||
font, text);
|
||||
print_size(canvas, "Ideal Size", node.fReverseLocalBounds, kLineInset, 4 * kLineHeight,
|
||||
font, text);
|
||||
} else {
|
||||
canvas->drawString("Source Input", kLineInset, kLineHeight, font, text);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns how far to the right the subtree took up in canvas
|
||||
static float draw_dag(SkCanvas* canvas, SkSurface* nodeSurface, const FilterNode& node) {
|
||||
// First capture the results of the node, into nodeSurface
|
||||
draw_node(nodeSurface->getCanvas(), node);
|
||||
sk_sp<SkImage> nodeResults = nodeSurface->makeImageSnapshot();
|
||||
|
||||
// Display filtered results in current canvas' location (assumed CTM is set for this node)
|
||||
canvas->drawImage(nodeResults, 0, 0);
|
||||
|
||||
SkPaint line;
|
||||
line.setStyle(SkPaint::kStroke_Style);
|
||||
line.setStrokeWidth(3.f);
|
||||
|
||||
// Text info
|
||||
canvas->save();
|
||||
canvas->translate(nodeResults->width(), 0);
|
||||
print_info(canvas, node);
|
||||
canvas->restore();
|
||||
|
||||
// Border around filtered results + text info
|
||||
static const float kTextWidth = 150.f;
|
||||
float textWidth = node.fDepth > 0 ? kTextWidth : 1.75f * kTextWidth;
|
||||
canvas->drawRect(SkRect::MakeWH(nodeResults->width() + textWidth, nodeResults->height()), line);
|
||||
|
||||
static const float kPad = 20.f;
|
||||
float x = 0;
|
||||
float y = nodeResults->height() + kPad;
|
||||
for (int i = 0; i < node.fInputNodes.count(); ++i) {
|
||||
// Line connecting this node to its child
|
||||
canvas->drawLine(0.5f * nodeResults->width(), nodeResults->height(), // bottom of node
|
||||
x + 0.5f * nodeResults->width(), y, line); // top of child
|
||||
canvas->save();
|
||||
canvas->translate(x, y);
|
||||
x = draw_dag(canvas, nodeSurface, node.fInputNodes[i]);
|
||||
canvas->restore();
|
||||
}
|
||||
return SkMaxScalar(x, nodeResults->width() + textWidth + kPad);
|
||||
}
|
||||
|
||||
static void draw_dag(SkCanvas* canvas, sk_sp<SkImageFilter> filter,
|
||||
const SkRect& rect, const SkISize& surfaceSize) {
|
||||
// Get the current CTM, which includes all the viewer's UI modifications, which we want to
|
||||
// pass into our mock canvases for each DAG node.
|
||||
SkMatrix ctm = canvas->getTotalMatrix();
|
||||
|
||||
canvas->save();
|
||||
// Reset the matrix so that the DAG layout and instructional text is fixed to the window.
|
||||
canvas->resetMatrix();
|
||||
|
||||
// Process the image filter DAG to display intermediate results later on, which will apply the
|
||||
// provided CTM during draw_node calls.
|
||||
FilterNode dag = build_dag(ctm, rect, filter.get());
|
||||
|
||||
sk_sp<SkSurface> nodeSurface = canvas->makeSurface(
|
||||
canvas->imageInfo().makeWH(surfaceSize.width(), surfaceSize.height()));
|
||||
draw_dag(canvas, nodeSurface.get(), dag);
|
||||
|
||||
canvas->restore();
|
||||
}
|
||||
|
||||
class ImageFilterDAGSample : public Sample {
|
||||
public:
|
||||
ImageFilterDAGSample() {}
|
||||
|
||||
void onDrawContent(SkCanvas* canvas) override {
|
||||
static const SkRect kFilterRect = SkRect::MakeXYWH(20.f, 20.f, 60.f, 60.f);
|
||||
static const SkISize kFilterSurfaceSize = SkISize::Make(
|
||||
2 * (kFilterRect.fRight + kFilterRect.fLeft),
|
||||
2 * (kFilterRect.fBottom + kFilterRect.fTop));
|
||||
|
||||
// Somewhat clunky, but we want to use the viewer calculated CTM in the mini surfaces used
|
||||
// per DAG node. The rotation matrix viewer calculates is based on the sample size so trick
|
||||
// it into calculating the right matrix for us w/ 1 frame latency.
|
||||
this->setSize(kFilterSurfaceSize.width(), kFilterSurfaceSize.height());
|
||||
|
||||
// Make a large DAG
|
||||
// /--- Color Filter <---- Blur <--- Offset
|
||||
// Merge <
|
||||
// \--- Blur <--- Drop Shadow
|
||||
sk_sp<SkImageFilter> drop2 = SkDropShadowImageFilter::Make(
|
||||
10.f, 5.f, 3.f, 3.f, SK_ColorBLACK,
|
||||
SkDropShadowImageFilter::kDrawShadowAndForeground_ShadowMode, nullptr);
|
||||
sk_sp<SkImageFilter> blur1 = SkBlurImageFilter::Make(2.f, 2.f, std::move(drop2));
|
||||
|
||||
sk_sp<SkImageFilter> offset3 = SkOffsetImageFilter::Make(-5.f, -5.f, nullptr);
|
||||
sk_sp<SkImageFilter> blur2 = SkBlurImageFilter::Make(4.f, 4.f, std::move(offset3));
|
||||
sk_sp<SkImageFilter> cf1 = SkColorFilterImageFilter::Make(
|
||||
SkColorFilters::Blend(SK_ColorGRAY, SkBlendMode::kModulate), std::move(blur2));
|
||||
|
||||
sk_sp<SkImageFilter> merge0 = SkMergeImageFilter::Make(std::move(blur1), std::move(cf1));
|
||||
|
||||
draw_dag(canvas, std::move(merge0), kFilterRect, kFilterSurfaceSize);
|
||||
}
|
||||
|
||||
SkString name() override { return SkString("ImageFilterDAG"); }
|
||||
|
||||
private:
|
||||
|
||||
typedef Sample INHERITED;
|
||||
};
|
||||
|
||||
DEF_SAMPLE(return new ImageFilterDAGSample();)
|
@ -567,3 +567,8 @@ bool SkIsSameFilter(const SkImageFilter* a, const SkImageFilter* b) {
|
||||
return a->fUniqueID == b->fUniqueID;
|
||||
}
|
||||
}
|
||||
|
||||
SkIRect SkFilterNodeBounds(const SkImageFilter* filter, const SkIRect& srcRect, const SkMatrix& ctm,
|
||||
SkImageFilter::MapDirection dir, const SkIRect* inputRect) {
|
||||
return filter->onFilterNodeBounds(srcRect, ctm, dir, inputRect);
|
||||
}
|
||||
|
@ -47,4 +47,8 @@ sk_sp<SkImageFilter> SkApplyCTMToBackdropFilter(const SkImageFilter* filter, con
|
||||
|
||||
bool SkIsSameFilter(const SkImageFilter* a, const SkImageFilter* b);
|
||||
|
||||
// Exposes just the behavior of the protected SkImageFilter::onFilterNodeBounds()
|
||||
SkIRect SkFilterNodeBounds(const SkImageFilter* filter, const SkIRect& srcRect, const SkMatrix& ctm,
|
||||
SkImageFilter::MapDirection dir, const SkIRect* inputRect);
|
||||
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user