|
| 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 | + |
| 8 | +#include "samplecode/Sample.h" |
| 9 | + |
| 10 | +#include "include/core/SkCanvas.h" |
| 11 | +#include "include/core/SkColor.h" |
| 12 | +#include "include/core/SkFont.h" |
| 13 | +#include "include/core/SkPaint.h" |
| 14 | +#include "include/core/SkPath.h" |
| 15 | +#include "include/core/SkPoint.h" |
| 16 | +#include "include/core/SkRect.h" |
| 17 | +#include "include/effects/SkDashPathEffect.h" |
| 18 | +#include "include/effects/SkImageFilters.h" |
| 19 | + |
| 20 | +#include "src/core/SkImageFilterTypes.h" |
| 21 | +#include "src/core/SkImageFilter_Base.h" |
| 22 | + |
| 23 | +#include "tools/ToolUtils.h" |
| 24 | + |
| 25 | +static constexpr float kLineHeight = 16.f; |
| 26 | +static constexpr float kLineInset = 8.f; |
| 27 | + |
| 28 | +static float print_size(SkCanvas* canvas, const char* prefix, const SkIRect& rect, |
| 29 | + float x, float y, const SkFont& font, const SkPaint& paint) { |
| 30 | + canvas->drawString(prefix, x, y, font, paint); |
| 31 | + y += kLineHeight; |
| 32 | + SkString sz; |
| 33 | + sz.appendf("%d x %d", rect.width(), rect.height()); |
| 34 | + canvas->drawString(sz, x, y, font, paint); |
| 35 | + return y + kLineHeight; |
| 36 | +} |
| 37 | + |
| 38 | +static float print_info(SkCanvas* canvas, |
| 39 | + const SkIRect& layerContentBounds, |
| 40 | + const SkIRect& outputBounds, |
| 41 | + const SkIRect& hintedOutputBounds, |
| 42 | + const SkIRect& unhintedLayerBounds) { |
| 43 | + SkFont font(nullptr, 12); |
| 44 | + SkPaint text; |
| 45 | + text.setAntiAlias(true); |
| 46 | + |
| 47 | + float y = kLineHeight; |
| 48 | + |
| 49 | + text.setColor(SK_ColorRED); |
| 50 | + y = print_size(canvas, "Content (in layer)", layerContentBounds, kLineInset, y, font, text); |
| 51 | + text.setColor(SK_ColorDKGRAY); |
| 52 | + y = print_size(canvas, "Target (in device)", outputBounds, kLineInset, y, font, text); |
| 53 | + text.setColor(SK_ColorBLUE); |
| 54 | + y = print_size(canvas, "Output (w/ hint)", hintedOutputBounds, kLineInset, y, font, text); |
| 55 | + text.setColor(SK_ColorGREEN); |
| 56 | + y = print_size(canvas, "Input (w/ no hint)", unhintedLayerBounds, kLineInset, y, font, text); |
| 57 | + |
| 58 | + return y; |
| 59 | +} |
| 60 | + |
| 61 | +static SkPaint line_paint(SkColor color, bool dashed = false) { |
| 62 | + SkPaint paint; |
| 63 | + paint.setColor(color); |
| 64 | + paint.setStrokeWidth(0.f); |
| 65 | + paint.setStyle(SkPaint::kStroke_Style); |
| 66 | + paint.setAntiAlias(true); |
| 67 | + if (dashed) { |
| 68 | + SkScalar dash[2] = {10.f, 10.f}; |
| 69 | + paint.setPathEffect(SkDashPathEffect::Make(dash, 2, 0.f)); |
| 70 | + } |
| 71 | + return paint; |
| 72 | +} |
| 73 | + |
| 74 | +static SkPath create_axis_path(const SkRect& rect, float axisSpace) { |
| 75 | + SkPath localSpace; |
| 76 | + for (float y = rect.fTop + axisSpace; y <= rect.fBottom; y += axisSpace) { |
| 77 | + localSpace.moveTo(rect.fLeft, y); |
| 78 | + localSpace.lineTo(rect.fRight, y); |
| 79 | + } |
| 80 | + for (float x = rect.fLeft + axisSpace; x <= rect.fRight; x += axisSpace) { |
| 81 | + localSpace.moveTo(x, rect.fTop); |
| 82 | + localSpace.lineTo(x, rect.fBottom); |
| 83 | + } |
| 84 | + return localSpace; |
| 85 | +} |
| 86 | + |
| 87 | +class FilterBoundsSample : public Sample { |
| 88 | +public: |
| 89 | + FilterBoundsSample() {} |
| 90 | + |
| 91 | + void onOnceBeforeDraw() override { |
| 92 | + fBlur = SkImageFilters::Blur(8.f, 8.f, nullptr); |
| 93 | + fImage = SkImage::MakeFromBitmap(ToolUtils::create_checkerboard_bitmap( |
| 94 | + 300, 300, SK_ColorMAGENTA, SK_ColorLTGRAY, 50)); |
| 95 | + } |
| 96 | + |
| 97 | + void onDrawContent(SkCanvas* canvas) override { |
| 98 | + // The local content, e.g. what would be submitted to drawRect or the bounds to saveLayer |
| 99 | + const SkRect localContentRect = SkRect::MakeLTRB(100.f, 20.f, 180.f, 140.f); |
| 100 | + SkMatrix ctm = canvas->getTotalMatrix(); |
| 101 | + |
| 102 | + // Base rendering of a filter |
| 103 | + SkPaint blurPaint; |
| 104 | + blurPaint.setImageFilter(fBlur); |
| 105 | + canvas->saveLayer(&localContentRect, &blurPaint); |
| 106 | + SkPaint imagePaint; |
| 107 | + imagePaint.setFilterQuality(kLow_SkFilterQuality); |
| 108 | + canvas->drawImageRect(fImage, localContentRect, &imagePaint); |
| 109 | + canvas->restore(); |
| 110 | + |
| 111 | + // Now visualize the underlying bounds calculations used to determine the layer for the blur |
| 112 | + skif::Mapping mapping = skif::Mapping::DecomposeCTM(ctm, fBlur.get()); |
| 113 | + |
| 114 | + // Add axis lines, to show perspective distortion |
| 115 | + canvas->drawPath(create_axis_path(localContentRect, 10.f), line_paint(SK_ColorGRAY)); |
| 116 | + |
| 117 | + // The device content rect, e.g. the clip bounds if 'localContentRect' were used as a clip |
| 118 | + // before the draw or saveLayer, representing what the filter must cover if it affects |
| 119 | + // transparent black or doesn't have a local content hint. |
| 120 | + const SkRect devContentRect = ctm.mapRect(localContentRect); |
| 121 | + canvas->setMatrix(SkMatrix::I()); |
| 122 | + canvas->drawRect(devContentRect, line_paint(SK_ColorDKGRAY)); |
| 123 | + |
| 124 | + // Layer bounds for the filter, in the layer space compatible with the filter's matrix |
| 125 | + // type requirements. |
| 126 | + skif::ParameterSpace<SkRect> contentBounds(localContentRect); |
| 127 | + skif::DeviceSpace<SkIRect> targetOutput(devContentRect.roundOut()); |
| 128 | + skif::LayerSpace<SkIRect> targetOutputInLayer = mapping.deviceToLayer(targetOutput); |
| 129 | + |
| 130 | + skif::LayerSpace<SkIRect> hintedLayerBounds = as_IFB(fBlur)->getInputBounds( |
| 131 | + mapping, targetOutput, &contentBounds); |
| 132 | + skif::LayerSpace<SkIRect> unhintedLayerBounds = as_IFB(fBlur)->getInputBounds( |
| 133 | + mapping, targetOutput, nullptr); |
| 134 | + |
| 135 | + canvas->setMatrix(mapping.deviceMatrix()); |
| 136 | + canvas->drawRect(SkRect::Make(SkIRect(targetOutputInLayer)), |
| 137 | + line_paint(SK_ColorDKGRAY, true)); |
| 138 | + canvas->drawRect(SkRect::Make(SkIRect(hintedLayerBounds)), line_paint(SK_ColorRED)); |
| 139 | + canvas->drawRect(SkRect::Make(SkIRect(unhintedLayerBounds)), line_paint(SK_ColorGREEN)); |
| 140 | + |
| 141 | + // For visualization purposes, we want to show the layer-space output, this is what we get |
| 142 | + // when contentBounds is provided as a hint in local/parameter space. |
| 143 | + skif::Mapping layerOnly(SkMatrix::I(), mapping.layerMatrix()); |
| 144 | + skif::DeviceSpace<SkIRect> hintedOutputBounds = as_IFB(fBlur)->getOutputBounds( |
| 145 | + layerOnly, contentBounds); |
| 146 | + canvas->drawRect(SkRect::Make(SkIRect(hintedOutputBounds)), line_paint(SK_ColorBLUE)); |
| 147 | + |
| 148 | + canvas->resetMatrix(); |
| 149 | + print_info(canvas, SkIRect(mapping.paramToLayer(contentBounds).roundOut()), |
| 150 | + devContentRect.roundOut(), |
| 151 | + SkIRect(hintedOutputBounds), |
| 152 | + SkIRect(unhintedLayerBounds)); |
| 153 | + } |
| 154 | + |
| 155 | + SkString name() override { return SkString("FilterBounds"); } |
| 156 | + |
| 157 | +private: |
| 158 | + sk_sp<SkImageFilter> fBlur; |
| 159 | + sk_sp<SkImage> fImage; |
| 160 | + |
| 161 | + using INHERITED = Sample; |
| 162 | +}; |
| 163 | + |
| 164 | +DEF_SAMPLE(return new FilterBoundsSample();) |
0 commit comments