73e6a66146
Change-Id: Ibfa2c0efa3e4addf21aa400e3d323675ac0185ef Reviewed-on: https://skia-review.googlesource.com/c/skia/+/551886 Reviewed-by: John Stiles <johnstiles@google.com> Commit-Queue: Herb Derby <herb@google.com>
260 lines
11 KiB
C++
260 lines
11 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/SkBitmap.h"
|
|
#include "include/core/SkCanvas.h"
|
|
#include "include/core/SkColor.h"
|
|
#include "include/core/SkFont.h"
|
|
#include "include/core/SkPaint.h"
|
|
#include "include/core/SkPath.h"
|
|
#include "include/core/SkPoint.h"
|
|
#include "include/core/SkRect.h"
|
|
#include "include/effects/SkDashPathEffect.h"
|
|
#include "include/effects/SkGradientShader.h"
|
|
#include "include/effects/SkImageFilters.h"
|
|
|
|
#include "src/core/SkImageFilterTypes.h"
|
|
#include "src/core/SkImageFilter_Base.h"
|
|
#include "src/core/SkMatrixPriv.h"
|
|
|
|
#include "tools/ToolUtils.h"
|
|
|
|
static constexpr float kLineHeight = 16.f;
|
|
static constexpr float kLineInset = 8.f;
|
|
|
|
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 SkIRect& layerContentBounds,
|
|
const SkIRect& outputBounds,
|
|
const SkIRect& hintedOutputBounds,
|
|
const SkIRect& unhintedLayerBounds) {
|
|
SkFont font(nullptr, 12);
|
|
SkPaint text;
|
|
text.setAntiAlias(true);
|
|
|
|
float y = kLineHeight;
|
|
|
|
text.setColor(SK_ColorRED);
|
|
y = print_size(canvas, "Content (in layer)", layerContentBounds, kLineInset, y, font, text);
|
|
text.setColor(SK_ColorDKGRAY);
|
|
y = print_size(canvas, "Target (in device)", outputBounds, kLineInset, y, font, text);
|
|
text.setColor(SK_ColorBLUE);
|
|
y = print_size(canvas, "Output (w/ hint)", hintedOutputBounds, kLineInset, y, font, text);
|
|
text.setColor(SK_ColorGREEN);
|
|
y = print_size(canvas, "Input (w/ no hint)", unhintedLayerBounds, kLineInset, y, font, text);
|
|
|
|
return y;
|
|
}
|
|
|
|
static void print_label(SkCanvas* canvas, float x, float y, float value) {
|
|
SkFont font(nullptr, 12);
|
|
SkPaint text;
|
|
text.setAntiAlias(true);
|
|
|
|
SkString label;
|
|
label.printf("%.3f", value);
|
|
|
|
canvas->drawString(label, x, y + kLineHeight / 2.f, font, text);
|
|
}
|
|
|
|
static SkPaint line_paint(SkColor color, bool dashed = false) {
|
|
SkPaint paint;
|
|
paint.setColor(color);
|
|
paint.setStrokeWidth(0.f);
|
|
paint.setStyle(SkPaint::kStroke_Style);
|
|
paint.setAntiAlias(true);
|
|
if (dashed) {
|
|
SkScalar dash[2] = {10.f, 10.f};
|
|
paint.setPathEffect(SkDashPathEffect::Make(dash, 2, 0.f));
|
|
}
|
|
return paint;
|
|
}
|
|
|
|
static SkPath create_axis_path(const SkRect& rect, float axisSpace) {
|
|
SkPath localSpace;
|
|
for (float y = rect.fTop + axisSpace; y <= rect.fBottom; y += axisSpace) {
|
|
localSpace.moveTo(rect.fLeft, y);
|
|
localSpace.lineTo(rect.fRight, y);
|
|
}
|
|
for (float x = rect.fLeft + axisSpace; x <= rect.fRight; x += axisSpace) {
|
|
localSpace.moveTo(x, rect.fTop);
|
|
localSpace.lineTo(x, rect.fBottom);
|
|
}
|
|
return localSpace;
|
|
}
|
|
|
|
static const SkColor4f kScaleGradientColors[] =
|
|
{ { 0.05f, 0.0f, 6.f, 1.f }, // Severe downscaling, s < 1/8, log(s) < -3
|
|
{ 0.6f, 0.6f, 0.8f, 0.6f }, // Okay downscaling, s < 1/2, log(s) < -1
|
|
{ 1.f, 1.f, 1.f, 0.2f }, // No scaling, s = 1, log(s) = 0
|
|
{ 0.95f, 0.6f, 0.5f, 0.6f }, // Okay upscaling, s > 2, log(s) > 1
|
|
{ 0.8f, 0.1f, 0.f, 1.f } }; // Severe upscaling, s > 8, log(s) > 3
|
|
static const SkScalar kLogScaleFactors[] = { -3.f, -1.f, 0.f, 1.f, 3.f };
|
|
static const SkScalar kGradientStops[] = { 0.f, 0.33333f, 0.5f, 0.66667f, 1.f };
|
|
static const int kStopCount = (int) std::size(kScaleGradientColors);
|
|
|
|
static void draw_scale_key(SkCanvas* canvas, float y) {
|
|
SkRect key = SkRect::MakeXYWH(15.f, y + 30.f, 15.f, 100.f);
|
|
SkPoint pts[] = {{key.centerX(), key.fTop}, {key.centerX(), key.fBottom}};
|
|
sk_sp<SkShader> gradient = SkGradientShader::MakeLinear(
|
|
pts, kScaleGradientColors, nullptr, kGradientStops, kStopCount, SkTileMode::kClamp,
|
|
SkGradientShader::kInterpolateColorsInPremul_Flag, nullptr);
|
|
SkPaint keyPaint;
|
|
keyPaint.setShader(gradient);
|
|
canvas->drawRect(key, keyPaint);
|
|
for (int i = 0; i < kStopCount; ++i) {
|
|
print_label(canvas, key.fRight + 5.f, key.fTop + kGradientStops[i] * key.height(),
|
|
SkScalarPow(2.f, kLogScaleFactors[i]));
|
|
}
|
|
}
|
|
|
|
static void draw_scale_factors(SkCanvas* canvas, const skif::Mapping& mapping, const SkRect& rect) {
|
|
SkPoint testPoints[5];
|
|
testPoints[0] = {rect.centerX(), rect.centerY()};
|
|
rect.toQuad(testPoints + 1);
|
|
for (int i = 0; i < 5; ++i) {
|
|
float scale = SkMatrixPriv::DifferentialAreaScale(
|
|
mapping.layerToDevice(),
|
|
SkPoint(mapping.paramToLayer(skif::ParameterSpace<SkPoint>(testPoints[i]))));
|
|
SkColor4f color = {0.f, 0.f, 0.f, 1.f};
|
|
|
|
if (SkScalarIsFinite(scale)) {
|
|
float logScale = SkScalarLog2(scale);
|
|
for (int j = 0; j <= kStopCount; ++j) {
|
|
if (j == kStopCount) {
|
|
color = kScaleGradientColors[j - 1];
|
|
break;
|
|
} else if (kLogScaleFactors[j] >= logScale) {
|
|
if (j == 0) {
|
|
color = kScaleGradientColors[0];
|
|
} else {
|
|
SkScalar t = (logScale - kLogScaleFactors[j - 1]) /
|
|
(kLogScaleFactors[j] - kLogScaleFactors[j - 1]);
|
|
|
|
SkColor4f a = kScaleGradientColors[j - 1] * (1.f - t);
|
|
SkColor4f b = kScaleGradientColors[j] * t;
|
|
color = {a.fR + b.fR, a.fG + b.fG, a.fB + b.fB, a.fA + b.fA};
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
SkPaint p;
|
|
p.setAntiAlias(true);
|
|
p.setColor4f(color, nullptr);
|
|
canvas->drawRect(SkRect::MakeLTRB(testPoints[i].fX - 4.f, testPoints[i].fY - 4.f,
|
|
testPoints[i].fX + 4.f, testPoints[i].fY + 4.f), p);
|
|
}
|
|
}
|
|
|
|
class FilterBoundsSample : public Sample {
|
|
public:
|
|
FilterBoundsSample() {}
|
|
|
|
void onOnceBeforeDraw() override {
|
|
fBlur = SkImageFilters::Blur(8.f, 8.f, nullptr);
|
|
fImage = ToolUtils::create_checkerboard_image(
|
|
300, 300, SK_ColorMAGENTA, SK_ColorLTGRAY, 50);
|
|
}
|
|
|
|
void onDrawContent(SkCanvas* canvas) override {
|
|
// The local content, e.g. what would be submitted to drawRect or the bounds to saveLayer
|
|
const SkRect localContentRect = SkRect::MakeLTRB(100.f, 20.f, 180.f, 140.f);
|
|
SkMatrix ctm = canvas->getLocalToDeviceAs3x3();
|
|
|
|
// Base rendering of a filter
|
|
SkPaint blurPaint;
|
|
blurPaint.setImageFilter(fBlur);
|
|
canvas->saveLayer(&localContentRect, &blurPaint);
|
|
canvas->drawImageRect(fImage.get(), localContentRect, localContentRect,
|
|
SkSamplingOptions(SkFilterMode::kLinear),
|
|
nullptr, SkCanvas::kFast_SrcRectConstraint);
|
|
canvas->restore();
|
|
|
|
// Now visualize the underlying bounds calculations used to determine the layer for the blur
|
|
SkIRect target = ctm.mapRect(localContentRect).roundOut();
|
|
if (!target.intersect(SkIRect::MakeWH(canvas->imageInfo().width(),
|
|
canvas->imageInfo().height()))) {
|
|
return;
|
|
}
|
|
skif::DeviceSpace<SkIRect> targetOutput(target);
|
|
skif::ParameterSpace<SkRect> contentBounds(localContentRect);
|
|
skif::ParameterSpace<SkPoint> contentCenter({localContentRect.centerX(),
|
|
localContentRect.centerY()});
|
|
skif::Mapping mapping;
|
|
SkAssertResult(mapping.decomposeCTM(ctm, fBlur.get(), contentCenter));
|
|
|
|
// Add axis lines, to show perspective distortion
|
|
canvas->save();
|
|
canvas->setMatrix(mapping.layerToDevice());
|
|
canvas->drawPath(create_axis_path(SkRect(mapping.paramToLayer(contentBounds)), 20.f),
|
|
line_paint(SK_ColorGRAY));
|
|
canvas->restore();
|
|
|
|
// Visualize scale factors at the four corners and center of the local rect
|
|
draw_scale_factors(canvas, mapping, localContentRect);
|
|
|
|
// The device content rect, e.g. the clip bounds if 'localContentRect' were used as a clip
|
|
// before the draw or saveLayer, representing what the filter must cover if it affects
|
|
// transparent black or doesn't have a local content hint.
|
|
canvas->setMatrix(SkMatrix::I());
|
|
canvas->drawRect(ctm.mapRect(localContentRect), line_paint(SK_ColorDKGRAY));
|
|
|
|
// Layer bounds for the filter, in the layer space compatible with the filter's matrix
|
|
// type requirements.
|
|
skif::LayerSpace<SkIRect> targetOutputInLayer = mapping.deviceToLayer(targetOutput);
|
|
skif::LayerSpace<SkIRect> hintedLayerBounds = as_IFB(fBlur)->getInputBounds(
|
|
mapping, targetOutput, &contentBounds);
|
|
skif::LayerSpace<SkIRect> unhintedLayerBounds = as_IFB(fBlur)->getInputBounds(
|
|
mapping, targetOutput, nullptr);
|
|
|
|
canvas->setMatrix(mapping.layerToDevice());
|
|
canvas->drawRect(SkRect::Make(SkIRect(targetOutputInLayer)),
|
|
line_paint(SK_ColorDKGRAY, true));
|
|
canvas->drawRect(SkRect::Make(SkIRect(hintedLayerBounds)), line_paint(SK_ColorRED));
|
|
canvas->drawRect(SkRect::Make(SkIRect(unhintedLayerBounds)), line_paint(SK_ColorGREEN));
|
|
|
|
// For visualization purposes, we want to show the layer-space output, this is what we get
|
|
// when contentBounds is provided as a hint in local/parameter space.
|
|
skif::Mapping layerOnly{mapping.layerMatrix()};
|
|
skif::DeviceSpace<SkIRect> hintedOutputBounds = as_IFB(fBlur)->getOutputBounds(
|
|
layerOnly, contentBounds);
|
|
canvas->drawRect(SkRect::Make(SkIRect(hintedOutputBounds)), line_paint(SK_ColorBLUE));
|
|
|
|
canvas->resetMatrix();
|
|
float y = print_info(canvas, SkIRect(mapping.paramToLayer(contentBounds).roundOut()),
|
|
SkIRect(targetOutput),
|
|
SkIRect(hintedOutputBounds),
|
|
SkIRect(unhintedLayerBounds));
|
|
|
|
// Draw color key for layer visualization
|
|
draw_scale_key(canvas, y);
|
|
}
|
|
|
|
SkString name() override { return SkString("FilterBounds"); }
|
|
|
|
private:
|
|
sk_sp<SkImageFilter> fBlur;
|
|
sk_sp<SkImage> fImage;
|
|
|
|
using INHERITED = Sample;
|
|
};
|
|
|
|
DEF_SAMPLE(return new FilterBoundsSample();)
|