skia2/gm/imagemakewithfilter.cpp
Greg Daniel 85da3368c5 Add d3d support for creating command lists.
Currently we only can create direct command lists and we also don't cache anything.

Change also has so minor fixes to GMs allowing the d3d backend to run through all
GMs without crashing (though not actually drawing anything).

Bug: skia:9935
Change-Id: Ibf378e522d2e49bf342c709eb93d6fca4d43eac9
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/276097
Reviewed-by: Jim Van Verth <jvanverth@google.com>
Commit-Queue: Greg Daniel <egdaniel@google.com>
2020-03-09 19:42:48 +00:00

391 lines
16 KiB
C++

/*
* Copyright 2016 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "gm/gm.h"
#include "include/core/SkBlendMode.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkColorFilter.h"
#include "include/core/SkImage.h"
#include "include/core/SkImageFilter.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPoint.h"
#include "include/core/SkPoint3.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkRegion.h"
#include "include/core/SkScalar.h"
#include "include/core/SkSize.h"
#include "include/core/SkString.h"
#include "include/core/SkSurface.h"
#include "include/core/SkTypes.h"
#include "include/effects/SkImageFilters.h"
#include "include/gpu/GrContext.h"
#include "tools/Resources.h"
#include "tools/ToolUtils.h"
#include <utility>
///////////////////////////////////////////////////////////////////////////////
static void show_bounds(SkCanvas* canvas, const SkIRect* clip, const SkIRect* inSubset,
const SkIRect* outSubset) {
const SkIRect* rects[] { clip, inSubset, outSubset };
SkColor colors[] { SK_ColorBLUE, SK_ColorYELLOW, SK_ColorRED };
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
for (size_t i = 0; i < SK_ARRAY_COUNT(rects); ++i) {
// Skip null bounds rects, since not all methods have subsets
if (rects[i]) {
paint.setColor(colors[i]);
canvas->drawRect(SkRect::Make(*(rects[i])), paint);
}
}
}
// Factories for creating image filters, either with or without a cropRect
// (this could go away if there was a SkImageFilter::makeWithCropRect() function, but that seems
// less generally useful).
typedef sk_sp<SkImageFilter> (*FilterFactory)(sk_sp<SkImage> auxImage, const SkIRect* cropRect);
static sk_sp<SkImageFilter> color_filter_factory(sk_sp<SkImage> auxImage, const SkIRect* cropRect) {
// The color filter uses kSrcIn so that it respects the transparency introduced by clamping;
// using kSrc would just turn the entire out rect to green regardless.
auto cf = SkColorFilters::Blend(SK_ColorGREEN, SkBlendMode::kSrcIn);
return SkImageFilters::ColorFilter(std::move(cf), nullptr, cropRect);
}
static sk_sp<SkImageFilter> blur_filter_factory(sk_sp<SkImage> auxImage, const SkIRect* cropRect) {
return SkImageFilters::Blur(2.0f, 2.0f, nullptr, cropRect);
}
static sk_sp<SkImageFilter> drop_shadow_factory(sk_sp<SkImage> auxImage, const SkIRect* cropRect) {
return SkImageFilters::DropShadow(10.0f, 5.0f, 3.0f, 3.0f, SK_ColorBLUE, nullptr, cropRect);
}
static sk_sp<SkImageFilter> offset_factory(sk_sp<SkImage> auxImage, const SkIRect* cropRect) {
return SkImageFilters::Offset(10.f, 5.f, nullptr, cropRect);
}
static sk_sp<SkImageFilter> dilate_factory(sk_sp<SkImage> auxImage, const SkIRect* cropRect) {
return SkImageFilters::Dilate(10.f, 5.f, nullptr, cropRect);
}
static sk_sp<SkImageFilter> erode_factory(sk_sp<SkImage> auxImage, const SkIRect* cropRect) {
return SkImageFilters::Erode(10.f, 5.f, nullptr, cropRect);
}
static sk_sp<SkImageFilter> displacement_factory(sk_sp<SkImage> auxImage, const SkIRect* cropRect) {
sk_sp<SkImageFilter> displacement = SkImageFilters::Image(std::move(auxImage));
return SkImageFilters::DisplacementMap(SkColorChannel::kR, SkColorChannel::kG, 40.f,
std::move(displacement), nullptr, cropRect);
}
static sk_sp<SkImageFilter> arithmetic_factory(sk_sp<SkImage> auxImage, const SkIRect* cropRect) {
sk_sp<SkImageFilter> background = SkImageFilters::Image(std::move(auxImage));
return SkImageFilters::Arithmetic(0.0f, .6f, 1.f, 0.f, false, std::move(background),
nullptr, cropRect);
}
static sk_sp<SkImageFilter> xfermode_factory(sk_sp<SkImage> auxImage, const SkIRect* cropRect) {
sk_sp<SkImageFilter> background = SkImageFilters::Image(std::move(auxImage));
return SkImageFilters::Xfermode(
SkBlendMode::kModulate, std::move(background), nullptr, cropRect);
}
static sk_sp<SkImageFilter> convolution_factory(sk_sp<SkImage> auxImage, const SkIRect* cropRect) {
SkISize kernelSize = SkISize::Make(3, 3);
SkIPoint kernelOffset = SkIPoint::Make(1, 1);
// A Laplacian edge detector, ee https://en.wikipedia.org/wiki/Kernel_(image_processing)
SkScalar kernel[9] = {-1.f, -1.f, -1.f,
-1.f, 8.f, -1.f,
-1.f, -1.f, -1.f};
return SkImageFilters::MatrixConvolution(kernelSize, kernel, 1.f, 0.f, kernelOffset,
SkTileMode::kClamp, false, nullptr, cropRect);
}
static sk_sp<SkImageFilter> matrix_factory(sk_sp<SkImage> auxImage, const SkIRect* cropRect) {
SkMatrix matrix = SkMatrix::I();
matrix.setRotate(45.f, 50.f, 50.f);
// This doesn't support a cropRect
return SkImageFilters::MatrixTransform(matrix, kLow_SkFilterQuality, nullptr);
}
static sk_sp<SkImageFilter> alpha_threshold_factory(sk_sp<SkImage> auxImage,
const SkIRect* cropRect) {
// Centered cross with higher opacity
SkRegion region(SkIRect::MakeLTRB(30, 45, 70, 55));
region.op(SkIRect::MakeLTRB(45, 30, 55, 70), SkRegion::kUnion_Op);
return SkImageFilters::AlphaThreshold(region, 1.f, .2f, nullptr, cropRect);
}
static sk_sp<SkImageFilter> lighting_factory(sk_sp<SkImage> auxImage, const SkIRect* cropRect) {
// Must convert the RGB values of the source to alpha, since that is what the lighting filters
// use to estimate their normals. This color matrix changes the color to white and the alpha
// to be equal to the approx. luminance of the original color.
static const float kMatrix[20] = {
0.f, 0.f, 0.f, 0.f, 1.f,
0.f, 0.f, 0.f, 0.f, 1.f,
0.f, 0.f, 0.f, 0.f, 1.f,
0.2126f, 0.7152f, 0.0722f, 0.f, 0.f
};
sk_sp<SkImageFilter> srcToAlpha = SkImageFilters::ColorFilter(
SkColorFilters::Matrix(kMatrix), nullptr);
// Combine both specular and diffuse into a single DAG since they use separate internal filter
// implementations.
SkScalar sinAzimuth = SkScalarSin(SkDegreesToRadians(225.f)),
cosAzimuth = SkScalarCos(SkDegreesToRadians(225.f));
SkPoint3 spotTarget = SkPoint3::Make(SkIntToScalar(40), SkIntToScalar(40), 0);
SkPoint3 diffLocation = SkPoint3::Make(spotTarget.fX + 50 * cosAzimuth,
spotTarget.fY + 50 * sinAzimuth,
SkIntToScalar(10));
SkPoint3 specLocation = SkPoint3::Make(spotTarget.fX - 50 * sinAzimuth,
spotTarget.fY + 50 * cosAzimuth,
SkIntToScalar(10));
sk_sp<SkImageFilter> diffuse = SkImageFilters::PointLitDiffuse(
diffLocation, SK_ColorWHITE, /* scale */ 1.f, /* kd */ 2.f, srcToAlpha, cropRect);
sk_sp<SkImageFilter> specular = SkImageFilters::PointLitSpecular(
specLocation, SK_ColorRED, /* scale */ 1.f, /* ks */ 1.f, /* shine */ 8.f,
srcToAlpha, cropRect);
return SkImageFilters::Merge(std::move(diffuse), std::move(specular), cropRect);
}
static sk_sp<SkImageFilter> tile_factory(sk_sp<SkImage> auxImage, const SkIRect* cropRect) {
// Tile the subset over a large region
return SkImageFilters::Tile(SkRect::MakeLTRB(25, 25, 75, 75), SkRect::MakeWH(100, 100),
nullptr);
}
namespace {
enum class Strategy {
// Uses makeWithFilter, passing in subset and clip directly
kMakeWithFilter,
// Uses saveLayer after clipRect() to filter on the restore (i.e. reference image)
kSaveLayer
};
};
// In this GM, we're going to feed the inner portion of a 100x100 mandrill (i.e., strip off a
// 25-wide border) through the makeWithFilter method. We'll then draw the appropriate subset of the
// result to the screen at the given offset. Some filters rely on a secondary image, which will be a
// 100x100 checkerboard. The original image is drawn in the background so that alignment is clear
// when drawing the result at its reported offset.
class ImageMakeWithFilterGM : public skiagm::GM {
public:
ImageMakeWithFilterGM (Strategy strategy, bool filterWithCropRect = false)
: fStrategy(strategy)
, fFilterWithCropRect(filterWithCropRect)
, fMainImage(nullptr)
, fAuxImage(nullptr) {}
protected:
SkString onShortName() override {
SkString name = SkString("imagemakewithfilter");
if (fFilterWithCropRect) {
name.append("_crop");
}
if (fStrategy == Strategy::kSaveLayer) {
name.append("_ref");
}
return name;
}
SkISize onISize() override { return SkISize::Make(1980, 860); }
void onOnceBeforeDraw() override {
SkImageInfo info = SkImageInfo::MakeN32(100, 100, kUnpremul_SkAlphaType);
auto surface = SkSurface::MakeRaster(info, nullptr);
sk_sp<SkImage> colorImage = GetResourceAsImage("images/mandrill_128.png");
// Resize to 100x100
surface->getCanvas()->drawImageRect(
colorImage, SkRect::MakeWH(colorImage->width(), colorImage->height()),
SkRect::MakeWH(info.width(), info.height()), nullptr);
fMainImage = surface->makeImageSnapshot();
ToolUtils::draw_checkerboard(surface->getCanvas());
fAuxImage = surface->makeImageSnapshot();
}
void onDraw(SkCanvas* canvas) override {
FilterFactory filters[] = {
color_filter_factory,
blur_filter_factory,
drop_shadow_factory,
offset_factory,
dilate_factory,
erode_factory,
displacement_factory,
arithmetic_factory,
xfermode_factory,
convolution_factory,
matrix_factory,
alpha_threshold_factory,
lighting_factory,
tile_factory
};
const char* filterNames[] = {
"Color",
"Blur",
"Drop Shadow",
"Offset",
"Dilate",
"Erode",
"Displacement",
"Arithmetic",
"Xfer Mode",
"Convolution",
"Matrix Xform",
"Alpha Threshold",
"Lighting",
"Tile"
};
static_assert(SK_ARRAY_COUNT(filters) == SK_ARRAY_COUNT(filterNames), "filter name length");
SkIRect clipBounds[] {
{ -20, -20, 100, 100 },
{ 0, 0, 75, 75 },
{ 20, 20, 100, 100 },
{ -20, -20, 50, 50 },
{ 20, 20, 50, 50 },
{ 30, 30, 75, 75 }
};
// These need to be GPU-backed when on the GPU to ensure that the image filters use the GPU
// code paths (otherwise they may choose to do CPU filtering then upload)
sk_sp<SkImage> mainImage, auxImage;
if (canvas->getGrContext()) {
if (canvas->getGrContext()->abandoned()) {
return;
}
mainImage = fMainImage->makeTextureImage(canvas->getGrContext());
auxImage = fAuxImage->makeTextureImage(canvas->getGrContext());
} else {
mainImage = fMainImage;
auxImage = fAuxImage;
}
if (!mainImage || !auxImage) {
return;
}
SkASSERT(mainImage && (mainImage->isTextureBacked() || !canvas->getGrContext()));
SkASSERT(auxImage && (auxImage->isTextureBacked() || !canvas->getGrContext()));
SkScalar MARGIN = SkIntToScalar(40);
SkScalar DX = mainImage->width() + MARGIN;
SkScalar DY = auxImage->height() + MARGIN;
// Header hinting at what the filters do
SkPaint textPaint;
textPaint.setAntiAlias(true);
SkFont font(nullptr, 12);
for (size_t i = 0; i < SK_ARRAY_COUNT(filterNames); ++i) {
canvas->drawString(filterNames[i], DX * i + MARGIN, 15, font, textPaint);
}
canvas->translate(MARGIN, MARGIN);
for (auto clipBound : clipBounds) {
canvas->save();
for (size_t i = 0; i < SK_ARRAY_COUNT(filters); ++i) {
SkIRect subset = SkIRect::MakeXYWH(25, 25, 50, 50);
SkIRect outSubset;
// Draw the original image faintly so that it aids in checking alignment of the
// filtered result.
SkPaint alpha;
alpha.setAlphaf(0.3f);
canvas->drawImage(mainImage, 0, 0, &alpha);
this->drawImageWithFilter(canvas, mainImage, auxImage, filters[i], clipBound,
subset, &outSubset);
// Draw outlines to highlight what was subset, what was cropped, and what was output
// (no output subset is displayed for kSaveLayer since that information isn't avail)
SkIRect* outSubsetBounds = nullptr;
if (fStrategy != Strategy::kSaveLayer) {
outSubsetBounds = &outSubset;
}
show_bounds(canvas, &clipBound, &subset, outSubsetBounds);
canvas->translate(DX, 0);
}
canvas->restore();
canvas->translate(0, DY);
}
}
private:
Strategy fStrategy;
bool fFilterWithCropRect;
sk_sp<SkImage> fMainImage;
sk_sp<SkImage> fAuxImage;
void drawImageWithFilter(SkCanvas* canvas, sk_sp<SkImage> mainImage, sk_sp<SkImage> auxImage,
FilterFactory filterFactory, const SkIRect& clip,
const SkIRect& subset, SkIRect* dstRect) {
// When creating the filter with a crop rect equal to the clip, we should expect to see no
// difference from a filter without a crop rect. However, if the CTM isn't managed properly
// by makeWithFilter, then the final result will be the incorrect intersection of the clip
// and the transformed crop rect.
sk_sp<SkImageFilter> filter = filterFactory(auxImage,
fFilterWithCropRect ? &clip : nullptr);
if (fStrategy == Strategy::kSaveLayer) {
SkAutoCanvasRestore acr(canvas, true);
// Clip before the saveLayer with the filter
canvas->clipRect(SkRect::Make(clip));
// Put the image filter on the layer
SkPaint paint;
paint.setImageFilter(filter);
canvas->saveLayer(nullptr, &paint);
// Draw the original subset of the image
canvas->drawImageRect(mainImage, subset, SkRect::Make(subset), nullptr);
*dstRect = subset;
} else {
sk_sp<SkImage> result;
SkIRect outSubset;
SkIPoint offset;
result = mainImage->makeWithFilter(filter.get(), subset, clip, &outSubset, &offset);
SkASSERT(result);
SkASSERT(mainImage->isTextureBacked() == result->isTextureBacked());
*dstRect = SkIRect::MakeXYWH(offset.x(), offset.y(),
outSubset.width(), outSubset.height());
canvas->drawImageRect(result, outSubset, SkRect::Make(*dstRect), nullptr);
}
}
typedef GM INHERITED;
};
// The different strategies should all look the same, with the exception of filters that affect
// transparent black (i.e. the lighting filter). In the save layer case, the filter affects the
// transparent pixels outside of the drawn subset, whereas the makeWithFilter is restricted. This
// works as intended.
DEF_GM( return new ImageMakeWithFilterGM(Strategy::kMakeWithFilter); )
DEF_GM( return new ImageMakeWithFilterGM(Strategy::kSaveLayer); )
// Test with crop rects on the image filters; should look identical to above if working correctly
DEF_GM( return new ImageMakeWithFilterGM(Strategy::kMakeWithFilter, true); )
DEF_GM( return new ImageMakeWithFilterGM(Strategy::kSaveLayer, true); )