Implement srcRect and dstRect functionality in SkBitmapSource. This is required for the "preserveAspectRatio" options of SVG's feImage. Covered by new GM "bitmapsource".

This also includes some changes to the xfermodeimagefilter and tileimagefilter GMs to properly handle the CTM. This worked before only because SkBitmapSource was ignoring the CTM. Now that it respects it, we need to give the correct transform. This also means the GMs now work while zoomed. It also implements CTM support for SkTileImageFilter.

NOTE: this will require rebaselining a number of imagefilter GMs on Nexus4, since they render in perspective (using the CTM). The changes to the results should all be improvements.

R=reed@google.com, robertphillips@google.com

Review URL: https://codereview.chromium.org/106933002

git-svn-id: http://skia.googlecode.com/svn/trunk@12571 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
senorblanco@chromium.org 2013-12-09 18:31:42 +00:00
parent 907fbd53c5
commit e93e1dbf0e
7 changed files with 198 additions and 56 deletions

91
gm/bitmapsource.cpp Normal file
View File

@ -0,0 +1,91 @@
/*
* Copyright 2013 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 "SkBitmapSource.h"
// This GM exercises the SkBitmapSource ImageFilter class.
class BitmapSourceGM : public skiagm::GM {
public:
BitmapSourceGM() {
}
protected:
virtual SkString onShortName() SK_OVERRIDE {
return SkString("bitmapsource");
}
void makeBitmap() {
fBitmap.setConfig(SkBitmap::kARGB_8888_Config, 100, 100);
fBitmap.allocPixels();
SkBitmapDevice device(fBitmap);
SkCanvas canvas(&device);
canvas.clear(0x00000000);
SkPaint paint;
paint.setAntiAlias(true);
paint.setColor(0xFFFFFFFF);
paint.setTextSize(SkIntToScalar(96));
const char* str = "e";
canvas.drawText(str, strlen(str), SkIntToScalar(20), SkIntToScalar(70), paint);
}
virtual SkISize onISize() SK_OVERRIDE { return SkISize::Make(500, 150); }
virtual void onOnceBeforeDraw() SK_OVERRIDE {
this->makeBitmap();
}
static void fillRectFiltered(SkCanvas* canvas, const SkRect& clipRect, SkImageFilter* filter) {
SkPaint paint;
paint.setImageFilter(filter);
canvas->save();
canvas->clipRect(clipRect);
canvas->drawPaint(paint);
canvas->restore();
}
virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE {
canvas->clear(0x00000000);
{
SkRect srcRect = SkRect::MakeXYWH(20, 20, 30, 30);
SkRect dstRect = SkRect::MakeXYWH(0, 10, 60, 60);
SkRect clipRect = SkRect::MakeXYWH(0, 0, 100, 100);
SkRect bounds;
fBitmap.getBounds(&bounds);
SkAutoTUnref<SkImageFilter> bitmapSource(new SkBitmapSource(fBitmap));
SkAutoTUnref<SkImageFilter> bitmapSourceSrcRect(new SkBitmapSource(fBitmap, srcRect, srcRect));
SkAutoTUnref<SkImageFilter> bitmapSourceSrcRectDstRect(new SkBitmapSource(fBitmap, srcRect, dstRect));
SkAutoTUnref<SkImageFilter> bitmapSourceDstRectOnly(new SkBitmapSource(fBitmap, bounds, dstRect));
// Draw an unscaled bitmap.
fillRectFiltered(canvas, clipRect, bitmapSource);
canvas->translate(SkIntToScalar(100), 0);
// Draw an unscaled subset of the source bitmap (srcRect -> srcRect).
fillRectFiltered(canvas, clipRect, bitmapSourceSrcRect);
canvas->translate(SkIntToScalar(100), 0);
// Draw a subset of the bitmap scaled to a destination rect (srcRect -> dstRect).
fillRectFiltered(canvas, clipRect, bitmapSourceSrcRectDstRect);
canvas->translate(SkIntToScalar(100), 0);
// Draw the entire bitmap scaled to a destination rect (bounds -> dstRect).
fillRectFiltered(canvas, clipRect, bitmapSourceDstRectOnly);
canvas->translate(SkIntToScalar(100), 0);
}
}
private:
SkBitmap fBitmap;
typedef GM INHERITED;
};
///////////////////////////////////////////////////////////////////////////////
DEF_GM( return new BitmapSourceGM; )

View File

@ -70,9 +70,10 @@ protected:
void drawClippedBitmap(SkCanvas* canvas, const SkBitmap& bitmap, const SkPaint& paint, void drawClippedBitmap(SkCanvas* canvas, const SkBitmap& bitmap, const SkPaint& paint,
SkScalar x, SkScalar y) { SkScalar x, SkScalar y) {
canvas->save(); canvas->save();
canvas->clipRect(SkRect::MakeXYWH(x, y, canvas->translate(x, y);
canvas->clipRect(SkRect::MakeXYWH(0, 0,
SkIntToScalar(bitmap.width()), SkIntToScalar(bitmap.height()))); SkIntToScalar(bitmap.width()), SkIntToScalar(bitmap.height())));
canvas->drawBitmap(bitmap, x, y, &paint); canvas->drawBitmap(bitmap, 0, 0, &paint);
canvas->restore(); canvas->restore();
} }

View File

@ -19,12 +19,12 @@ namespace skiagm {
class XfermodeImageFilterGM : public GM { class XfermodeImageFilterGM : public GM {
public: public:
XfermodeImageFilterGM() : fInitialized(false) { XfermodeImageFilterGM(){
this->setBGColor(0xFF000000); this->setBGColor(0xFF000000);
} }
protected: protected:
virtual SkString onShortName() { virtual SkString onShortName() SK_OVERRIDE {
return SkString("xfermodeimagefilter"); return SkString("xfermodeimagefilter");
} }
@ -65,25 +65,35 @@ protected:
} }
} }
virtual SkISize onISize() { virtual SkISize onISize() SK_OVERRIDE {
return make_isize(WIDTH, HEIGHT); return make_isize(WIDTH, HEIGHT);
} }
void drawClippedBitmap(SkCanvas* canvas, const SkBitmap& bitmap, const SkPaint& paint, static void drawClippedBitmap(SkCanvas* canvas, const SkBitmap& bitmap, const SkPaint& paint,
SkScalar x, SkScalar y) { int x, int y) {
canvas->save(); canvas->save();
canvas->clipRect(SkRect::MakeXYWH(x, y, canvas->translate(SkIntToScalar(x), SkIntToScalar(y));
canvas->clipRect(SkRect::MakeWH(
SkIntToScalar(bitmap.width()), SkIntToScalar(bitmap.height()))); SkIntToScalar(bitmap.width()), SkIntToScalar(bitmap.height())));
canvas->drawBitmap(bitmap, x, y, &paint); canvas->drawBitmap(bitmap, 0, 0, &paint);
canvas->restore(); canvas->restore();
} }
virtual void onDraw(SkCanvas* canvas) { static void drawClippedPaint(SkCanvas* canvas, const SkRect& rect, const SkPaint& paint,
if (!fInitialized) { int x, int y) {
make_bitmap(); canvas->save();
make_checkerboard(); canvas->translate(SkIntToScalar(x), SkIntToScalar(y));
fInitialized = true; canvas->clipRect(rect);
} canvas->drawPaint(paint);
canvas->restore();
}
virtual void onOnceBeforeDraw() SK_OVERRIDE {
make_bitmap();
make_checkerboard();
}
virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE {
canvas->clear(0x00000000); canvas->clear(0x00000000);
SkPaint paint; SkPaint paint;
@ -130,7 +140,7 @@ protected:
SkAutoTUnref<SkImageFilter> filter(SkNEW_ARGS( SkAutoTUnref<SkImageFilter> filter(SkNEW_ARGS(
SkXfermodeImageFilter, (mode, background))); SkXfermodeImageFilter, (mode, background)));
paint.setImageFilter(filter); paint.setImageFilter(filter);
drawClippedBitmap(canvas, fBitmap, paint, SkIntToScalar(x), SkIntToScalar(y)); drawClippedBitmap(canvas, fBitmap, paint, x, y);
x += fBitmap.width() + MARGIN; x += fBitmap.width() + MARGIN;
if (x + fBitmap.width() > WIDTH) { if (x + fBitmap.width() > WIDTH) {
x = 0; x = 0;
@ -141,7 +151,7 @@ protected:
SkAutoTUnref<SkXfermode> mode(SkArithmeticMode::Create(0, SK_Scalar1, SK_Scalar1, 0)); SkAutoTUnref<SkXfermode> mode(SkArithmeticMode::Create(0, SK_Scalar1, SK_Scalar1, 0));
SkAutoTUnref<SkImageFilter> filter(SkNEW_ARGS(SkXfermodeImageFilter, (mode, background))); SkAutoTUnref<SkImageFilter> filter(SkNEW_ARGS(SkXfermodeImageFilter, (mode, background)));
paint.setImageFilter(filter); paint.setImageFilter(filter);
drawClippedBitmap(canvas, fBitmap, paint, SkIntToScalar(x), SkIntToScalar(y)); drawClippedBitmap(canvas, fBitmap, paint, x, y);
x += fBitmap.width() + MARGIN; x += fBitmap.width() + MARGIN;
if (x + fBitmap.width() > WIDTH) { if (x + fBitmap.width() > WIDTH) {
x = 0; x = 0;
@ -150,12 +160,14 @@ protected:
// Test NULL mode // Test NULL mode
filter.reset(SkNEW_ARGS(SkXfermodeImageFilter, (NULL, background))); filter.reset(SkNEW_ARGS(SkXfermodeImageFilter, (NULL, background)));
paint.setImageFilter(filter); paint.setImageFilter(filter);
drawClippedBitmap(canvas, fBitmap, paint, SkIntToScalar(x), SkIntToScalar(y)); drawClippedBitmap(canvas, fBitmap, paint, x, y);
x += fBitmap.width() + MARGIN; x += fBitmap.width() + MARGIN;
if (x + fBitmap.width() > WIDTH) { if (x + fBitmap.width() > WIDTH) {
x = 0; x = 0;
y += fBitmap.height() + MARGIN; y += fBitmap.height() + MARGIN;
} }
SkRect clipRect = SkRect::MakeWH(SkIntToScalar(fBitmap.width() + 4),
SkIntToScalar(fBitmap.height() + 4));
// Test offsets on SrcMode (uses fixed-function blend) // Test offsets on SrcMode (uses fixed-function blend)
SkAutoTUnref<SkImageFilter> foreground(SkNEW_ARGS(SkBitmapSource, (fBitmap))); SkAutoTUnref<SkImageFilter> foreground(SkNEW_ARGS(SkBitmapSource, (fBitmap)));
SkAutoTUnref<SkImageFilter> offsetForeground(SkNEW_ARGS(SkOffsetImageFilter, SkAutoTUnref<SkImageFilter> offsetForeground(SkNEW_ARGS(SkOffsetImageFilter,
@ -166,13 +178,7 @@ protected:
filter.reset(SkNEW_ARGS(SkXfermodeImageFilter, filter.reset(SkNEW_ARGS(SkXfermodeImageFilter,
(mode, offsetBackground, offsetForeground))); (mode, offsetBackground, offsetForeground)));
paint.setImageFilter(filter); paint.setImageFilter(filter);
canvas->save(); drawClippedPaint(canvas, clipRect, paint, x, y);
canvas->clipRect(SkRect::MakeXYWH(SkIntToScalar(x),
SkIntToScalar(y),
SkIntToScalar(fBitmap.width() + 4),
SkIntToScalar(fBitmap.height() + 4)));
canvas->drawPaint(paint);
canvas->restore();
x += fBitmap.width() + MARGIN; x += fBitmap.width() + MARGIN;
if (x + fBitmap.width() > WIDTH) { if (x + fBitmap.width() > WIDTH) {
x = 0; x = 0;
@ -182,13 +188,7 @@ protected:
mode.reset(SkXfermode::Create(SkXfermode::kDarken_Mode)); mode.reset(SkXfermode::Create(SkXfermode::kDarken_Mode));
filter.reset(SkNEW_ARGS(SkXfermodeImageFilter, (mode, offsetBackground, offsetForeground))); filter.reset(SkNEW_ARGS(SkXfermodeImageFilter, (mode, offsetBackground, offsetForeground)));
paint.setImageFilter(filter); paint.setImageFilter(filter);
canvas->save(); drawClippedPaint(canvas, clipRect, paint, x, y);
canvas->clipRect(SkRect::MakeXYWH(SkIntToScalar(x),
SkIntToScalar(y),
SkIntToScalar(fBitmap.width() + 4),
SkIntToScalar(fBitmap.height() + 4)));
canvas->drawPaint(paint);
canvas->restore();
x += fBitmap.width() + MARGIN; x += fBitmap.width() + MARGIN;
if (x + fBitmap.width() > WIDTH) { if (x + fBitmap.width() > WIDTH) {
x = 0; x = 0;
@ -203,8 +203,8 @@ protected:
{ 10, 10, 10, 10}, { 10, 10, 10, 10},
{-10, -10, -6, -6}}; {-10, -10, -6, -6}};
for (size_t i = 0; i < nbSamples; ++i) { for (size_t i = 0; i < nbSamples; ++i) {
SkIRect cropRect = SkIRect::MakeXYWH(x + offsets[i][0], SkIRect cropRect = SkIRect::MakeXYWH(offsets[i][0],
y + offsets[i][1], offsets[i][1],
fBitmap.width() + offsets[i][2], fBitmap.width() + offsets[i][2],
fBitmap.height() + offsets[i][3]); fBitmap.height() + offsets[i][3]);
SkImageFilter::CropRect rect(SkRect::Make(cropRect)); SkImageFilter::CropRect rect(SkRect::Make(cropRect));
@ -212,13 +212,7 @@ protected:
filter.reset(SkNEW_ARGS(SkXfermodeImageFilter, filter.reset(SkNEW_ARGS(SkXfermodeImageFilter,
(mode, offsetBackground, offsetForeground, &rect))); (mode, offsetBackground, offsetForeground, &rect)));
paint.setImageFilter(filter); paint.setImageFilter(filter);
canvas->save(); drawClippedPaint(canvas, clipRect, paint, x, y);
canvas->clipRect(SkRect::MakeXYWH(SkIntToScalar(x),
SkIntToScalar(y),
SkIntToScalar(fBitmap.width() + 4),
SkIntToScalar(fBitmap.height() + 4)));
canvas->drawPaint(paint);
canvas->restore();
x += fBitmap.width() + MARGIN; x += fBitmap.width() + MARGIN;
if (x + fBitmap.width() > WIDTH) { if (x + fBitmap.width() > WIDTH) {
x = 0; x = 0;
@ -227,14 +221,12 @@ protected:
} }
} }
private: private:
typedef GM INHERITED;
SkBitmap fBitmap, fCheckerboard; SkBitmap fBitmap, fCheckerboard;
bool fInitialized; typedef GM INHERITED;
}; };
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
static GM* MyFactory(void*) { return new XfermodeImageFilterGM; } DEF_GM( return new XfermodeImageFilterGM; );
static GMRegistry reg(MyFactory);
} }

View File

@ -21,6 +21,7 @@
'../gm/bitmaprecttest.cpp', '../gm/bitmaprecttest.cpp',
'../gm/bitmapscroll.cpp', '../gm/bitmapscroll.cpp',
'../gm/bitmapshader.cpp', '../gm/bitmapshader.cpp',
'../gm/bitmapsource.cpp',
'../gm/bleed.cpp', '../gm/bleed.cpp',
'../gm/blurs.cpp', '../gm/blurs.cpp',
'../gm/blurquickreject.cpp', '../gm/blurquickreject.cpp',

View File

@ -14,6 +14,7 @@
class SK_API SkBitmapSource : public SkImageFilter { class SK_API SkBitmapSource : public SkImageFilter {
public: public:
explicit SkBitmapSource(const SkBitmap& bitmap); explicit SkBitmapSource(const SkBitmap& bitmap);
SkBitmapSource(const SkBitmap& bitmap, const SkRect& srcRect, const SkRect& dstRect);
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkBitmapSource) SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkBitmapSource)
@ -25,6 +26,7 @@ protected:
private: private:
SkBitmap fBitmap; SkBitmap fBitmap;
SkRect fSrcRect, fDstRect;
typedef SkImageFilter INHERITED; typedef SkImageFilter INHERITED;
}; };

View File

@ -6,24 +6,74 @@
*/ */
#include "SkBitmapSource.h" #include "SkBitmapSource.h"
#include "SkDevice.h"
#include "SkCanvas.h"
#include "SkFlattenableBuffers.h"
#include "SkValidationUtils.h"
SkBitmapSource::SkBitmapSource(const SkBitmap& bitmap) SkBitmapSource::SkBitmapSource(const SkBitmap& bitmap)
: INHERITED(0, 0), : INHERITED(0, 0),
fBitmap(bitmap) { fBitmap(bitmap),
fSrcRect(SkRect::MakeWH(SkIntToScalar(bitmap.width()),
SkIntToScalar(bitmap.height()))),
fDstRect(fSrcRect) {
}
SkBitmapSource::SkBitmapSource(const SkBitmap& bitmap, const SkRect& srcRect, const SkRect& dstRect)
: INHERITED(0, 0),
fBitmap(bitmap),
fSrcRect(srcRect),
fDstRect(dstRect) {
} }
SkBitmapSource::SkBitmapSource(SkFlattenableReadBuffer& buffer) SkBitmapSource::SkBitmapSource(SkFlattenableReadBuffer& buffer)
: INHERITED(0, buffer) { : INHERITED(0, buffer) {
fBitmap.unflatten(buffer); fBitmap.unflatten(buffer);
buffer.readRect(&fSrcRect);
buffer.readRect(&fDstRect);
buffer.validate(SkIsValidRect(fSrcRect) && SkIsValidRect(fDstRect));
} }
void SkBitmapSource::flatten(SkFlattenableWriteBuffer& buffer) const { void SkBitmapSource::flatten(SkFlattenableWriteBuffer& buffer) const {
this->INHERITED::flatten(buffer); this->INHERITED::flatten(buffer);
fBitmap.flatten(buffer); fBitmap.flatten(buffer);
buffer.writeRect(fSrcRect);
buffer.writeRect(fDstRect);
} }
bool SkBitmapSource::onFilterImage(Proxy*, const SkBitmap&, const SkMatrix&, bool SkBitmapSource::onFilterImage(Proxy* proxy, const SkBitmap&, const SkMatrix& matrix,
SkBitmap* result, SkIPoint* offset) { SkBitmap* result, SkIPoint* offset) {
*result = fBitmap; SkRect bounds, dstRect;
fBitmap.getBounds(&bounds);
matrix.mapRect(&dstRect, fDstRect);
if (fSrcRect == bounds && dstRect == bounds) {
// No regions cropped out or resized; return entire bitmap.
*result = fBitmap;
return true;
}
SkIRect dstIRect;
dstRect.roundOut(&dstIRect);
SkAutoTUnref<SkBaseDevice> device(proxy->createDevice(dstIRect.width(), dstIRect.height()));
if (NULL == device.get()) {
return false;
}
SkCanvas canvas(device.get());
SkPaint paint;
// Subtract off the integer component of the translation (will be applied in loc, below).
dstRect.offset(-SkIntToScalar(dstIRect.fLeft), -SkIntToScalar(dstIRect.fTop));
paint.setXfermodeMode(SkXfermode::kSrc_Mode);
// FIXME: this probably shouldn't be necessary, but drawBitmapRectToRect asserts
// None filtering when it's translate-only
paint.setFilterLevel(
fSrcRect.width() == dstRect.width() && fSrcRect.height() == dstRect.height() ?
SkPaint::kNone_FilterLevel : SkPaint::kMedium_FilterLevel);
canvas.drawBitmapRectToRect(fBitmap, &fSrcRect, dstRect, &paint);
*result = device.get()->accessBitmap(false);
offset->fX += dstIRect.fLeft;
offset->fY += dstIRect.fTop;
return true; return true;
} }

