skia2/samplecode/SampleFilterBounds.cpp
Michael Ludwig d6bf999365 Factor perspective scale into filter matrix decomposition
I experimented with passing in a rectangle and computing a geometric
mean of the scale factors for the 4 corners and center. The downside
to that approach is that callers either know the parameter-space bounds
or the device-space bounds. In the latter case, we have to map by the
inverse CTM, which opens up a can of worms. In practice it seemed using
just the center point worked out just as well.

This also updates the sample to draw the axes in the layer space instead
of parameter space. I found this helps display the scale effects of the
parameter-to-layer matrix better.

Bug: skia:9074
Change-Id: I855c85cdbe1072c451aa3a0601571f2e137e5203
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/327624
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
2020-10-16 18:49:51 +00:00

258 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/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) SK_ARRAY_COUNT(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.deviceMatrix(),
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 = SkImage::MakeFromBitmap(ToolUtils::create_checkerboard_bitmap(
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->getTotalMatrix();
// Base rendering of a filter
SkPaint blurPaint;
blurPaint.setImageFilter(fBlur);
canvas->saveLayer(&localContentRect, &blurPaint);
SkPaint imagePaint;
imagePaint.setFilterQuality(kLow_SkFilterQuality);
canvas->drawImageRect(fImage, localContentRect, &imagePaint);
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::Mapping mapping = skif::Mapping::DecomposeCTM(
ctm, fBlur.get(), skif::ParameterSpace<SkPoint>({localContentRect.centerX(),
localContentRect.centerY()}));
// Add axis lines, to show perspective distortion
canvas->save();
canvas->setMatrix(mapping.deviceMatrix());
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.deviceMatrix());
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(SkMatrix::I(), 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();)