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