diff options
author | senorblanco <senorblanco@chromium.org> | 2016-05-19 14:50:29 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2016-05-19 14:50:29 -0700 |
commit | 5878dbdf1b5d86201d299c6e07d53e35048713c7 (patch) | |
tree | 1037af4fb13bda913320414ebd901b8c168a835d | |
parent | 57a69dc013b7105a6a535e84588f9aa95397b2ee (diff) |
Image filters: implement SkImage::makeWithFilter().
This API provides a way to directly filter a subregion of an SkImage
(usually texture-backed), and returns an SkImage which may include
extra padding, along with a size to indicate the active region.
GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1964043002
Review-Url: https://codereview.chromium.org/1964043002
-rw-r--r-- | gm/imagemakewithfilter.cpp | 103 | ||||
-rw-r--r-- | include/core/SkImage.h | 21 | ||||
-rw-r--r-- | include/core/SkImageFilter.h | 7 | ||||
-rw-r--r-- | src/image/SkImage.cpp | 41 | ||||
-rw-r--r-- | tests/ImageFilterTest.cpp | 70 |
5 files changed, 239 insertions, 3 deletions
diff --git a/gm/imagemakewithfilter.cpp b/gm/imagemakewithfilter.cpp new file mode 100644 index 0000000000..15439f0c15 --- /dev/null +++ b/gm/imagemakewithfilter.cpp @@ -0,0 +1,103 @@ +/* + * 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.h" +#include "SkBlurImageFilter.h" +#include "SkCanvas.h" +#include "SkColorFilter.h" +#include "SkColorFilterImageFilter.h" +#include "SkDropShadowImageFilter.h" +#include "SkSurface.h" + +/////////////////////////////////////////////////////////////////////////////// + +static void show_bounds(SkCanvas* canvas, const SkIRect& subset, const SkIRect& clip) { + SkIRect rects[] { subset, clip }; + SkColor colors[] { SK_ColorRED, SK_ColorBLUE }; + + SkPaint paint; + paint.setStyle(SkPaint::kStroke_Style); + + for (size_t i = 0; i < SK_ARRAY_COUNT(rects); ++i) { + paint.setColor(colors[i]); + canvas->drawRect(SkRect::Make(rects[i]), paint); + } +} + +// In this GM, we're going to feed the inner portion of a 100x100 checkboard +// (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. +class ImageMakeWithFilterGM : public skiagm::GM { +public: + ImageMakeWithFilterGM () {} + +protected: + SkString onShortName() override { + return SkString("imagemakewithfilter"); + } + + SkISize onISize() override { return SkISize::Make(320, 100); } + + void onDraw(SkCanvas* canvas) override { + auto cf = SkColorFilter::MakeModeFilter(SK_ColorGREEN, SkXfermode::kSrc_Mode); + sk_sp<SkImageFilter> filters[] = { + SkColorFilterImageFilter::Make(std::move(cf), nullptr), + SkBlurImageFilter::Make(2.0f, 2.0f, nullptr), + SkDropShadowImageFilter::Make( + 10.0f, 5.0f, 3.0f, 3.0f, SK_ColorBLUE, + SkDropShadowImageFilter::kDrawShadowAndForeground_ShadowMode, + nullptr), + }; + + SkIRect clipBounds[] { + { -20, -20, 100, 100 }, + { 0, 0, 75, 75 }, + { 20, 20, 100, 100 }, + { -20, -20, 50, 50 }, + { 20, 20, 50, 50 }, + }; + + SkImageInfo info = SkImageInfo::MakeN32(100, 100, kPremul_SkAlphaType); + SkScalar MARGIN = SkIntToScalar(40); + SkScalar DX = info.width() + MARGIN; + SkScalar DY = info.height() + MARGIN; + + canvas->translate(MARGIN, MARGIN); + + sk_sp<SkSurface> surface = canvas->makeSurface(info); + if (!surface) { + surface = SkSurface::MakeRaster(info); + } + sk_tool_utils::draw_checkerboard(surface->getCanvas()); + sk_sp<SkImage> source = surface->makeImageSnapshot(); + + 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; + SkIPoint offset; + sk_sp<SkImage> result = source->makeWithFilter(filters[i].get(), subset, clipBound, + &outSubset, &offset); + SkASSERT(result); + SkASSERT(source->isTextureBacked() == result->isTextureBacked()); + result = result->makeSubset(outSubset); + canvas->drawImage(result.get(), SkIntToScalar(offset.fX), SkIntToScalar(offset.fY)); + show_bounds(canvas, SkIRect::MakeXYWH(offset.x(), offset.y(), outSubset.width(), + outSubset.height()), clipBound); + canvas->translate(DX, 0); + } + canvas->restore(); + canvas->translate(0, DY); + } + } + +private: + typedef GM INHERITED; +}; +DEF_GM( return new ImageMakeWithFilterGM; ) diff --git a/include/core/SkImage.h b/include/core/SkImage.h index 31985a6c69..c9579b610c 100644 --- a/include/core/SkImage.h +++ b/include/core/SkImage.h @@ -320,6 +320,27 @@ public: */ sk_sp<SkImage> makeTextureImage(GrContext*) const; + /** + * Apply a given image filter to this image, and return the filtered result. + * + * The subset represents the active portion of this image. The return value is similarly an + * SkImage, with an active subset (outSubset). This is usually used with texture-backed + * images, where the texture may be approx-match and thus larger than the required size. + * + * clipBounds constrains the device-space extent of the image which may be produced to the + * given rect. + * + * offset is the amount to translate the resulting image relative to the src when it is drawn. + * This is an out-param. + * + * If the result image cannot be created, or the result would be transparent black, null + * is returned, in which case the offset and outSubset parameters should be ignored by the + * caller. + */ + sk_sp<SkImage> makeWithFilter(const SkImageFilter* filter, const SkIRect& subset, + const SkIRect& clipBounds, SkIRect* outSubset, + SkIPoint* offset) const; + /** Drawing params for which a deferred texture image data should be optimized. */ struct DeferredTextureImageUsageParams { SkMatrix fMatrix; diff --git a/include/core/SkImageFilter.h b/include/core/SkImageFilter.h index aa9648eb3c..8a1bc4071a 100644 --- a/include/core/SkImageFilter.h +++ b/include/core/SkImageFilter.h @@ -93,7 +93,7 @@ public: }; /** - * Request a new (result) image to be created from the src image. + * Request a new filtered image to be created from the src image. * * The context contains the environment in which the filter is occurring. * It includes the clip bounds, CTM and cache. @@ -101,8 +101,9 @@ public: * Offset is the amount to translate the resulting image relative to the * src when it is drawn. This is an out-param. * - * If the result image cannot be created, return null, in which case - * the offset parameters will be ignored by the caller. + * If the result image cannot be created, or the result would be + * transparent black, return null, in which case the offset parameter + * should be ignored by the caller. * * TODO: Right now the imagefilters sometimes return empty result bitmaps/ * specialimages. That doesn't seem quite right. diff --git a/src/image/SkImage.cpp b/src/image/SkImage.cpp index 1970c57839..8bf1f247b0 100644 --- a/src/image/SkImage.cpp +++ b/src/image/SkImage.cpp @@ -10,6 +10,7 @@ #include "SkCanvas.h" #include "SkData.h" #include "SkImageEncoder.h" +#include "SkImageFilter.h" #include "SkImageGenerator.h" #include "SkImagePriv.h" #include "SkImageShader.h" @@ -19,6 +20,7 @@ #include "SkPixelRef.h" #include "SkPixelSerializer.h" #include "SkReadPixelsRec.h" +#include "SkSpecialImage.h" #include "SkString.h" #include "SkSurface.h" @@ -342,6 +344,45 @@ sk_sp<SkImage> SkImage::MakeFromPicture(sk_sp<SkPicture> picture, const SkISize& matrix, paint)); } +sk_sp<SkImage> SkImage::makeWithFilter(const SkImageFilter* filter, const SkIRect& subset, + const SkIRect& clipBounds, SkIRect* outSubset, + SkIPoint* offset) const { + if (!filter || !outSubset || !offset || !this->bounds().contains(subset)) { + return nullptr; + } + sk_sp<SkSpecialImage> srcSpecialImage = SkSpecialImage::MakeFromImage( + subset, sk_ref_sp(const_cast<SkImage*>(this))); + if (!srcSpecialImage) { + return nullptr; + } + + // FIXME: build a cache here. + SkImageFilter::Context context(SkMatrix::I(), clipBounds, nullptr); + sk_sp<SkSpecialImage> result = + filter->filterImage(srcSpecialImage.get(), context, offset); + + if (!result) { + return nullptr; + } + + SkIRect fullSize = SkIRect::MakeWH(result->width(), result->height()); +#if SK_SUPPORT_GPU + if (result->isTextureBacked()) { + GrContext* context = result->getContext(); + sk_sp<GrTexture> texture = result->asTextureRef(context); + fullSize = SkIRect::MakeWH(texture->width(), texture->height()); + } +#endif + *outSubset = SkIRect::MakeWH(result->width(), result->height()); + if (!outSubset->intersect(clipBounds.makeOffset(-offset->x(), -offset->y()))) { + return nullptr; + } + offset->fX += outSubset->x(); + offset->fY += outSubset->y(); + // This isn't really a "tight" subset, but includes any texture padding. + return result->makeTightSubset(fullSize); +} + bool SkImage::isLazyGenerated() const { return as_IB(this)->onIsLazyGenerated(); } diff --git a/tests/ImageFilterTest.cpp b/tests/ImageFilterTest.cpp index ed98ef4ab9..12b1caa16d 100644 --- a/tests/ImageFilterTest.cpp +++ b/tests/ImageFilterTest.cpp @@ -377,6 +377,18 @@ static sk_sp<SkSpecialSurface> create_empty_special_surface(GrContext* context, } } +static sk_sp<SkSurface> create_surface(GrContext* context, int width, int height) { + const SkImageInfo info = SkImageInfo::MakeN32(width, height, kOpaque_SkAlphaType); +#if SK_SUPPORT_GPU + if (context) { + return SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info); + } else +#endif + { + return SkSurface::MakeRaster(info); + } +} + static sk_sp<SkSpecialImage> create_empty_special_image(GrContext* context, int widthHeight) { sk_sp<SkSpecialSurface> surf(create_empty_special_surface(context, widthHeight)); @@ -1669,6 +1681,64 @@ DEF_TEST(ImageFilterBlurLargeImage, reporter) { test_large_blur_input(reporter, surface->getCanvas()); } +static void test_make_with_filter(skiatest::Reporter* reporter, GrContext* context) { + sk_sp<SkSurface> surface(create_surface(context, 100, 100)); + surface->getCanvas()->clear(SK_ColorRED); + SkPaint bluePaint; + bluePaint.setColor(SK_ColorBLUE); + SkIRect subset = SkIRect::MakeXYWH(25, 20, 50, 50); + surface->getCanvas()->drawRect(SkRect::Make(subset), bluePaint); + sk_sp<SkImage> sourceImage = surface->makeImageSnapshot(); + + sk_sp<SkImageFilter> filter = make_grayscale(nullptr, nullptr); + SkIRect clipBounds = SkIRect::MakeXYWH(30, 35, 100, 100); + SkIRect outSubset; + SkIPoint offset; + sk_sp<SkImage> result; + + result = sourceImage->makeWithFilter(nullptr, subset, clipBounds, &outSubset, &offset); + REPORTER_ASSERT(reporter, !result); + + result = sourceImage->makeWithFilter(filter.get(), subset, clipBounds, nullptr, &offset); + REPORTER_ASSERT(reporter, !result); + + result = sourceImage->makeWithFilter(filter.get(), subset, clipBounds, &outSubset, nullptr); + REPORTER_ASSERT(reporter, !result); + + SkIRect bigSubset = SkIRect::MakeXYWH(-10000, -10000, 20000, 20000); + result = sourceImage->makeWithFilter(filter.get(), bigSubset, clipBounds, &outSubset, &offset); + REPORTER_ASSERT(reporter, !result); + + SkIRect empty = SkIRect::MakeEmpty(); + result = sourceImage->makeWithFilter(filter.get(), empty, clipBounds, &outSubset, &offset); + REPORTER_ASSERT(reporter, !result); + + result = sourceImage->makeWithFilter(filter.get(), subset, empty, &outSubset, &offset); + REPORTER_ASSERT(reporter, !result); + + SkIRect leftField = SkIRect::MakeXYWH(-1000, 0, 100, 100); + result = sourceImage->makeWithFilter(filter.get(), subset, leftField, &outSubset, &offset); + REPORTER_ASSERT(reporter, !result); + + result = sourceImage->makeWithFilter(filter.get(), subset, clipBounds, &outSubset, &offset); + + REPORTER_ASSERT(reporter, result); + REPORTER_ASSERT(reporter, result->bounds().contains(outSubset)); + SkIRect destRect = SkIRect::MakeXYWH(offset.x(), offset.y(), + outSubset.width(), outSubset.height()); + REPORTER_ASSERT(reporter, clipBounds.contains(destRect)); +} + +DEF_TEST(ImageFilterMakeWithFilter, reporter) { + test_make_with_filter(reporter, nullptr); +} + +#if SK_SUPPORT_GPU +DEF_GPUTEST_FOR_RENDERING_CONTEXTS(ImageFilterMakeWithFilter_Gpu, reporter, ctxInfo) { + test_make_with_filter(reporter, ctxInfo.grContext()); +} +#endif + #if SK_SUPPORT_GPU DEF_GPUTEST_FOR_RENDERING_CONTEXTS(ImageFilterHugeBlur_Gpu, reporter, ctxInfo) { |