SampleImageFilterDAG.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. /*
  2. * Copyright 2019 Google LLC
  3. *
  4. * Use of this source code is governed by a BSD-style license that can be
  5. * found in the LICENSE file.
  6. */
  7. #include "samplecode/Sample.h"
  8. #include "include/core/SkCanvas.h"
  9. #include "include/core/SkColor.h"
  10. #include "include/core/SkFont.h"
  11. #include "include/core/SkImage.h"
  12. #include "include/core/SkImageFilter.h"
  13. #include "include/core/SkImageInfo.h"
  14. #include "include/core/SkPaint.h"
  15. #include "include/core/SkPoint.h"
  16. #include "include/core/SkRect.h"
  17. #include "include/core/SkSurface.h"
  18. #include "include/effects/SkBlurImageFilter.h"
  19. #include "include/effects/SkColorFilterImageFilter.h"
  20. #include "include/effects/SkDashPathEffect.h"
  21. #include "include/effects/SkDropShadowImageFilter.h"
  22. #include "include/effects/SkGradientShader.h"
  23. #include "include/effects/SkMergeImageFilter.h"
  24. #include "include/effects/SkOffsetImageFilter.h"
  25. #include "src/core/SkImageFilterPriv.h"
  26. #include "src/core/SkSpecialImage.h"
  27. #include "tools/ToolUtils.h"
  28. namespace {
  29. struct FilterNode {
  30. // Pointer to the actual filter in the DAG, so it still contains its input filters and
  31. // may be used as an input in an earlier node. Null when this represents the "source" input
  32. sk_sp<SkImageFilter> fFilter;
  33. // FilterNodes wrapping each of fFilter's inputs. Leaf node when fInputNodes is empty.
  34. SkTArray<FilterNode> fInputNodes;
  35. // Distance from root filter
  36. int fDepth;
  37. // The source content rect (this is the same for all nodes, but is stored here for convenience)
  38. SkRect fContent;
  39. // The portion of the original CTM that is kept as the local matrix/ctm when filtering
  40. SkMatrix fLocalCTM;
  41. // The portion of the original CTM that the results should be drawn with (or given current
  42. // canvas impl., the portion of the CTM that is baked into a new DAG)
  43. SkMatrix fRemainingCTM;
  44. // Cached reverse bounds using device-space clip bounds (e.g. SkCanvas::clipRectBounds with
  45. // null first argument). This represents the layer calculated in SkCanvas for the filtering.
  46. // FIXME: SkCanvas (and this sample), this is seeded with the device-space clip bounds so that
  47. // the implicit matrix node's reverse bounds are updated appropriately when it recurses to the
  48. // original root node.
  49. SkIRect fLayerBounds;
  50. // Cached reverse bounds using the local draw bounds (e.g. SkCanvas::clipRectBounds with the
  51. // draw bounds provided as first argument). For intermediate nodes in a DAG, this is calculated
  52. // to match what the filter would compute when being evaluated as part of the original DAG
  53. // (i.e. if the implicit matrix filter node were not inserted at the beginning).
  54. // fReverseLocalIsolatedBounds is the same, except it represents what would be calculated if
  55. // only this node were being applied as the image filter.
  56. SkIRect fReverseLocalBounds;
  57. SkIRect fReverseLocalIsolatedBounds;
  58. // Cached forward bounds based on local draw bounds. For intermediate nodes in a DAG, this is
  59. // calculated to match what the filter computes as part of the whole DAG. fForwardIsolatedBounds
  60. // is the same but represents what would be calculated if only this node were applied.
  61. SkIRect fForwardBounds;
  62. SkIRect fForwardIsolatedBounds;
  63. // Should be called after the input nodes have been created since this will complete the
  64. // entire tree.
  65. void computeBounds() {
  66. // In normal usage, forward bounds are filter-space bounds of the geometry content, so
  67. // fContent mapped by the local matrix, since we assume the layer content is made by
  68. // concat(localCTM) -> clipRect(content) -> drawRect(content).
  69. // Similarly, in normal usage, reverse bounds are the filter-space bounds of the space to
  70. // be filled by image filter results. Since the clip rect is set to the same as the content,
  71. // it's the same bounds forward or reverse in this contrived case.
  72. SkIRect inputRect;
  73. fLocalCTM.mapRect(fContent).roundOut(&inputRect);
  74. this->computeForwardBounds(inputRect);
  75. // The layer bounds (matching what SkCanvas computes), use the content rect mapped by the
  76. // entire CTM as its input rect. If this is an implicit matrix node, the computeReverseX
  77. // functions will switch to using the local-mapped bounds for children in order to simulate
  78. // what would happen if the last step were done as a draw. When there's no implicit matrix
  79. // node, this calculated rectangle is the same as inputRect.
  80. SkIRect deviceRect;
  81. SkMatrix ctm = SkMatrix::Concat(fRemainingCTM, fLocalCTM);
  82. ctm.mapRect(fContent).roundOut(&deviceRect);
  83. SkASSERT(this->isImplicitMatrixNode() || inputRect == deviceRect);
  84. this->computeReverseLocalIsolatedBounds(deviceRect);
  85. this->computeReverseBounds(deviceRect, false);
  86. // Unlike the above two calls, calculating layer bounds will keep the device bounds for
  87. // intermediate nodes to show the current SkCanvas behavior vs. the ideal
  88. this->computeReverseBounds(deviceRect, true);
  89. }
  90. bool isImplicitMatrixNode() const {
  91. // In the future we wish to replace the implicit matrix node with direct draws to the final
  92. // destination (instead of using an SkMatrixImageFilter). Visualizing the DAG correctly
  93. // requires handling these nodes differently since it has part of the canvas CTM built in.
  94. return fDepth == 1 && !fRemainingCTM.isIdentity();
  95. }
  96. private:
  97. void computeForwardBounds(const SkIRect srcRect) {
  98. if (fFilter) {
  99. // For forward filtering, the leaves of the DAG are evaluated first and are then
  100. // propagated to the root. This means that every filter's filterBounds() function sees
  101. // the original src rect. It is never dependent on the parent node (unlike reverse
  102. // filtering), so calling filterBounds() on an intermediate node gives us the correct
  103. // intermediate values.
  104. fForwardBounds = fFilter->filterBounds(
  105. srcRect, fLocalCTM, SkImageFilter::kForward_MapDirection, nullptr);
  106. // For isolated forward filtering, it uses the same input but should not be propagated
  107. // to the inputs, so get the filter node bounds directly.
  108. fForwardIsolatedBounds = SkFilterNodeBounds(
  109. fFilter.get(), srcRect, fLocalCTM,
  110. SkImageFilter::kForward_MapDirection, nullptr);
  111. } else {
  112. fForwardBounds = srcRect;
  113. fForwardIsolatedBounds = srcRect;
  114. }
  115. // Fill in children
  116. for (int i = 0; i < fInputNodes.count(); ++i) {
  117. fInputNodes[i].computeForwardBounds(srcRect);
  118. }
  119. }
  120. void computeReverseLocalIsolatedBounds(const SkIRect& srcRect) {
  121. if (fFilter) {
  122. fReverseLocalIsolatedBounds = SkFilterNodeBounds(
  123. fFilter.get(), srcRect, fLocalCTM,
  124. SkImageFilter::kReverse_MapDirection, &srcRect);
  125. } else {
  126. fReverseLocalIsolatedBounds = srcRect;
  127. }
  128. SkIRect childSrcRect = srcRect;
  129. if (this->isImplicitMatrixNode()) {
  130. // Switch srcRect from the device-space bounds to what would be used when the draw is
  131. // the final step of filtering, as if the implicit node weren't needed
  132. fLocalCTM.mapRect(fContent).roundOut(&childSrcRect);
  133. }
  134. // Fill in children. Unlike regular reverse bounds mapping, the input nodes see the original
  135. // bounds. Normally, the bounds that the child nodes see have already been mapped processed
  136. // by this node.
  137. for (int i = 0; i < fInputNodes.count(); ++i) {
  138. fInputNodes[i].computeReverseLocalIsolatedBounds(childSrcRect);
  139. }
  140. }
  141. // fReverseLocalBounds and fLayerBounds are computed the same, except they differ in what the
  142. // initial bounding rectangle was. It is assumed that the 'srcRect' has already been processed
  143. // by the parent node's onFilterNodeBounds() function, as in SkImageFilter::filterBounds().
  144. void computeReverseBounds(const SkIRect& srcRect, bool writeToLayerBounds) {
  145. SkIRect reverseBounds = srcRect;
  146. if (fFilter) {
  147. // Since srcRect has been through parent's onFilterNodeBounds(), calling filterBounds()
  148. // directly on this node will calculate the same rectangle that this filter would report
  149. // during the parent node's onFilterBounds() recursion.
  150. reverseBounds = fFilter->filterBounds(
  151. srcRect, fLocalCTM, SkImageFilter::kReverse_MapDirection, &srcRect);
  152. SkIRect nextSrcRect;
  153. if (this->isImplicitMatrixNode() && !writeToLayerBounds) {
  154. // When not writing to the layer bounds, and we're the implicit matrix node
  155. // we reset the src rect to be what it should be if no implicit node was necessary.
  156. fLocalCTM.mapRect(fContent).roundOut(&nextSrcRect);
  157. } else {
  158. // To calculate the appropriate intermediate reverse bounds for the children, we
  159. // need this node's onFilterNodeBounds() results based on its parents' bounds (the
  160. // current 'srcRect').
  161. nextSrcRect = SkFilterNodeBounds(
  162. fFilter.get(), srcRect, fLocalCTM,
  163. SkImageFilter::kReverse_MapDirection, &srcRect);
  164. }
  165. // Fill in the children. The union of these bounds should equal the value calculated
  166. // for reverseBounds already.
  167. SkDEBUGCODE(SkIRect netReverseBounds = SkIRect::MakeEmpty();)
  168. for (int i = 0; i < fInputNodes.count(); ++i) {
  169. fInputNodes[i].computeReverseBounds(nextSrcRect, writeToLayerBounds);
  170. SkDEBUGCODE(netReverseBounds.join(
  171. writeToLayerBounds ? fInputNodes[i].fLayerBounds
  172. : fInputNodes[i].fReverseLocalBounds);)
  173. }
  174. // Because of the resetting done when not computing layer bounds for the implicit
  175. // matrix node, this assertion doesn't hold in that particular scenario.
  176. SkASSERT(netReverseBounds == reverseBounds ||
  177. (this->isImplicitMatrixNode() && !writeToLayerBounds));
  178. }
  179. if (writeToLayerBounds) {
  180. fLayerBounds = reverseBounds;
  181. } else {
  182. fReverseLocalBounds = reverseBounds;
  183. }
  184. }
  185. };
  186. } // anonymous namespace
  187. static FilterNode build_dag(const SkMatrix& local, const SkMatrix& remainder, const SkRect& rect,
  188. const SkImageFilter* filter, int depth) {
  189. FilterNode node;
  190. node.fFilter = sk_ref_sp(filter);
  191. node.fDepth = depth;
  192. node.fContent = rect;
  193. node.fLocalCTM = local;
  194. node.fRemainingCTM = remainder;
  195. if (node.fFilter) {
  196. if (depth > 0) {
  197. // We don't visit children when at the root because the real child filters are replaced
  198. // with the internalSaveLayer decomposition emulation, which then cycles back to the
  199. // original filter but with an updated matrix (and then we process the children).
  200. node.fInputNodes.reserve(node.fFilter->countInputs());
  201. for (int i = 0; i < node.fFilter->countInputs(); ++i) {
  202. node.fInputNodes.push_back() =
  203. build_dag(local, remainder, rect, node.fFilter->getInput(i), depth + 1);
  204. }
  205. }
  206. }
  207. return node;
  208. }
  209. static FilterNode build_dag(const SkMatrix& ctm, const SkRect& rect,
  210. const SkImageFilter* rootFilter) {
  211. // Emulate SkCanvas::internalSaveLayer's decomposition of the CTM.
  212. SkMatrix local;
  213. sk_sp<SkImageFilter> finalFilter = SkApplyCTMToFilter(rootFilter, ctm, &local);
  214. // In ApplyCTMToFilter, the CTM is decomposed such that CTM = remainder * local. The matrix
  215. // that is embedded in 'finalFilter' is actually local^-1*remainder*local to account for
  216. // how SkMatrixImageFilter is specified, but we want the true remainder since it is what should
  217. // transform the results to put in the correct place after filtering.
  218. SkMatrix invLocal, remaining;
  219. if (!SkIsSameFilter(finalFilter.get(), rootFilter) && local.invert(&invLocal)) {
  220. remaining = SkMatrix::Concat(ctm, invLocal);
  221. } else {
  222. remaining = SkMatrix::I();
  223. }
  224. // Create a root node that represents the full result
  225. FilterNode rootNode = build_dag(ctm, SkMatrix::I(), rect, rootFilter, 0);
  226. // Set its only child as the modified DAG that handles the CTM decomposition
  227. rootNode.fInputNodes.push_back() =
  228. build_dag(local, remaining, rect, finalFilter.get(), 1);
  229. // Fill in bounds information that requires the entire node DAG to have been extracted first.
  230. rootNode.fInputNodes[0].computeBounds();
  231. return rootNode;
  232. }
  233. static void draw_node(SkCanvas* canvas, const FilterNode& node) {
  234. canvas->clear(SK_ColorTRANSPARENT);
  235. SkPaint filterPaint;
  236. filterPaint.setImageFilter(node.fFilter);
  237. SkPaint paint;
  238. static const SkColor kColors[2] = {SK_ColorGREEN, SK_ColorWHITE};
  239. SkPoint points[2] = { {node.fContent.fLeft + 15.f, node.fContent.fTop + 15.f},
  240. {node.fContent.fRight - 15.f, node.fContent.fBottom - 15.f} };
  241. paint.setShader(SkGradientShader::MakeLinear(points, kColors, nullptr, SK_ARRAY_COUNT(kColors),
  242. SkTileMode::kRepeat));
  243. SkPaint line;
  244. line.setStrokeWidth(0.f);
  245. line.setStyle(SkPaint::kStroke_Style);
  246. if (node.fDepth == 0) {
  247. // The root node, so draw this one the canonical way through SkCanvas to show current
  248. // net behavior. Will not include bounds visualization.
  249. canvas->save();
  250. canvas->concat(node.fLocalCTM);
  251. SkASSERT(node.fRemainingCTM.isIdentity());
  252. canvas->clipRect(node.fContent, /* aa */ true);
  253. canvas->saveLayer(nullptr, &filterPaint);
  254. canvas->drawRect(node.fContent, paint);
  255. canvas->restore(); // Completes the image filter
  256. canvas->restore(); // Undoes matrix and clip
  257. // Draw content rect (no clipping)
  258. canvas->save();
  259. canvas->concat(node.fLocalCTM);
  260. line.setColor(SK_ColorBLACK);
  261. canvas->drawRect(node.fContent, line);
  262. canvas->restore();
  263. } else {
  264. canvas->save();
  265. if (!node.isImplicitMatrixNode()) {
  266. canvas->concat(node.fRemainingCTM);
  267. }
  268. canvas->concat(node.fLocalCTM);
  269. canvas->saveLayer(nullptr, &filterPaint);
  270. canvas->drawRect(node.fContent, paint);
  271. canvas->restore(); // Completes the image filter
  272. // Draw content-rect bounds
  273. line.setColor(SK_ColorBLACK);
  274. if (node.isImplicitMatrixNode()) {
  275. canvas->setMatrix(SkMatrix::Concat(node.fRemainingCTM, node.fLocalCTM));
  276. }
  277. canvas->drawRect(node.fContent, line);
  278. canvas->restore(); // Undoes the matrix
  279. // Bounding boxes have all been mapped by the local matrix already, so drawing them with
  280. // the remaining CTM should align everything to the already drawn filter outputs. The
  281. // exception is forward bounds of the implicit matrix node, which also have been mapped
  282. // by the remainder matrix.
  283. canvas->save();
  284. canvas->concat(node.fRemainingCTM);
  285. // The bounds of the layer saved for the filtering as currently implemented
  286. line.setColor(SK_ColorRED);
  287. canvas->drawRect(SkRect::Make(node.fLayerBounds).makeOutset(5.f, 5.f), line);
  288. // The bounds of the layer that could be saved if the last step were a draw
  289. line.setColor(SK_ColorMAGENTA);
  290. canvas->drawRect(SkRect::Make(node.fReverseLocalBounds).makeOutset(4.f, 4.f), line);
  291. // Dashed lines for the isolated shapes
  292. static const SkScalar kDashParams[] = {6.f, 12.f};
  293. line.setPathEffect(SkDashPathEffect::Make(kDashParams, 2, 0.f));
  294. // The bounds of the layer if it were the only filter in the DAG
  295. canvas->drawRect(SkRect::Make(node.fReverseLocalIsolatedBounds).makeOutset(3.f, 3.f), line);
  296. if (node.isImplicitMatrixNode()) {
  297. canvas->resetMatrix();
  298. }
  299. // The output bounds calculated as if the node were the only filter in the DAG
  300. line.setColor(SK_ColorBLUE);
  301. canvas->drawRect(SkRect::Make(node.fForwardIsolatedBounds).makeOutset(1.f, 1.f), line);
  302. // The output bounds calculated for the node
  303. line.setPathEffect(nullptr);
  304. canvas->drawRect(SkRect::Make(node.fForwardBounds).makeOutset(2.f, 2.f), line);
  305. canvas->restore();
  306. }
  307. }
  308. static constexpr float kLineHeight = 16.f;
  309. static constexpr float kLineInset = 8.f;
  310. static float print_matrix(SkCanvas* canvas, const char* prefix, const SkMatrix& matrix,
  311. float x, float y, const SkFont& font, const SkPaint& paint) {
  312. canvas->drawString(prefix, x, y, font, paint);
  313. y += kLineHeight;
  314. for (int i = 0; i < 3; ++i) {
  315. SkString row;
  316. row.appendf("[%.2f %.2f %.2f]",
  317. matrix.get(i * 3), matrix.get(i * 3 + 1), matrix.get(i * 3 + 2));
  318. canvas->drawString(row, x, y, font, paint);
  319. y += kLineHeight;
  320. }
  321. return y;
  322. }
  323. static float print_size(SkCanvas* canvas, const char* prefix, const SkIRect& rect,
  324. float x, float y, const SkFont& font, const SkPaint& paint) {
  325. canvas->drawString(prefix, x, y, font, paint);
  326. y += kLineHeight;
  327. SkString sz;
  328. sz.appendf("%d x %d", rect.width(), rect.height());
  329. canvas->drawString(sz, x, y, font, paint);
  330. return y + kLineHeight;
  331. }
  332. static float print_info(SkCanvas* canvas, const FilterNode& node) {
  333. SkFont font(nullptr, 12);
  334. SkPaint text;
  335. text.setAntiAlias(true);
  336. float y = kLineHeight;
  337. if (node.fDepth == 0) {
  338. canvas->drawString("Final Results", kLineInset, y, font, text);
  339. // The actual interesting matrices are in the root node's first child
  340. y = print_matrix(canvas, "Local", node.fInputNodes[0].fLocalCTM,
  341. kLineInset, y + kLineHeight, font, text);
  342. y = print_matrix(canvas, "Embedded", node.fInputNodes[0].fRemainingCTM,
  343. kLineInset, y, font, text);
  344. } else if (node.fFilter) {
  345. canvas->drawString(node.fFilter->getTypeName(), kLineInset, y, font, text);
  346. print_size(canvas, "Layer Size", node.fLayerBounds, kLineInset, y + kLineHeight,
  347. font, text);
  348. y = print_size(canvas, "Ideal Size", node.fReverseLocalBounds, 10 * kLineInset,
  349. y + kLineHeight, font, text);
  350. } else {
  351. canvas->drawString("Source Input", kLineInset, kLineHeight, font, text);
  352. y += kLineHeight;
  353. }
  354. return y;
  355. }
  356. // Returns bottom edge in pixels that the subtree reached in canvas
  357. static float draw_dag(SkCanvas* canvas, SkSurface* nodeSurface, const FilterNode& node) {
  358. // First capture the results of the node, into nodeSurface
  359. draw_node(nodeSurface->getCanvas(), node);
  360. sk_sp<SkImage> nodeResults = nodeSurface->makeImageSnapshot();
  361. // Fill in background of the filter node with a checkerboard
  362. canvas->save();
  363. canvas->clipRect(SkRect::MakeWH(nodeResults->width(), nodeResults->height()));
  364. ToolUtils::draw_checkerboard(canvas, SK_ColorGRAY, SK_ColorLTGRAY, 10);
  365. canvas->restore();
  366. // Display filtered results in current canvas' location (assumed CTM is set for this node)
  367. canvas->drawImage(nodeResults, 0, 0);
  368. SkPaint line;
  369. line.setAntiAlias(true);
  370. line.setStyle(SkPaint::kStroke_Style);
  371. line.setStrokeWidth(3.f);
  372. // Text info
  373. canvas->save();
  374. canvas->translate(0, nodeResults->height());
  375. float textHeight = print_info(canvas, node);
  376. canvas->restore();
  377. // Border around filtered results + text info
  378. canvas->drawRect(SkRect::MakeWH(nodeResults->width(), nodeResults->height() + textHeight),
  379. line);
  380. static const float kPad = 20.f;
  381. float x = nodeResults->width() + kPad;
  382. float y = 0;
  383. for (int i = 0; i < node.fInputNodes.count(); ++i) {
  384. // Line connecting this node to its child
  385. canvas->drawLine(nodeResults->width(), 0.5f * nodeResults->height(), // right of node
  386. x, y + 0.5f * nodeResults->height(), line); // left of child
  387. canvas->save();
  388. canvas->translate(x, y);
  389. y = draw_dag(canvas, nodeSurface, node.fInputNodes[i]);
  390. canvas->restore();
  391. }
  392. return SkMaxScalar(y, nodeResults->height() + textHeight + kPad);
  393. }
  394. static void draw_dag(SkCanvas* canvas, sk_sp<SkImageFilter> filter,
  395. const SkRect& rect, const SkISize& surfaceSize) {
  396. // Get the current CTM, which includes all the viewer's UI modifications, which we want to
  397. // pass into our mock canvases for each DAG node.
  398. SkMatrix ctm = canvas->getTotalMatrix();
  399. canvas->save();
  400. // Reset the matrix so that the DAG layout and instructional text is fixed to the window.
  401. canvas->resetMatrix();
  402. // Process the image filter DAG to display intermediate results later on, which will apply the
  403. // provided CTM during draw_node calls.
  404. FilterNode dag = build_dag(ctm, rect, filter.get());
  405. sk_sp<SkSurface> nodeSurface = canvas->makeSurface(
  406. canvas->imageInfo().makeWH(surfaceSize.width(), surfaceSize.height()));
  407. draw_dag(canvas, nodeSurface.get(), dag);
  408. canvas->restore();
  409. }
  410. class ImageFilterDAGSample : public Sample {
  411. public:
  412. ImageFilterDAGSample() {}
  413. void onDrawContent(SkCanvas* canvas) override {
  414. static const SkRect kFilterRect = SkRect::MakeXYWH(20.f, 20.f, 60.f, 60.f);
  415. static const SkISize kFilterSurfaceSize = SkISize::Make(
  416. 2 * (kFilterRect.fRight + kFilterRect.fLeft),
  417. 2 * (kFilterRect.fBottom + kFilterRect.fTop));
  418. // Somewhat clunky, but we want to use the viewer calculated CTM in the mini surfaces used
  419. // per DAG node. The rotation matrix viewer calculates is based on the sample size so trick
  420. // it into calculating the right matrix for us w/ 1 frame latency.
  421. this->setSize(kFilterSurfaceSize.width(), kFilterSurfaceSize.height());
  422. // Make a large DAG
  423. // /--- Color Filter <---- Blur <--- Offset
  424. // Merge <
  425. // \--- Blur <--- Drop Shadow
  426. sk_sp<SkImageFilter> drop2 = SkDropShadowImageFilter::Make(
  427. 10.f, 5.f, 3.f, 3.f, SK_ColorBLACK,
  428. SkDropShadowImageFilter::kDrawShadowAndForeground_ShadowMode, nullptr);
  429. sk_sp<SkImageFilter> blur1 = SkBlurImageFilter::Make(2.f, 2.f, std::move(drop2));
  430. sk_sp<SkImageFilter> offset3 = SkOffsetImageFilter::Make(-5.f, -5.f, nullptr);
  431. sk_sp<SkImageFilter> blur2 = SkBlurImageFilter::Make(4.f, 4.f, std::move(offset3));
  432. sk_sp<SkImageFilter> cf1 = SkColorFilterImageFilter::Make(
  433. SkColorFilters::Blend(SK_ColorGRAY, SkBlendMode::kModulate), std::move(blur2));
  434. sk_sp<SkImageFilter> merge0 = SkMergeImageFilter::Make(std::move(blur1), std::move(cf1));
  435. draw_dag(canvas, std::move(merge0), kFilterRect, kFilterSurfaceSize);
  436. }
  437. SkString name() override { return SkString("ImageFilterDAG"); }
  438. private:
  439. typedef Sample INHERITED;
  440. };
  441. DEF_SAMPLE(return new ImageFilterDAGSample();)