Restyle SkPDFImageShader and support tiling bitmaps outside clip bounds
authorcommit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>
Wed, 21 Aug 2013 23:10:45 +0000 (23:10 +0000)
committercommit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>
Wed, 21 Aug 2013 23:10:45 +0000 (23:10 +0000)
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 [new file with mode: 0644]
gyp/gmslides.gypi
src/pdf/SkPDFShader.cpp

diff --git a/gm/clippedbitmapshaders.cpp b/gm/clippedbitmapshaders.cpp
new file mode 100644 (file)
index 0000000..02d07bf
--- /dev/null
@@ -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<uint32_t*>(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); )
+}
index e8950c2..76b6739 100644 (file)
@@ -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',
index 9394f1b..fa0587f 100644 (file)
@@ -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();
         }
     }