Home | History | Annotate | Download | only in pdf
      1 /*
      2  * Copyright 2011 Google Inc.
      3  *
      4  * Use of this source code is governed by a BSD-style license that can be
      5  * found in the LICENSE file.
      6  */
      7 
      8 
      9 #include "SkPDFShader.h"
     10 
     11 #include "SkData.h"
     12 #include "SkPDFDocument.h"
     13 #include "SkPDFDevice.h"
     14 #include "SkPDFDocumentPriv.h"
     15 #include "SkPDFFormXObject.h"
     16 #include "SkPDFGradientShader.h"
     17 #include "SkPDFGraphicState.h"
     18 #include "SkPDFResourceDict.h"
     19 #include "SkPDFUtils.h"
     20 #include "SkScalar.h"
     21 #include "SkStream.h"
     22 #include "SkSurface.h"
     23 #include "SkTemplates.h"
     24 
     25 
     26 static void draw_image_matrix(SkCanvas* canvas, const SkImage* img,
     27                               const SkMatrix& matrix, const SkPaint& paint) {
     28     SkAutoCanvasRestore acr(canvas, true);
     29     canvas->concat(matrix);
     30     canvas->drawImage(img, 0, 0, &paint);
     31 }
     32 
     33 static void draw_bitmap_matrix(SkCanvas* canvas, const SkBitmap& bm,
     34                                const SkMatrix& matrix, const SkPaint& paint) {
     35     SkAutoCanvasRestore acr(canvas, true);
     36     canvas->concat(matrix);
     37     canvas->drawBitmap(bm, 0, 0, &paint);
     38 }
     39 
     40 static SkPDFIndirectReference make_image_shader(SkPDFDocument* doc,
     41                                                 const SkPDFImageShaderKey& key,
     42                                                 SkImage* image) {
     43     SkASSERT(image);
     44 
     45     // The image shader pattern cell will be drawn into a separate device
     46     // in pattern cell space (no scaling on the bitmap, though there may be
     47     // translations so that all content is in the device, coordinates > 0).
     48 
     49     // Map clip bounds to shader space to ensure the device is large enough
     50     // to handle fake clamping.
     51     SkMatrix finalMatrix = key.fCanvasTransform;
     52     finalMatrix.preConcat(key.fShaderTransform);
     53     SkRect deviceBounds = SkRect::Make(key.fBBox);
     54     if (!SkPDFUtils::InverseTransformBBox(finalMatrix, &deviceBounds)) {
     55         return SkPDFIndirectReference();
     56     }
     57 
     58     SkRect bitmapBounds = SkRect::Make(image->bounds());
     59 
     60     // For tiling modes, the bounds should be extended to include the bitmap,
     61     // otherwise the bitmap gets clipped out and the shader is empty and awful.
     62     // For clamp modes, we're only interested in the clip region, whether
     63     // or not the main bitmap is in it.
     64     SkShader::TileMode tileModes[2];
     65     tileModes[0] = key.fImageTileModes[0];
     66     tileModes[1] = key.fImageTileModes[1];
     67     if (tileModes[0] != SkShader::kClamp_TileMode ||
     68             tileModes[1] != SkShader::kClamp_TileMode) {
     69         deviceBounds.join(bitmapBounds);
     70     }
     71 
     72     SkISize patternDeviceSize = {SkScalarCeilToInt(deviceBounds.width()),
     73                                  SkScalarCeilToInt(deviceBounds.height())};
     74     auto patternDevice = sk_make_sp<SkPDFDevice>(patternDeviceSize, doc);
     75     SkCanvas canvas(patternDevice);
     76 
     77     SkRect patternBBox = SkRect::Make(image->bounds());
     78 
     79     // Translate the canvas so that the bitmap origin is at (0, 0).
     80     canvas.translate(-deviceBounds.left(), -deviceBounds.top());
     81     patternBBox.offset(-deviceBounds.left(), -deviceBounds.top());
     82     // Undo the translation in the final matrix
     83     finalMatrix.preTranslate(deviceBounds.left(), deviceBounds.top());
     84 
     85     // If the bitmap is out of bounds (i.e. clamp mode where we only see the
     86     // stretched sides), canvas will clip this out and the extraneous data
     87     // won't be saved to the PDF.
     88     canvas.drawImage(image, 0, 0);
     89 
     90     SkScalar width = SkIntToScalar(image->width());
     91     SkScalar height = SkIntToScalar(image->height());
     92 
     93     SkPaint paint;
     94     paint.setColor(key.fPaintColor);
     95     // Tiling is implied.  First we handle mirroring.
     96     if (tileModes[0] == SkShader::kMirror_TileMode) {
     97         SkMatrix xMirror;
     98         xMirror.setScale(-1, 1);
     99         xMirror.postTranslate(2 * width, 0);
    100         draw_image_matrix(&canvas, image, xMirror, paint);
    101         patternBBox.fRight += width;
    102     }
    103     if (tileModes[1] == SkShader::kMirror_TileMode) {
    104         SkMatrix yMirror;
    105         yMirror.setScale(SK_Scalar1, -SK_Scalar1);
    106         yMirror.postTranslate(0, 2 * height);
    107         draw_image_matrix(&canvas, image, yMirror, paint);
    108         patternBBox.fBottom += height;
    109     }
    110     if (tileModes[0] == SkShader::kMirror_TileMode &&
    111             tileModes[1] == SkShader::kMirror_TileMode) {
    112         SkMatrix mirror;
    113         mirror.setScale(-1, -1);
    114         mirror.postTranslate(2 * width, 2 * height);
    115         draw_image_matrix(&canvas, image, mirror, paint);
    116     }
    117 
    118     // Then handle Clamping, which requires expanding the pattern canvas to
    119     // cover the entire surfaceBBox.
    120 
    121     SkBitmap bitmap;
    122     if (tileModes[0] == SkShader::kClamp_TileMode ||
    123         tileModes[1] == SkShader::kClamp_TileMode) {
    124         // For now, the easiest way to access the colors in the corners and sides is
    125         // to just make a bitmap from the image.
    126         if (!SkPDFUtils::ToBitmap(image, &bitmap)) {
    127             bitmap.allocN32Pixels(image->width(), image->height());
    128             bitmap.eraseColor(0x00000000);
    129         }
    130     }
    131 
    132     // If both x and y are in clamp mode, we start by filling in the corners.
    133     // (Which are just a rectangles of the corner colors.)
    134     if (tileModes[0] == SkShader::kClamp_TileMode &&
    135             tileModes[1] == SkShader::kClamp_TileMode) {
    136         SkASSERT(!bitmap.drawsNothing());
    137         SkPaint paint;
    138         SkRect rect;
    139         rect = SkRect::MakeLTRB(deviceBounds.left(), deviceBounds.top(), 0, 0);
    140         if (!rect.isEmpty()) {
    141             paint.setColor(bitmap.getColor(0, 0));
    142             canvas.drawRect(rect, paint);
    143         }
    144 
    145         rect = SkRect::MakeLTRB(width, deviceBounds.top(),
    146                                 deviceBounds.right(), 0);
    147         if (!rect.isEmpty()) {
    148             paint.setColor(bitmap.getColor(bitmap.width() - 1, 0));
    149             canvas.drawRect(rect, paint);
    150         }
    151 
    152         rect = SkRect::MakeLTRB(width, height,
    153                                 deviceBounds.right(), deviceBounds.bottom());
    154         if (!rect.isEmpty()) {
    155             paint.setColor(bitmap.getColor(bitmap.width() - 1,
    156                                            bitmap.height() - 1));
    157             canvas.drawRect(rect, paint);
    158         }
    159 
    160         rect = SkRect::MakeLTRB(deviceBounds.left(), height,
    161                                 0, deviceBounds.bottom());
    162         if (!rect.isEmpty()) {
    163             paint.setColor(bitmap.getColor(0, bitmap.height() - 1));
    164             canvas.drawRect(rect, paint);
    165         }
    166     }
    167 
    168     // Then expand the left, right, top, then bottom.
    169     if (tileModes[0] == SkShader::kClamp_TileMode) {
    170         SkASSERT(!bitmap.drawsNothing());
    171         SkIRect subset = SkIRect::MakeXYWH(0, 0, 1, bitmap.height());
    172         if (deviceBounds.left() < 0) {
    173             SkBitmap left;
    174             SkAssertResult(bitmap.extractSubset(&left, subset));
    175 
    176             SkMatrix leftMatrix;
    177             leftMatrix.setScale(-deviceBounds.left(), 1);
    178             leftMatrix.postTranslate(deviceBounds.left(), 0);
    179             draw_bitmap_matrix(&canvas, left, leftMatrix, paint);
    180 
    181             if (tileModes[1] == SkShader::kMirror_TileMode) {
    182                 leftMatrix.postScale(SK_Scalar1, -SK_Scalar1);
    183                 leftMatrix.postTranslate(0, 2 * height);
    184                 draw_bitmap_matrix(&canvas, left, leftMatrix, paint);
    185             }
    186             patternBBox.fLeft = 0;
    187         }
    188 
    189         if (deviceBounds.right() > width) {
    190             SkBitmap right;
    191             subset.offset(bitmap.width() - 1, 0);
    192             SkAssertResult(bitmap.extractSubset(&right, subset));
    193 
    194             SkMatrix rightMatrix;
    195             rightMatrix.setScale(deviceBounds.right() - width, 1);
    196             rightMatrix.postTranslate(width, 0);
    197             draw_bitmap_matrix(&canvas, right, rightMatrix, paint);
    198 
    199             if (tileModes[1] == SkShader::kMirror_TileMode) {
    200                 rightMatrix.postScale(SK_Scalar1, -SK_Scalar1);
    201                 rightMatrix.postTranslate(0, 2 * height);
    202                 draw_bitmap_matrix(&canvas, right, rightMatrix, paint);
    203             }
    204             patternBBox.fRight = deviceBounds.width();
    205         }
    206     }
    207 
    208     if (tileModes[1] == SkShader::kClamp_TileMode) {
    209         SkASSERT(!bitmap.drawsNothing());
    210         SkIRect subset = SkIRect::MakeXYWH(0, 0, bitmap.width(), 1);
    211         if (deviceBounds.top() < 0) {
    212             SkBitmap top;
    213             SkAssertResult(bitmap.extractSubset(&top, subset));
    214 
    215             SkMatrix topMatrix;
    216             topMatrix.setScale(SK_Scalar1, -deviceBounds.top());
    217             topMatrix.postTranslate(0, deviceBounds.top());
    218             draw_bitmap_matrix(&canvas, top, topMatrix, paint);
    219 
    220             if (tileModes[0] == SkShader::kMirror_TileMode) {
    221                 topMatrix.postScale(-1, 1);
    222                 topMatrix.postTranslate(2 * width, 0);
    223                 draw_bitmap_matrix(&canvas, top, topMatrix, paint);
    224             }
    225             patternBBox.fTop = 0;
    226         }
    227 
    228         if (deviceBounds.bottom() > height) {
    229             SkBitmap bottom;
    230             subset.offset(0, bitmap.height() - 1);
    231             SkAssertResult(bitmap.extractSubset(&bottom, subset));
    232 
    233             SkMatrix bottomMatrix;
    234             bottomMatrix.setScale(SK_Scalar1, deviceBounds.bottom() - height);
    235             bottomMatrix.postTranslate(0, height);
    236             draw_bitmap_matrix(&canvas, bottom, bottomMatrix, paint);
    237 
    238             if (tileModes[0] == SkShader::kMirror_TileMode) {
    239                 bottomMatrix.postScale(-1, 1);
    240                 bottomMatrix.postTranslate(2 * width, 0);
    241                 draw_bitmap_matrix(&canvas, bottom, bottomMatrix, paint);
    242             }
    243             patternBBox.fBottom = deviceBounds.height();
    244         }
    245     }
    246 
    247     auto imageShader = patternDevice->content();
    248     std::unique_ptr<SkPDFDict> resourceDict = patternDevice->makeResourceDict();
    249     std::unique_ptr<SkPDFDict> dict = SkPDFMakeDict();
    250     SkPDFUtils::PopulateTilingPatternDict(dict.get(), patternBBox,
    251                                           std::move(resourceDict), finalMatrix);
    252     return SkPDFStreamOut(std::move(dict), std::move(imageShader), doc);
    253 }
    254 
    255 // Generic fallback for unsupported shaders:
    256 //  * allocate a surfaceBBox-sized bitmap
    257 //  * shade the whole area
    258 //  * use the result as a bitmap shader
    259 static SkPDFIndirectReference make_fallback_shader(SkPDFDocument* doc,
    260                                                    SkShader* shader,
    261                                                    const SkMatrix& canvasTransform,
    262                                                    const SkIRect& surfaceBBox,
    263                                                    SkColor paintColor) {
    264     // TODO(vandebo) This drops SKComposeShader on the floor.  We could
    265     // handle compose shader by pulling things up to a layer, drawing with
    266     // the first shader, applying the xfer mode and drawing again with the
    267     // second shader, then applying the layer to the original drawing.
    268     SkPDFImageShaderKey key = {
    269         canvasTransform,
    270         SkMatrix::I(),
    271         surfaceBBox,
    272         {{0, 0, 0, 0}, 0},  // don't need the key; won't de-dup.
    273         {SkShader::kClamp_TileMode, SkShader::kClamp_TileMode},
    274         paintColor};
    275 
    276     key.fShaderTransform = shader->getLocalMatrix();
    277 
    278     // surfaceBBox is in device space. While that's exactly what we
    279     // want for sizing our bitmap, we need to map it into
    280     // shader space for adjustments (to match
    281     // MakeImageShader's behavior).
    282     SkRect shaderRect = SkRect::Make(surfaceBBox);
    283     if (!SkPDFUtils::InverseTransformBBox(canvasTransform, &shaderRect)) {
    284         return SkPDFIndirectReference();
    285     }
    286     // Clamp the bitmap size to about 1M pixels
    287     static const SkScalar kMaxBitmapArea = 1024 * 1024;
    288     SkScalar bitmapArea = surfaceBBox.width() * surfaceBBox.height();
    289     SkScalar rasterScale = 1.0f;
    290     if (bitmapArea > kMaxBitmapArea) {
    291         rasterScale *= SkScalarSqrt(kMaxBitmapArea / bitmapArea);
    292     }
    293 
    294     SkISize size = {SkScalarRoundToInt(rasterScale * surfaceBBox.width()),
    295                     SkScalarRoundToInt(rasterScale * surfaceBBox.height())};
    296     SkSize scale = {SkIntToScalar(size.width()) / shaderRect.width(),
    297                     SkIntToScalar(size.height()) / shaderRect.height()};
    298 
    299     auto surface = SkSurface::MakeRasterN32Premul(size.width(), size.height());
    300     SkCanvas* canvas = surface->getCanvas();
    301     canvas->clear(SK_ColorTRANSPARENT);
    302 
    303     SkPaint p;
    304     p.setShader(sk_ref_sp(shader));
    305     p.setColor(paintColor);
    306 
    307     canvas->scale(scale.width(), scale.height());
    308     canvas->translate(-shaderRect.x(), -shaderRect.y());
    309     canvas->drawPaint(p);
    310 
    311     key.fShaderTransform.setTranslate(shaderRect.x(), shaderRect.y());
    312     key.fShaderTransform.preScale(1 / scale.width(), 1 / scale.height());
    313 
    314     sk_sp<SkImage> image = surface->makeImageSnapshot();
    315     return make_image_shader(doc, key, image.get());
    316 }
    317 
    318 static SkColor adjust_color(SkShader* shader, SkColor paintColor) {
    319     if (SkImage* img = shader->isAImage(nullptr, nullptr)) {
    320         if (img->isAlphaOnly()) {
    321             return paintColor;
    322         }
    323     }
    324     // only preserve the alpha.
    325     return paintColor & SK_ColorBLACK;
    326 }
    327 
    328 SkPDFIndirectReference SkPDFMakeShader(SkPDFDocument* doc,
    329                                        SkShader* shader,
    330                                        const SkMatrix& canvasTransform,
    331                                        const SkIRect& surfaceBBox,
    332                                        SkColor paintColor) {
    333     SkASSERT(shader);
    334     SkASSERT(doc);
    335     if (SkShader::kNone_GradientType != shader->asAGradient(nullptr)) {
    336         return SkPDFGradientShader::Make(doc, shader, canvasTransform, surfaceBBox);
    337     }
    338     if (surfaceBBox.isEmpty()) {
    339         return SkPDFIndirectReference();
    340     }
    341     SkBitmap image;
    342     SkPDFImageShaderKey key = {
    343         canvasTransform,
    344         SkMatrix::I(),
    345         surfaceBBox,
    346         {{0, 0, 0, 0}, 0},
    347         {SkShader::kClamp_TileMode, SkShader::kClamp_TileMode},
    348         adjust_color(shader, paintColor)};
    349 
    350     SkASSERT(shader->asAGradient(nullptr) == SkShader::kNone_GradientType) ;
    351     if (SkImage* skimg = shader->isAImage(&key.fShaderTransform, key.fImageTileModes)) {
    352         key.fBitmapKey = SkBitmapKeyFromImage(skimg);
    353         SkPDFIndirectReference* shaderPtr = doc->fImageShaderMap.find(key);
    354         if (shaderPtr) {
    355             return *shaderPtr;
    356         }
    357         SkPDFIndirectReference pdfShader = make_image_shader(doc, key, skimg);
    358         doc->fImageShaderMap.set(std::move(key), pdfShader);
    359         return pdfShader;
    360     }
    361     // Don't bother to de-dup fallback shader.
    362     return make_fallback_shader(doc, shader, canvasTransform, surfaceBBox, key.fPaintColor);
    363 }
    364