View File

@ -24,16 +24,24 @@ bool SkTileImageFilter::onFilterImage(Proxy* proxy, const SkBitmap& src, const S
return false; return false;
} }
int w = SkScalarTruncToInt(fDstRect.width()); SkRect dstRect;
int h = SkScalarTruncToInt(fDstRect.height()); ctm.mapRect(&dstRect, fDstRect);
int w = SkScalarCeilToInt(dstRect.width());
int h = SkScalarCeilToInt(dstRect.height());
if (!fSrcRect.width() || !fSrcRect.height() || !w || !h) { if (!fSrcRect.width() || !fSrcRect.height() || !w || !h) {
return false; return false;
} }
SkIRect srcRect; SkRect srcRect;
fSrcRect.roundOut(&srcRect); ctm.mapRect(&srcRect, fSrcRect);
SkIRect srcIRect;
srcRect.roundOut(&srcIRect);
SkBitmap subset; SkBitmap subset;
if (!source.extractSubset(&subset, srcRect)) { SkIRect bounds;
source.getBounds(&bounds);
if (!srcIRect.intersect(bounds)) {
return true;
} else if (!source.extractSubset(&subset, srcIRect)) {
return false; return false;
} }
@ -41,8 +49,6 @@ bool SkTileImageFilter::onFilterImage(Proxy* proxy, const SkBitmap& src, const S
if (NULL == device.get()) { if (NULL == device.get()) {
return false; return false;
} }
SkIRect bounds;
source.getBounds(&bounds);
SkCanvas canvas(device); SkCanvas canvas(device);
SkPaint paint; SkPaint paint;
paint.setXfermodeMode(SkXfermode::kSrc_Mode); paint.setXfermodeMode(SkXfermode::kSrc_Mode);
@ -50,7 +56,6 @@ bool SkTileImageFilter::onFilterImage(Proxy* proxy, const SkBitmap& src, const S
SkAutoTUnref<SkShader> shader(SkShader::CreateBitmapShader(subset, SkAutoTUnref<SkShader> shader(SkShader::CreateBitmapShader(subset,
SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode)); SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode));
paint.setShader(shader); paint.setShader(shader);
SkRect dstRect = fDstRect;
dstRect.offset(SkIntToScalar(localOffset.fX), SkIntToScalar(localOffset.fY)); dstRect.offset(SkIntToScalar(localOffset.fX), SkIntToScalar(localOffset.fY));
canvas.drawRect(dstRect, paint); canvas.drawRect(dstRect, paint);
*dst = device->accessBitmap(false); *dst = device->accessBitmap(false);