halcanary | 1b5c604 | 2015-02-18 11:29:56 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2015 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 | #include "SkColorPriv.h" |
| 9 | #include "SkDeflateWStream.h" |
| 10 | #include "SkPDFBitmap.h" |
| 11 | #include "SkPDFCanon.h" |
| 12 | #include "SkPDFCatalog.h" |
| 13 | #include "SkPDFDocument.h" |
| 14 | #include "SkStream.h" |
| 15 | #include "SkUnPreMultiply.h" |
| 16 | |
| 17 | //////////////////////////////////////////////////////////////////////////////// |
| 18 | |
| 19 | static void pdf_stream_begin(SkWStream* stream) { |
| 20 | static const char streamBegin[] = " stream\n"; |
| 21 | stream->write(streamBegin, strlen(streamBegin)); |
| 22 | } |
| 23 | |
| 24 | static void pdf_stream_end(SkWStream* stream) { |
| 25 | static const char streamEnd[] = "\nendstream"; |
| 26 | stream->write(streamEnd, strlen(streamEnd)); |
| 27 | } |
| 28 | |
| 29 | static size_t pixel_count(const SkBitmap& bm) { |
| 30 | return SkToSizeT(bm.width()) * SkToSizeT(bm.height()); |
| 31 | } |
| 32 | |
halcanary | 1b5c604 | 2015-02-18 11:29:56 -0800 | [diff] [blame] | 33 | // write a single byte to a stream n times. |
| 34 | static void fill_stream(SkWStream* out, char value, size_t n) { |
| 35 | char buffer[4096]; |
| 36 | memset(buffer, value, sizeof(buffer)); |
| 37 | while (n) { |
| 38 | size_t k = SkTMin(n, sizeof(buffer)); |
| 39 | out->write(buffer, k); |
| 40 | n -= k; |
| 41 | } |
| 42 | } |
| 43 | |
| 44 | static SkPMColor get_pmcolor_neighbor_avg_color(const SkBitmap& bitmap, |
| 45 | int xOrig, |
| 46 | int yOrig) { |
| 47 | SkASSERT(kN32_SkColorType == bitmap.colorType()); |
| 48 | SkASSERT(bitmap.getPixels()); |
| 49 | uint8_t count = 0; |
| 50 | unsigned r = 0; |
| 51 | unsigned g = 0; |
| 52 | unsigned b = 0; |
| 53 | for (int y = yOrig - 1; y <= yOrig + 1; ++y) { |
| 54 | if (y < 0 || y >= bitmap.height()) { |
| 55 | continue; |
| 56 | } |
| 57 | uint32_t* src = bitmap.getAddr32(0, y); |
| 58 | for (int x = xOrig - 1; x <= xOrig + 1; ++x) { |
| 59 | if (x < 0 || x >= bitmap.width()) { |
| 60 | continue; |
| 61 | } |
| 62 | SkPMColor pmColor = src[x]; |
| 63 | U8CPU alpha = SkGetPackedA32(pmColor); |
| 64 | if (alpha != SK_AlphaTRANSPARENT) { |
| 65 | uint32_t s = SkUnPreMultiply::GetScale(alpha); |
| 66 | r += SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(pmColor)); |
| 67 | g += SkUnPreMultiply::ApplyScale(s, SkGetPackedG32(pmColor)); |
| 68 | b += SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(pmColor)); |
| 69 | ++count; |
| 70 | } |
| 71 | } |
| 72 | } |
| 73 | if (count == 0) { |
| 74 | return SkPackARGB32NoCheck(SK_AlphaOPAQUE, 0, 0, 0); |
| 75 | } else { |
| 76 | return SkPackARGB32NoCheck( |
| 77 | SK_AlphaOPAQUE, r / count, g / count, b / count); |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | static void pmcolor_to_rgb24(const SkBitmap& bm, SkWStream* out) { |
| 82 | SkASSERT(kN32_SkColorType == bm.colorType()); |
| 83 | if (!bm.getPixels()) { |
| 84 | fill_stream(out, '\xFF', 3 * pixel_count(bm)); |
| 85 | return; |
| 86 | } |
| 87 | size_t scanlineLength = 3 * bm.width(); |
| 88 | SkAutoTMalloc<uint8_t> scanline(scanlineLength); |
| 89 | for (int y = 0; y < bm.height(); ++y) { |
| 90 | uint8_t* dst = scanline.get(); |
| 91 | const SkPMColor* src = bm.getAddr32(0, y); |
| 92 | for (int x = 0; x < bm.width(); ++x) { |
| 93 | SkPMColor color = *src++; |
| 94 | U8CPU alpha = SkGetPackedA32(color); |
| 95 | if (alpha != SK_AlphaTRANSPARENT) { |
| 96 | uint32_t s = SkUnPreMultiply::GetScale(alpha); |
| 97 | *dst++ = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(color)); |
| 98 | *dst++ = SkUnPreMultiply::ApplyScale(s, SkGetPackedG32(color)); |
| 99 | *dst++ = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(color)); |
| 100 | } else { |
| 101 | /* It is necessary to average the color component of |
| 102 | transparent pixels with their surrounding neighbors |
| 103 | since the PDF renderer may separately re-sample the |
| 104 | alpha and color channels when the image is not |
| 105 | displayed at its native resolution. Since an alpha |
| 106 | of zero gives no information about the color |
| 107 | component, the pathological case is a white image |
| 108 | with sharp transparency bounds - the color channel |
| 109 | goes to black, and the should-be-transparent pixels |
| 110 | are rendered as grey because of the separate soft |
| 111 | mask and color resizing. e.g.: gm/bitmappremul.cpp */ |
| 112 | color = get_pmcolor_neighbor_avg_color(bm, x, y); |
| 113 | *dst++ = SkGetPackedR32(color); |
| 114 | *dst++ = SkGetPackedG32(color); |
| 115 | *dst++ = SkGetPackedB32(color); |
| 116 | } |
| 117 | } |
| 118 | out->write(scanline.get(), scanlineLength); |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | static void pmcolor_alpha_to_a8(const SkBitmap& bm, SkWStream* out) { |
| 123 | SkASSERT(kN32_SkColorType == bm.colorType()); |
| 124 | if (!bm.getPixels()) { |
| 125 | fill_stream(out, '\xFF', pixel_count(bm)); |
| 126 | return; |
| 127 | } |
| 128 | size_t scanlineLength = bm.width(); |
| 129 | SkAutoTMalloc<uint8_t> scanline(scanlineLength); |
| 130 | for (int y = 0; y < bm.height(); ++y) { |
| 131 | uint8_t* dst = scanline.get(); |
| 132 | const SkPMColor* src = bm.getAddr32(0, y); |
| 133 | for (int x = 0; x < bm.width(); ++x) { |
| 134 | *dst++ = SkGetPackedA32(*src++); |
| 135 | } |
| 136 | out->write(scanline.get(), scanlineLength); |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | //////////////////////////////////////////////////////////////////////////////// |
| 141 | |
| 142 | namespace { |
| 143 | // This SkPDFObject only outputs the alpha layer of the given bitmap. |
| 144 | class PDFAlphaBitmap : public SkPDFObject { |
| 145 | public: |
| 146 | PDFAlphaBitmap(const SkBitmap& bm) : fBitmap(bm) {} |
| 147 | ~PDFAlphaBitmap() {} |
| 148 | void emitObject(SkWStream*, SkPDFCatalog*) SK_OVERRIDE; |
| 149 | void addResources(SkTSet<SkPDFObject*>*, SkPDFCatalog*) const SK_OVERRIDE {} |
| 150 | |
| 151 | private: |
| 152 | const SkBitmap fBitmap; |
| 153 | void emitDict(SkWStream*, SkPDFCatalog*, size_t, bool) const; |
| 154 | }; |
| 155 | |
| 156 | void PDFAlphaBitmap::emitObject(SkWStream* stream, SkPDFCatalog* catalog) { |
| 157 | SkAutoLockPixels autoLockPixels(fBitmap); |
| 158 | |
halcanary | 1b5c604 | 2015-02-18 11:29:56 -0800 | [diff] [blame] | 159 | #ifndef SK_NO_FLATE |
| 160 | // Write to a temporary buffer to get the compressed length. |
| 161 | SkDynamicMemoryWStream buffer; |
| 162 | SkDeflateWStream deflateWStream(&buffer); |
| 163 | pmcolor_alpha_to_a8(fBitmap, &deflateWStream); |
| 164 | deflateWStream.finalize(); // call before detachAsStream(). |
| 165 | SkAutoTDelete<SkStreamAsset> asset(buffer.detachAsStream()); |
| 166 | |
| 167 | this->emitDict(stream, catalog, asset->getLength(), /*deflate=*/true); |
| 168 | pdf_stream_begin(stream); |
| 169 | stream->writeStream(asset.get(), asset->getLength()); |
| 170 | pdf_stream_end(stream); |
mtklein | cabc08c | 2015-02-19 08:29:24 -0800 | [diff] [blame] | 171 | #else |
| 172 | this->emitDict(stream, catalog, pixel_count(fBitmap), /*deflate=*/false); |
| 173 | pdf_stream_begin(stream); |
| 174 | pmcolor_alpha_to_a8(fBitmap, stream); |
| 175 | pdf_stream_end(stream); |
halcanary | 1b5c604 | 2015-02-18 11:29:56 -0800 | [diff] [blame] | 176 | #endif // SK_NO_FLATE |
| 177 | } |
| 178 | |
| 179 | void PDFAlphaBitmap::emitDict(SkWStream* stream, |
| 180 | SkPDFCatalog* catalog, |
| 181 | size_t length, |
| 182 | bool deflate) const { |
| 183 | SkPDFDict pdfDict("XObject"); |
| 184 | pdfDict.insertName("Subtype", "Image"); |
| 185 | pdfDict.insertInt("Width", fBitmap.width()); |
| 186 | pdfDict.insertInt("Height", fBitmap.height()); |
| 187 | pdfDict.insertName("ColorSpace", "DeviceGray"); |
| 188 | pdfDict.insertInt("BitsPerComponent", 8); |
| 189 | if (deflate) { |
| 190 | pdfDict.insertName("Filter", "FlateDecode"); |
| 191 | } |
| 192 | pdfDict.insertInt("Length", length); |
| 193 | pdfDict.emitObject(stream, catalog); |
| 194 | } |
| 195 | } // namespace |
| 196 | |
| 197 | //////////////////////////////////////////////////////////////////////////////// |
| 198 | |
| 199 | void SkPDFBitmap::addResources(SkTSet<SkPDFObject*>* resourceSet, |
| 200 | SkPDFCatalog* catalog) const { |
| 201 | if (fSMask.get()) { |
| 202 | resourceSet->add(fSMask.get()); |
| 203 | } |
| 204 | } |
| 205 | |
| 206 | void SkPDFBitmap::emitObject(SkWStream* stream, SkPDFCatalog* catalog) { |
| 207 | SkAutoLockPixels autoLockPixels(fBitmap); |
| 208 | |
halcanary | 1b5c604 | 2015-02-18 11:29:56 -0800 | [diff] [blame] | 209 | #ifndef SK_NO_FLATE |
| 210 | // Write to a temporary buffer to get the compressed length. |
| 211 | SkDynamicMemoryWStream buffer; |
| 212 | SkDeflateWStream deflateWStream(&buffer); |
| 213 | pmcolor_to_rgb24(fBitmap, &deflateWStream); |
| 214 | deflateWStream.finalize(); // call before detachAsStream(). |
| 215 | SkAutoTDelete<SkStreamAsset> asset(buffer.detachAsStream()); |
| 216 | |
| 217 | this->emitDict(stream, catalog, asset->getLength(), /*deflate=*/true); |
| 218 | pdf_stream_begin(stream); |
| 219 | stream->writeStream(asset.get(), asset->getLength()); |
| 220 | pdf_stream_end(stream); |
mtklein | cabc08c | 2015-02-19 08:29:24 -0800 | [diff] [blame] | 221 | #else |
| 222 | this->emitDict(stream, catalog, 3 * pixel_count(fBitmap), /*deflate=*/false); |
| 223 | pdf_stream_begin(stream); |
| 224 | pmcolor_to_rgb24(fBitmap, stream); |
| 225 | pdf_stream_end(stream); |
| 226 | return; |
halcanary | 1b5c604 | 2015-02-18 11:29:56 -0800 | [diff] [blame] | 227 | #endif // SK_NO_FLATE |
| 228 | } |
| 229 | |
| 230 | void SkPDFBitmap::emitDict(SkWStream* stream, |
| 231 | SkPDFCatalog* catalog, |
| 232 | size_t length, |
| 233 | bool deflate) const { |
| 234 | SkPDFDict pdfDict("XObject"); |
| 235 | pdfDict.insertName("Subtype", "Image"); |
| 236 | pdfDict.insertInt("Width", fBitmap.width()); |
| 237 | pdfDict.insertInt("Height", fBitmap.height()); |
| 238 | pdfDict.insertName("ColorSpace", "DeviceRGB"); |
| 239 | pdfDict.insertInt("BitsPerComponent", 8); |
| 240 | if (fSMask) { |
| 241 | pdfDict.insert("SMask", new SkPDFObjRef(fSMask))->unref(); |
| 242 | } |
| 243 | if (deflate) { |
| 244 | pdfDict.insertName("Filter", "FlateDecode"); |
| 245 | } |
| 246 | pdfDict.insertInt("Length", length); |
| 247 | pdfDict.emitObject(stream, catalog); |
| 248 | } |
| 249 | |
halcanary | 792c80f | 2015-02-20 07:21:05 -0800 | [diff] [blame] | 250 | SkPDFBitmap::SkPDFBitmap(SkPDFCanon* canon, |
| 251 | const SkBitmap& bm, |
| 252 | SkPDFObject* smask) |
| 253 | : fCanon(canon), fBitmap(bm), fSMask(smask) {} |
halcanary | 1b5c604 | 2015-02-18 11:29:56 -0800 | [diff] [blame] | 254 | |
halcanary | 792c80f | 2015-02-20 07:21:05 -0800 | [diff] [blame] | 255 | SkPDFBitmap::~SkPDFBitmap() { fCanon->removeBitmap(this); } |
halcanary | 1b5c604 | 2015-02-18 11:29:56 -0800 | [diff] [blame] | 256 | |
| 257 | //////////////////////////////////////////////////////////////////////////////// |
| 258 | static bool is_transparent(const SkBitmap& bm) { |
| 259 | SkAutoLockPixels autoLockPixels(bm); |
| 260 | if (NULL == bm.getPixels()) { |
| 261 | return true; |
| 262 | } |
| 263 | SkASSERT(kN32_SkColorType == bm.colorType()); |
| 264 | for (int y = 0; y < bm.height(); ++y) { |
| 265 | U8CPU alpha = 0; |
| 266 | const SkPMColor* src = bm.getAddr32(0, y); |
| 267 | for (int x = 0; x < bm.width(); ++x) { |
| 268 | alpha |= SkGetPackedA32(*src++); |
| 269 | } |
| 270 | if (alpha) { |
| 271 | return false; |
| 272 | } |
| 273 | } |
| 274 | return true; |
| 275 | } |
| 276 | |
halcanary | 792c80f | 2015-02-20 07:21:05 -0800 | [diff] [blame] | 277 | SkPDFBitmap* SkPDFBitmap::Create(SkPDFCanon* canon, |
| 278 | const SkBitmap& bitmap, |
halcanary | 1b5c604 | 2015-02-18 11:29:56 -0800 | [diff] [blame] | 279 | const SkIRect& subset) { |
halcanary | 792c80f | 2015-02-20 07:21:05 -0800 | [diff] [blame] | 280 | SkASSERT(canon); |
halcanary | 1b5c604 | 2015-02-18 11:29:56 -0800 | [diff] [blame] | 281 | if (kN32_SkColorType != bitmap.colorType()) { |
| 282 | // TODO(halcanary): support other colortypes. |
| 283 | return NULL; |
| 284 | } |
| 285 | SkBitmap bm; |
| 286 | // Should extractSubset be done by the SkPDFDevice? |
| 287 | if (!bitmap.extractSubset(&bm, subset)) { |
| 288 | return NULL; |
| 289 | } |
| 290 | if (bm.drawsNothing()) { |
| 291 | return NULL; |
| 292 | } |
| 293 | if (!bm.isImmutable()) { |
| 294 | SkBitmap copy; |
| 295 | if (!bm.copyTo(©)) { |
| 296 | return NULL; |
| 297 | } |
| 298 | copy.setImmutable(); |
| 299 | bm = copy; |
| 300 | } |
| 301 | |
halcanary | 792c80f | 2015-02-20 07:21:05 -0800 | [diff] [blame] | 302 | SkPDFBitmap* pdfBitmap = canon->findBitmap(bm); |
halcanary | 1b5c604 | 2015-02-18 11:29:56 -0800 | [diff] [blame] | 303 | if (pdfBitmap) { |
| 304 | return SkRef(pdfBitmap); |
| 305 | } |
| 306 | SkPDFObject* smask = NULL; |
| 307 | if (!bm.isOpaque() && !SkBitmap::ComputeIsOpaque(bm)) { |
| 308 | if (is_transparent(bm)) { |
| 309 | return NULL; |
| 310 | } |
| 311 | // PDFAlphaBitmaps do not get directly canonicalized (they |
| 312 | // are refed by the SkPDFBitmap). |
| 313 | smask = SkNEW_ARGS(PDFAlphaBitmap, (bm)); |
| 314 | } |
halcanary | 792c80f | 2015-02-20 07:21:05 -0800 | [diff] [blame] | 315 | pdfBitmap = SkNEW_ARGS(SkPDFBitmap, (canon, bm, smask)); |
| 316 | canon->addBitmap(pdfBitmap); |
halcanary | 1b5c604 | 2015-02-18 11:29:56 -0800 | [diff] [blame] | 317 | return pdfBitmap; |
| 318 | } |