9241a6d394
This is a reland of6fc04f88a8
Original change's description: > Reland "SkSurface asynchronous read APIs allow client to extend pixel lifetime" > > This is a reland ofce240cc6fd
> > Original change's description: > > SkSurface asynchronous read APIs allow client to extend pixel lifetime > > > > Previously the pixel data passed to the client was only valid during > > the client's callback. This meant if the client wanted to defer > > processing of the data a copy was necessary. > > > > Now we pass an object to the callback and the pixel lifetime is tied > > to the lifetime of that object. > > > > The object may be holding a GPU transfer buffer mapped. We don't assume > > that the object will be released on the direct GrContext thread. So > > when the object is destroyed it posts a message to a new type, > > GrClientMappedBufferManager, hanging off the direct context. The direct > > context will periodically check for messages and unmap and then unref > > buffers so that they can be reused. Currently this is done in > > GrContext::performDeferredCleanup() and GrDrawingManager::flush(). > > > > The old API is kept around for backwards compatibility but it is > > reimplemented as a bridge on top of the new mechanism. > > > > Also a utility function to SkImageInfo is added to directly make a new > > info with a specified dimensions rather than passing the width and > > height separately to makeWH(). > > > > Bug: chromium:973403 > > Bug: skia:8962 > > > > Change-Id: Id5cf04235376170142a48e90d3ecd13fd021a2a6 > > Reviewed-on: https://skia-review.googlesource.com/c/skia/+/245457 > > Reviewed-by: Brian Osman <brianosman@google.com> > > Commit-Queue: Brian Salomon <bsalomon@google.com> > > Bug: chromium:973403, skia:8962 > Change-Id: I5cecd36276c8b6dc942cf549c7095db2df88530c > Reviewed-on: https://skia-review.googlesource.com/c/skia/+/245678 > Reviewed-by: Brian Salomon <bsalomon@google.com> > Commit-Queue: Brian Salomon <bsalomon@google.com> Bug: chromium:973403, skia:8962 Change-Id: Ie584c1c3ef8021c976f71b708e53871c693cc450 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/246057 Reviewed-by: Brian Osman <brianosman@google.com> Commit-Queue: Brian Salomon <bsalomon@google.com>
516 lines
23 KiB
C++
516 lines
23 KiB
C++
/*
|
|
* 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/SkColorFilter.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/SkDashPathEffect.h"
|
|
#include "include/effects/SkGradientShader.h"
|
|
#include "include/effects/SkImageFilters.h"
|
|
|
|
#include "src/core/SkImageFilter_Base.h"
|
|
#include "src/core/SkSpecialImage.h"
|
|
|
|
#include "tools/ToolUtils.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), this is seeded with the device-space clip bounds so that
|
|
// the implicit matrix node's reverse bounds are updated appropriately when it recurses to the
|
|
// original root node.
|
|
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
|
|
// (i.e. if the implicit matrix filter node were not inserted at the beginning).
|
|
// 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);
|
|
|
|
// The layer bounds (matching what SkCanvas computes), use the content rect mapped by the
|
|
// entire CTM as its input rect. If this is an implicit matrix node, the computeReverseX
|
|
// functions will switch to using the local-mapped bounds for children in order to simulate
|
|
// what would happen if the last step were done as a draw. When there's no implicit matrix
|
|
// node, this calculated rectangle is the same as inputRect.
|
|
SkIRect deviceRect;
|
|
SkMatrix ctm = SkMatrix::Concat(fRemainingCTM, fLocalCTM);
|
|
ctm.mapRect(fContent).roundOut(&deviceRect);
|
|
|
|
SkASSERT(this->isImplicitMatrixNode() || inputRect == deviceRect);
|
|
this->computeReverseLocalIsolatedBounds(deviceRect);
|
|
this->computeReverseBounds(deviceRect, false);
|
|
// Unlike the above two calls, calculating layer bounds will keep the device bounds for
|
|
// intermediate nodes to show the current SkCanvas behavior vs. the ideal
|
|
this->computeReverseBounds(deviceRect, true);
|
|
}
|
|
|
|
bool isImplicitMatrixNode() const {
|
|
// In the future we wish to replace the implicit matrix node with direct draws to the final
|
|
// destination (instead of using an SkMatrixImageFilter). Visualizing the DAG correctly
|
|
// requires handling these nodes differently since it has part of the canvas CTM built in.
|
|
return fDepth == 1 && !fRemainingCTM.isIdentity();
|
|
}
|
|
|
|
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 = as_IFB(fFilter)->filterNodeBounds(
|
|
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 = as_IFB(fFilter)->filterNodeBounds(
|
|
srcRect, fLocalCTM, SkImageFilter::kReverse_MapDirection, &srcRect);
|
|
} else {
|
|
fReverseLocalIsolatedBounds = srcRect;
|
|
}
|
|
|
|
SkIRect childSrcRect = srcRect;
|
|
if (this->isImplicitMatrixNode()) {
|
|
// Switch srcRect from the device-space bounds to what would be used when the draw is
|
|
// the final step of filtering, as if the implicit node weren't needed
|
|
fLocalCTM.mapRect(fContent).roundOut(&childSrcRect);
|
|
}
|
|
|
|
// 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(childSrcRect);
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
|
|
SkIRect nextSrcRect;
|
|
if (this->isImplicitMatrixNode() && !writeToLayerBounds) {
|
|
// When not writing to the layer bounds, and we're the implicit matrix node
|
|
// we reset the src rect to be what it should be if no implicit node was necessary.
|
|
fLocalCTM.mapRect(fContent).roundOut(&nextSrcRect);
|
|
} else {
|
|
// 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').
|
|
nextSrcRect = as_IFB(fFilter)->filterNodeBounds(
|
|
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);)
|
|
}
|
|
// Because of the resetting done when not computing layer bounds for the implicit
|
|
// matrix node, this assertion doesn't hold in that particular scenario.
|
|
SkASSERT(netReverseBounds == reverseBounds ||
|
|
(this->isImplicitMatrixNode() && !writeToLayerBounds));
|
|
}
|
|
|
|
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 = as_IFB(rootFilter)->applyCTM(ctm, &local);
|
|
|
|
// In ApplyCTMToFilter, the CTM is decomposed such that CTM = remainder * local. The matrix
|
|
// that is embedded in 'finalFilter' is actually local^-1*remainder*local to account for
|
|
// how SkMatrixImageFilter is specified, but we want the true remainder since it is what should
|
|
// transform the results to put in the correct place after filtering.
|
|
SkMatrix invLocal, remaining;
|
|
if (as_IFB(rootFilter)->uniqueID() != as_IFB(finalFilter)->uniqueID()) {
|
|
remaining = SkMatrix::Concat(ctm, invLocal);
|
|
} else {
|
|
remaining = 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, remaining, 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 SkColor kColors[2] = {SK_ColorGREEN, SK_ColorWHITE};
|
|
SkPoint points[2] = { {node.fContent.fLeft + 15.f, node.fContent.fTop + 15.f},
|
|
{node.fContent.fRight - 15.f, node.fContent.fBottom - 15.f} };
|
|
paint.setShader(SkGradientShader::MakeLinear(points, kColors, nullptr, SK_ARRAY_COUNT(kColors),
|
|
SkTileMode::kRepeat));
|
|
|
|
SkPaint line;
|
|
line.setStrokeWidth(0.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, /* aa */ true);
|
|
canvas->saveLayer(nullptr, &filterPaint);
|
|
canvas->drawRect(node.fContent, paint);
|
|
canvas->restore(); // Completes the image filter
|
|
canvas->restore(); // Undoes matrix and clip
|
|
|
|
// Draw content rect (no clipping)
|
|
canvas->save();
|
|
canvas->concat(node.fLocalCTM);
|
|
line.setColor(SK_ColorBLACK);
|
|
canvas->drawRect(node.fContent, line);
|
|
canvas->restore();
|
|
} else {
|
|
canvas->save();
|
|
if (!node.isImplicitMatrixNode()) {
|
|
canvas->concat(node.fRemainingCTM);
|
|
}
|
|
canvas->concat(node.fLocalCTM);
|
|
|
|
canvas->saveLayer(nullptr, &filterPaint);
|
|
canvas->drawRect(node.fContent, paint);
|
|
canvas->restore(); // Completes the image filter
|
|
|
|
// Draw content-rect bounds
|
|
line.setColor(SK_ColorBLACK);
|
|
if (node.isImplicitMatrixNode()) {
|
|
canvas->setMatrix(SkMatrix::Concat(node.fRemainingCTM, node.fLocalCTM));
|
|
}
|
|
canvas->drawRect(node.fContent, line);
|
|
canvas->restore(); // Undoes the matrix
|
|
|
|
// Bounding boxes have all been mapped by the local matrix already, so drawing them with
|
|
// the remaining CTM should align everything to the already drawn filter outputs. The
|
|
// exception is forward bounds of the implicit matrix node, which also have been mapped
|
|
// by the remainder matrix.
|
|
canvas->save();
|
|
canvas->concat(node.fRemainingCTM);
|
|
|
|
// The bounds of the layer saved for the filtering as currently implemented
|
|
line.setColor(SK_ColorRED);
|
|
canvas->drawRect(SkRect::Make(node.fLayerBounds).makeOutset(5.f, 5.f), line);
|
|
// The bounds of the layer that could be saved if the last step were a draw
|
|
line.setColor(SK_ColorMAGENTA);
|
|
canvas->drawRect(SkRect::Make(node.fReverseLocalBounds).makeOutset(4.f, 4.f), line);
|
|
|
|
// Dashed lines for the isolated shapes
|
|
static const SkScalar kDashParams[] = {6.f, 12.f};
|
|
line.setPathEffect(SkDashPathEffect::Make(kDashParams, 2, 0.f));
|
|
// The bounds of the layer if it were the only filter in the DAG
|
|
canvas->drawRect(SkRect::Make(node.fReverseLocalIsolatedBounds).makeOutset(3.f, 3.f), line);
|
|
|
|
if (node.isImplicitMatrixNode()) {
|
|
canvas->resetMatrix();
|
|
}
|
|
// The output bounds calculated as if the node were the only filter in the DAG
|
|
line.setColor(SK_ColorBLUE);
|
|
canvas->drawRect(SkRect::Make(node.fForwardIsolatedBounds).makeOutset(1.f, 1.f), line);
|
|
|
|
// The output bounds calculated for the node
|
|
line.setPathEffect(nullptr);
|
|
canvas->drawRect(SkRect::Make(node.fForwardBounds).makeOutset(2.f, 2.f), line);
|
|
|
|
canvas->restore();
|
|
}
|
|
}
|
|
|
|
static constexpr float kLineHeight = 16.f;
|
|
static constexpr float kLineInset = 8.f;
|
|
|
|
static float 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;
|
|
}
|
|
return y;
|
|
}
|
|
|
|
static float 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);
|
|
return y + kLineHeight;
|
|
}
|
|
|
|
static float print_info(SkCanvas* canvas, const FilterNode& node) {
|
|
SkFont font(nullptr, 12);
|
|
SkPaint text;
|
|
text.setAntiAlias(true);
|
|
|
|
float y = kLineHeight;
|
|
if (node.fDepth == 0) {
|
|
canvas->drawString("Final Results", kLineInset, y, font, text);
|
|
// The actual interesting matrices are in the root node's first child
|
|
y = print_matrix(canvas, "Local", node.fInputNodes[0].fLocalCTM,
|
|
kLineInset, y + kLineHeight, font, text);
|
|
y = print_matrix(canvas, "Embedded", node.fInputNodes[0].fRemainingCTM,
|
|
kLineInset, y, font, text);
|
|
} else if (node.fFilter) {
|
|
canvas->drawString(node.fFilter->getTypeName(), kLineInset, y, font, text);
|
|
print_size(canvas, "Layer Size", node.fLayerBounds, kLineInset, y + kLineHeight,
|
|
font, text);
|
|
y = print_size(canvas, "Ideal Size", node.fReverseLocalBounds, 10 * kLineInset,
|
|
y + kLineHeight, font, text);
|
|
} else {
|
|
canvas->drawString("Source Input", kLineInset, kLineHeight, font, text);
|
|
y += kLineHeight;
|
|
}
|
|
|
|
return y;
|
|
}
|
|
|
|
// Returns bottom edge in pixels that the subtree reached 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();
|
|
|
|
// Fill in background of the filter node with a checkerboard
|
|
canvas->save();
|
|
canvas->clipRect(SkRect::MakeWH(nodeResults->width(), nodeResults->height()));
|
|
ToolUtils::draw_checkerboard(canvas, SK_ColorGRAY, SK_ColorLTGRAY, 10);
|
|
canvas->restore();
|
|
|
|
// Display filtered results in current canvas' location (assumed CTM is set for this node)
|
|
canvas->drawImage(nodeResults, 0, 0);
|
|
|
|
SkPaint line;
|
|
line.setAntiAlias(true);
|
|
line.setStyle(SkPaint::kStroke_Style);
|
|
line.setStrokeWidth(3.f);
|
|
|
|
// Text info
|
|
canvas->save();
|
|
canvas->translate(0, nodeResults->height());
|
|
float textHeight = print_info(canvas, node);
|
|
canvas->restore();
|
|
|
|
// Border around filtered results + text info
|
|
canvas->drawRect(SkRect::MakeWH(nodeResults->width(), nodeResults->height() + textHeight),
|
|
line);
|
|
|
|
static const float kPad = 20.f;
|
|
float x = nodeResults->width() + kPad;
|
|
float y = 0;
|
|
for (int i = 0; i < node.fInputNodes.count(); ++i) {
|
|
// Line connecting this node to its child
|
|
canvas->drawLine(nodeResults->width(), 0.5f * nodeResults->height(), // right of node
|
|
x, y + 0.5f * nodeResults->height(), line); // left of child
|
|
canvas->save();
|
|
canvas->translate(x, y);
|
|
y = draw_dag(canvas, nodeSurface, node.fInputNodes[i]);
|
|
canvas->restore();
|
|
}
|
|
return SkMaxScalar(y, nodeResults->height() + textHeight + 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().makeDimensions(surfaceSize));
|
|
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 = SkImageFilters::DropShadow(
|
|
10.f, 5.f, 3.f, 3.f, SK_ColorBLACK, nullptr);
|
|
sk_sp<SkImageFilter> blur1 = SkImageFilters::Blur(2.f, 2.f, std::move(drop2));
|
|
|
|
sk_sp<SkImageFilter> offset3 = SkImageFilters::Offset(-5.f, -5.f, nullptr);
|
|
sk_sp<SkImageFilter> blur2 = SkImageFilters::Blur(4.f, 4.f, std::move(offset3));
|
|
sk_sp<SkImageFilter> cf1 = SkImageFilters::ColorFilter(
|
|
SkColorFilters::Blend(SK_ColorGRAY, SkBlendMode::kModulate), std::move(blur2));
|
|
|
|
sk_sp<SkImageFilter> merge0 = SkImageFilters::Merge(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();)
|