From e324cc69be2be62a76cef52ba3562771af02f315 Mon Sep 17 00:00:00 2001 From: "commit-bot@chromium.org" Date: Wed, 21 Aug 2013 23:10:45 +0000 Subject: Restyle SkPDFImageShader and support tiling bitmaps outside clip bounds BUG=chromium:99458 R=edisonn@google.com, vandebo@chromium.org Author: richardlin@chromium.org Review URL: https://chromiumcodereview.appspot.com/22884013 git-svn-id: http://skia.googlecode.com/svn/trunk@10870 2bbb7eff-a529-9590-31e7-b0007b416f81 --- gm/clippedbitmapshaders.cpp | 118 ++++++++++++++++++++++++++++++++++++++++++++ gyp/gmslides.gypi | 1 + src/pdf/SkPDFShader.cpp | 99 +++++++++++++++++++++++-------------- 3 files changed, 182 insertions(+), 36 deletions(-) create mode 100644 gm/clippedbitmapshaders.cpp diff --git a/gm/clippedbitmapshaders.cpp b/gm/clippedbitmapshaders.cpp new file mode 100644 index 0000000000..02d07bf01e --- /dev/null +++ b/gm/clippedbitmapshaders.cpp @@ -0,0 +1,118 @@ +/* + * 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 "SkBitmap.h" +#include "SkCanvas.h" +#include "SkColor.h" +#include "SkShader.h" + +namespace skiagm { + +// This GM draws a 3x3 grid (with the center element excluded) of rectangles +// filled with a bitmap shader. The bitmap shader is transformed so that the +// pattern cell is at the center (excluded) region. +// +// In Repeat and Mirror mode, this tests that the bitmap shader still draws +// even though the pattern cell is outside the clip. +// +// In Clamp mode, this tests that the clamp is handled properly. For PDF, +// (and possibly other exported formats) this also "tests" that the image itself +// is not stored (well, you'll need to open it up with an external tool to +// verify that). + +static SkBitmap create_bitmap() { + SkBitmap bmp; + bmp.setConfig(SkBitmap::kARGB_8888_Config, 2, 2); + bmp.allocPixels(); + bmp.lockPixels(); + uint32_t* pixels = reinterpret_cast(bmp.getPixels()); + pixels[0] = SkPreMultiplyColor(SK_ColorRED); + pixels[1] = SkPreMultiplyColor(SK_ColorGREEN); + pixels[2] = SkPreMultiplyColor(SK_ColorBLACK); + pixels[3] = SkPreMultiplyColor(SK_ColorBLUE); + bmp.unlockPixels(); + + return bmp; +} + +static const SkScalar RECT_SIZE = 64; +static const SkScalar SLIDE_SIZE = 300; + +class ClippedBitmapShadersGM : public GM { +public: + ClippedBitmapShadersGM(SkShader::TileMode mode) + : fMode(mode) { + } + +protected: + SkShader::TileMode fMode; + + virtual SkString onShortName() { + SkString descriptor; + switch (fMode) { + case SkShader::kRepeat_TileMode: + descriptor = "tile"; + break; + case SkShader::kMirror_TileMode: + descriptor = "mirror"; + break; + case SkShader::kClamp_TileMode: + descriptor = "clamp"; + break; + default: + SkASSERT(false); + } + descriptor.prepend("clipped-bitmap-shaders-"); + return descriptor; + } + + virtual SkISize onISize() { + return SkISize::Make(300, 300); + } + + virtual void onDraw(SkCanvas* canvas) { + SkBitmap bmp = create_bitmap(); + SkShader* shader = SkShader::CreateBitmapShader( + bmp, fMode, fMode); + + SkPaint paint; + SkMatrix s; + s.reset(); + s.setScale(8, 8); + s.postTranslate(SLIDE_SIZE / 2, SLIDE_SIZE / 2); + shader->setLocalMatrix(s); + paint.setShader(shader)->unref(); + + SkScalar margin = (SLIDE_SIZE / 3 - RECT_SIZE) / 2; + for (int i = 0; i < 3; i++) { + SkScalar yOrigin = SLIDE_SIZE / 3 * i + margin; + for (int j = 0; j < 3; j++) { + SkScalar xOrigin = SLIDE_SIZE / 3 * j + margin; + if (i == 1 && j == 1) { + continue; // skip center element + } + SkRect rect = SkRect::MakeXYWH(xOrigin, yOrigin, + RECT_SIZE, RECT_SIZE); + canvas->save(); + canvas->clipRect(rect); + canvas->drawRect(rect, paint); + canvas->restore(); + } + } + } + +private: + typedef GM INHERITED; +}; + +////////////////////////////////////////////////////////////////////////////// + +DEF_GM( return new ClippedBitmapShadersGM(SkShader::kRepeat_TileMode); ) +DEF_GM( return new ClippedBitmapShadersGM(SkShader::kMirror_TileMode); ) +DEF_GM( return new ClippedBitmapShadersGM(SkShader::kClamp_TileMode); ) +} diff --git a/gyp/gmslides.gypi b/gyp/gmslides.gypi index e8950c2da2..76b6739d99 100644 --- a/gyp/gmslides.gypi +++ b/gyp/gmslides.gypi @@ -23,6 +23,7 @@ '../gm/canvasstate.cpp', '../gm/circles.cpp', '../gm/circularclips.cpp', + '../gm/clippedbitmapshaders.cpp', '../gm/colorfilterimagefilter.cpp', '../gm/colormatrix.cpp', '../gm/colortype.cpp', diff --git a/src/pdf/SkPDFShader.cpp b/src/pdf/SkPDFShader.cpp index 9394f1b959..fa0587fc50 100644 --- a/src/pdf/SkPDFShader.cpp +++ b/src/pdf/SkPDFShader.cpp @@ -24,7 +24,7 @@ #include "SkTSet.h" #include "SkTypes.h" -static bool transformBBox(const SkMatrix& matrix, SkRect* bbox) { +static bool inverseTransformBBox(const SkMatrix& matrix, SkRect* bbox) { SkMatrix inverse; if (!matrix.invert(&inverse)) { return false; @@ -780,7 +780,7 @@ SkPDFFunctionShader::SkPDFFunctionShader(SkPDFShader::State* state) SkRect bbox; bbox.set(fState.get()->fBBox); - if (!transformBBox(finalMatrix, &bbox)) { + if (!inverseTransformBBox(finalMatrix, &bbox)) { return; } @@ -828,34 +828,60 @@ SkPDFFunctionShader::SkPDFFunctionShader(SkPDFShader::State* state) SkPDFImageShader::SkPDFImageShader(SkPDFShader::State* state) : fState(state) { fState.get()->fImage.lockPixels(); + // The image shader pattern cell will be drawn into a separate device + // in pattern cell space (no scaling on the bitmap, though there may be + // translations so that all content is in the device, coordinates > 0). + + // Map clip bounds to shader space to ensure the device is large enough + // to handle fake clamping. SkMatrix finalMatrix = fState.get()->fCanvasTransform; finalMatrix.preConcat(fState.get()->fShaderTransform); - SkRect surfaceBBox; - surfaceBBox.set(fState.get()->fBBox); - if (!transformBBox(finalMatrix, &surfaceBBox)) { + SkRect deviceBounds; + deviceBounds.set(fState.get()->fBBox); + if (!inverseTransformBBox(finalMatrix, &deviceBounds)) { return; } + const SkBitmap* image = &fState.get()->fImage; + SkRect bitmapBounds; + image->getBounds(&bitmapBounds); + + // For tiling modes, the bounds should be extended to include the bitmap, + // otherwise the bitmap gets clipped out and the shader is empty and awful. + // For clamp modes, we're only interested in the clip region, whether + // or not the main bitmap is in it. + SkShader::TileMode tileModes[2]; + tileModes[0] = fState.get()->fImageTileModes[0]; + tileModes[1] = fState.get()->fImageTileModes[1]; + if (tileModes[0] != SkShader::kClamp_TileMode || + tileModes[1] != SkShader::kClamp_TileMode) { + deviceBounds.join(bitmapBounds); + } + SkMatrix unflip; - unflip.setTranslate(0, SkScalarRoundToScalar(surfaceBBox.height())); + unflip.setTranslate(0, SkScalarRoundToScalar(deviceBounds.height())); unflip.preScale(SK_Scalar1, -SK_Scalar1); - SkISize size = SkISize::Make(SkScalarRound(surfaceBBox.width()), - SkScalarRound(surfaceBBox.height())); + SkISize size = SkISize::Make(SkScalarRound(deviceBounds.width()), + SkScalarRound(deviceBounds.height())); SkPDFDevice pattern(size, size, unflip); SkCanvas canvas(&pattern); - canvas.translate(-surfaceBBox.fLeft, -surfaceBBox.fTop); - finalMatrix.preTranslate(surfaceBBox.fLeft, surfaceBBox.fTop); - const SkBitmap* image = &fState.get()->fImage; - SkScalar width = SkIntToScalar(image->width()); - SkScalar height = SkIntToScalar(image->height()); - SkShader::TileMode tileModes[2]; - tileModes[0] = fState.get()->fImageTileModes[0]; - tileModes[1] = fState.get()->fImageTileModes[1]; + SkRect patternBBox; + image->getBounds(&patternBBox); + // Translate the canvas so that the bitmap origin is at (0, 0). + canvas.translate(-deviceBounds.left(), -deviceBounds.top()); + patternBBox.offset(-deviceBounds.left(), -deviceBounds.top()); + // Undo the translation in the final matrix + finalMatrix.preTranslate(deviceBounds.left(), deviceBounds.top()); + + // If the bitmap is out of bounds (i.e. clamp mode where we only see the + // stretched sides), canvas will clip this out and the extraneous data + // won't be saved to the PDF. canvas.drawBitmap(*image, 0, 0); - SkRect patternBBox = SkRect::MakeXYWH(-surfaceBBox.fLeft, -surfaceBBox.fTop, - width, height); + + SkScalar width = SkIntToScalar(image->width()); + SkScalar height = SkIntToScalar(image->height()); // Tiling is implied. First we handle mirroring. if (tileModes[0] == SkShader::kMirror_TileMode) { @@ -889,28 +915,29 @@ SkPDFImageShader::SkPDFImageShader(SkPDFShader::State* state) : fState(state) { tileModes[1] == SkShader::kClamp_TileMode) { SkPaint paint; SkRect rect; - rect = SkRect::MakeLTRB(surfaceBBox.fLeft, surfaceBBox.fTop, 0, 0); + rect = SkRect::MakeLTRB(deviceBounds.left(), deviceBounds.top(), 0, 0); if (!rect.isEmpty()) { paint.setColor(image->getColor(0, 0)); canvas.drawRect(rect, paint); } - rect = SkRect::MakeLTRB(width, surfaceBBox.fTop, surfaceBBox.fRight, 0); + rect = SkRect::MakeLTRB(width, deviceBounds.top(), + deviceBounds.right(), 0); if (!rect.isEmpty()) { paint.setColor(image->getColor(image->width() - 1, 0)); canvas.drawRect(rect, paint); } - rect = SkRect::MakeLTRB(width, height, surfaceBBox.fRight, - surfaceBBox.fBottom); + rect = SkRect::MakeLTRB(width, height, + deviceBounds.right(), deviceBounds.bottom()); if (!rect.isEmpty()) { paint.setColor(image->getColor(image->width() - 1, image->height() - 1)); canvas.drawRect(rect, paint); } - rect = SkRect::MakeLTRB(surfaceBBox.fLeft, height, 0, - surfaceBBox.fBottom); + rect = SkRect::MakeLTRB(deviceBounds.left(), height, + 0, deviceBounds.bottom()); if (!rect.isEmpty()) { paint.setColor(image->getColor(0, image->height() - 1)); canvas.drawRect(rect, paint); @@ -920,13 +947,13 @@ SkPDFImageShader::SkPDFImageShader(SkPDFShader::State* state) : fState(state) { // Then expand the left, right, top, then bottom. if (tileModes[0] == SkShader::kClamp_TileMode) { SkIRect subset = SkIRect::MakeXYWH(0, 0, 1, image->height()); - if (surfaceBBox.fLeft < 0) { + if (deviceBounds.left() < 0) { SkBitmap left; SkAssertResult(image->extractSubset(&left, subset)); SkMatrix leftMatrix; - leftMatrix.setScale(-surfaceBBox.fLeft, 1); - leftMatrix.postTranslate(surfaceBBox.fLeft, 0); + leftMatrix.setScale(-deviceBounds.left(), 1); + leftMatrix.postTranslate(deviceBounds.left(), 0); canvas.drawBitmapMatrix(left, leftMatrix); if (tileModes[1] == SkShader::kMirror_TileMode) { @@ -937,13 +964,13 @@ SkPDFImageShader::SkPDFImageShader(SkPDFShader::State* state) : fState(state) { patternBBox.fLeft = 0; } - if (surfaceBBox.fRight > width) { + if (deviceBounds.right() > width) { SkBitmap right; subset.offset(image->width() - 1, 0); SkAssertResult(image->extractSubset(&right, subset)); SkMatrix rightMatrix; - rightMatrix.setScale(surfaceBBox.fRight - width, 1); + rightMatrix.setScale(deviceBounds.right() - width, 1); rightMatrix.postTranslate(width, 0); canvas.drawBitmapMatrix(right, rightMatrix); @@ -952,19 +979,19 @@ SkPDFImageShader::SkPDFImageShader(SkPDFShader::State* state) : fState(state) { rightMatrix.postTranslate(0, 2 * height); canvas.drawBitmapMatrix(right, rightMatrix); } - patternBBox.fRight = surfaceBBox.width(); + patternBBox.fRight = deviceBounds.width(); } } if (tileModes[1] == SkShader::kClamp_TileMode) { SkIRect subset = SkIRect::MakeXYWH(0, 0, image->width(), 1); - if (surfaceBBox.fTop < 0) { + if (deviceBounds.top() < 0) { SkBitmap top; SkAssertResult(image->extractSubset(&top, subset)); SkMatrix topMatrix; - topMatrix.setScale(SK_Scalar1, -surfaceBBox.fTop); - topMatrix.postTranslate(0, surfaceBBox.fTop); + topMatrix.setScale(SK_Scalar1, -deviceBounds.top()); + topMatrix.postTranslate(0, deviceBounds.top()); canvas.drawBitmapMatrix(top, topMatrix); if (tileModes[0] == SkShader::kMirror_TileMode) { @@ -975,13 +1002,13 @@ SkPDFImageShader::SkPDFImageShader(SkPDFShader::State* state) : fState(state) { patternBBox.fTop = 0; } - if (surfaceBBox.fBottom > height) { + if (deviceBounds.bottom() > height) { SkBitmap bottom; subset.offset(0, image->height() - 1); SkAssertResult(image->extractSubset(&bottom, subset)); SkMatrix bottomMatrix; - bottomMatrix.setScale(SK_Scalar1, surfaceBBox.fBottom - height); + bottomMatrix.setScale(SK_Scalar1, deviceBounds.bottom() - height); bottomMatrix.postTranslate(0, height); canvas.drawBitmapMatrix(bottom, bottomMatrix); @@ -990,7 +1017,7 @@ SkPDFImageShader::SkPDFImageShader(SkPDFShader::State* state) : fState(state) { bottomMatrix.postTranslate(2 * width, 0); canvas.drawBitmapMatrix(bottom, bottomMatrix); } - patternBBox.fBottom = surfaceBBox.height(); + patternBBox.fBottom = deviceBounds.height(); } } -- cgit v1.2.3