66470f8b7d
SkCanvas and SkDevice were using SkM44 and its definition of invert(), but it was slightly more generous than SkMatrix::invert() so the fuzzer caught a case where the layer's SkDevice had a valid transform but then converting it to a SkMatrix in skif::Mapping was no longer invertible. This modifies it so that skif::Mapping no longer tries to invert the matrices. In almost all cases, the inverse of the layer-to-device matrix can be constructed directly from a matrix multiply (that's what device->getRelativeTransform() does). When the matrices are ill-conditioned the constructed inverse may be inaccurate (hence why SkMatrix::invert reports false), but in practice this happens for ridiculously large transforms and the error isn't significant compared to the precision range of the matrices anyways. Other cases explicitly want to use the identity matrix for the layer to device matrix, so I added a helper in the few places that would have had to pass SkMatrix::I() twice instead. The last case is drawImage() that creates its own skif::Mapping, now it just calculates the inverse that skif::Mapping() would have done and if it fails it drops the draw since it means the canvas matrix is bad. Bug: chromium:1276525 Change-Id: Ib516bb2fac19d5e7397bd27d80f8e3932b25b2e2 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/509396 Reviewed-by: Robert Phillips <robertphillips@google.com> Commit-Queue: Michael Ludwig <michaelludwig@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) 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 = 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.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{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();)
|