| /* |
| * Copyright 2010 The Android Open Source Project |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "SkPDFImage.h" |
| |
| #include "SkBitmap.h" |
| #include "SkColor.h" |
| #include "SkColorPriv.h" |
| #include "SkPDFCatalog.h" |
| #include "SkRect.h" |
| #include "SkStream.h" |
| #include "SkString.h" |
| #include "SkUnPreMultiply.h" |
| |
| namespace { |
| |
| void extractImageData(const SkBitmap& bitmap, const SkIRect& srcRect, |
| SkStream** imageData, SkStream** alphaData) { |
| SkMemoryStream* image = NULL; |
| SkMemoryStream* alpha = NULL; |
| bool hasAlpha = false; |
| bool isTransparent = false; |
| |
| bitmap.lockPixels(); |
| switch (bitmap.getConfig()) { |
| case SkBitmap::kIndex8_Config: { |
| const int rowBytes = srcRect.width(); |
| image = new SkMemoryStream(rowBytes * srcRect.height()); |
| uint8_t* dst = (uint8_t*)image->getMemoryBase(); |
| for (int y = srcRect.fTop; y < srcRect.fBottom; y++) { |
| memcpy(dst, bitmap.getAddr8(srcRect.fLeft, y), rowBytes); |
| dst += rowBytes; |
| } |
| break; |
| } |
| case SkBitmap::kARGB_4444_Config: { |
| isTransparent = true; |
| const int rowBytes = (srcRect.width() * 3 + 1) / 2; |
| const int alphaRowBytes = (srcRect.width() + 1) / 2; |
| image = new SkMemoryStream(rowBytes * srcRect.height()); |
| alpha = new SkMemoryStream(alphaRowBytes * srcRect.height()); |
| uint8_t* dst = (uint8_t*)image->getMemoryBase(); |
| uint8_t* alphaDst = (uint8_t*)alpha->getMemoryBase(); |
| for (int y = srcRect.fTop; y < srcRect.fBottom; y++) { |
| uint16_t* src = bitmap.getAddr16(0, y); |
| int x; |
| for (x = srcRect.fLeft; x + 1 < srcRect.fRight; x += 2) { |
| dst[0] = (SkGetPackedR4444(src[x]) << 4) | |
| SkGetPackedG4444(src[x]); |
| dst[1] = (SkGetPackedB4444(src[x]) << 4) | |
| SkGetPackedR4444(src[x + 1]); |
| dst[2] = (SkGetPackedG4444(src[x + 1]) << 4) | |
| SkGetPackedB4444(src[x + 1]); |
| dst += 3; |
| alphaDst[0] = (SkGetPackedA4444(src[x]) << 4) | |
| SkGetPackedA4444(src[x + 1]); |
| if (alphaDst[0] != 0xFF) { |
| hasAlpha = true; |
| } |
| if (alphaDst[0]) { |
| isTransparent = false; |
| } |
| alphaDst++; |
| } |
| if (srcRect.width() & 1) { |
| dst[0] = (SkGetPackedR4444(src[x]) << 4) | |
| SkGetPackedG4444(src[x]); |
| dst[1] = (SkGetPackedB4444(src[x]) << 4); |
| dst += 2; |
| alphaDst[0] = (SkGetPackedA4444(src[x]) << 4); |
| if (alphaDst[0] != 0xF0) { |
| hasAlpha = true; |
| } |
| if (alphaDst[0] & 0xF0) { |
| isTransparent = false; |
| } |
| alphaDst++; |
| } |
| } |
| break; |
| } |
| case SkBitmap::kRGB_565_Config: { |
| const int rowBytes = srcRect.width() * 3; |
| image = new SkMemoryStream(rowBytes * srcRect.height()); |
| uint8_t* dst = (uint8_t*)image->getMemoryBase(); |
| for (int y = srcRect.fTop; y < srcRect.fBottom; y++) { |
| uint16_t* src = bitmap.getAddr16(0, y); |
| for (int x = srcRect.fLeft; x < srcRect.fRight; x++) { |
| dst[0] = SkGetPackedR16(src[x]); |
| dst[1] = SkGetPackedG16(src[x]); |
| dst[2] = SkGetPackedB16(src[x]); |
| dst += 3; |
| } |
| } |
| break; |
| } |
| case SkBitmap::kARGB_8888_Config: { |
| isTransparent = true; |
| const int rowBytes = srcRect.width() * 3; |
| image = new SkMemoryStream(rowBytes * srcRect.height()); |
| alpha = new SkMemoryStream(srcRect.width() * srcRect.height()); |
| uint8_t* dst = (uint8_t*)image->getMemoryBase(); |
| uint8_t* alphaDst = (uint8_t*)alpha->getMemoryBase(); |
| for (int y = srcRect.fTop; y < srcRect.fBottom; y++) { |
| uint32_t* src = bitmap.getAddr32(0, y); |
| for (int x = srcRect.fLeft; x < srcRect.fRight; x++) { |
| dst[0] = SkGetPackedR32(src[x]); |
| dst[1] = SkGetPackedG32(src[x]); |
| dst[2] = SkGetPackedB32(src[x]); |
| dst += 3; |
| alphaDst[0] = SkGetPackedA32(src[x]); |
| if (alphaDst[0] != 0xFF) { |
| hasAlpha = true; |
| } |
| if (alphaDst[0]) { |
| isTransparent = false; |
| } |
| alphaDst++; |
| } |
| } |
| break; |
| } |
| case SkBitmap::kA1_Config: { |
| isTransparent = true; |
| image = new SkMemoryStream(1); |
| ((uint8_t*)image->getMemoryBase())[0] = 0; |
| |
| const int alphaRowBytes = (srcRect.width() + 7) / 8; |
| alpha = new SkMemoryStream(alphaRowBytes * srcRect.height()); |
| uint8_t* alphaDst = (uint8_t*)alpha->getMemoryBase(); |
| int offset1 = srcRect.fLeft % 8; |
| int offset2 = 8 - offset1; |
| for (int y = srcRect.fTop; y < srcRect.fBottom; y++) { |
| uint8_t* src = bitmap.getAddr1(0, y); |
| // This may read up to one byte after src, but the potentially |
| // invalid bits are never used for computation. |
| for (int x = srcRect.fLeft; x < srcRect.fRight; x += 8) { |
| if (offset1) { |
| alphaDst[0] = src[x / 8] << offset1 | |
| src[x / 8 + 1] >> offset2; |
| } else { |
| alphaDst[0] = src[x / 8]; |
| } |
| if (x + 7 < srcRect.fRight && alphaDst[0] != 0xFF) { |
| hasAlpha = true; |
| } |
| if (x + 7 < srcRect.fRight && alphaDst[0]) { |
| isTransparent = false; |
| } |
| alphaDst++; |
| } |
| // Calculate the mask of bits we're interested in within the |
| // last byte of alphaDst. |
| // width mod 8 == 1 -> 0x80 ... width mod 8 == 7 -> 0xFE |
| uint8_t mask = ~((1 << (8 - (srcRect.width() % 8))) - 1); |
| if (srcRect.width() % 8 && (alphaDst[-1] & mask) != mask) { |
| hasAlpha = true; |
| } |
| if (srcRect.width() % 8 && (alphaDst[-1] & mask)) { |
| isTransparent = false; |
| } |
| } |
| break; |
| } |
| case SkBitmap::kA8_Config: { |
| isTransparent = true; |
| image = new SkMemoryStream(1); |
| ((uint8_t*)image->getMemoryBase())[0] = 0; |
| |
| const int alphaRowBytes = srcRect.width(); |
| alpha = new SkMemoryStream(alphaRowBytes * srcRect.height()); |
| uint8_t* alphaDst = (uint8_t*)alpha->getMemoryBase(); |
| for (int y = srcRect.fTop; y < srcRect.fBottom; y++) { |
| uint8_t* src = bitmap.getAddr8(0, y); |
| for (int x = srcRect.fLeft; x < srcRect.fRight; x++) { |
| alphaDst[0] = src[x]; |
| if (alphaDst[0] != 0xFF) { |
| hasAlpha = true; |
| } |
| if (alphaDst[0]) { |
| isTransparent = false; |
| } |
| alphaDst++; |
| } |
| } |
| break; |
| } |
| default: |
| SkASSERT(false); |
| } |
| bitmap.unlockPixels(); |
| |
| if (isTransparent) { |
| SkSafeUnref(image); |
| } else { |
| *imageData = image; |
| } |
| |
| if (isTransparent || !hasAlpha) { |
| SkSafeUnref(alpha); |
| } else { |
| *alphaData = alpha; |
| } |
| } |
| |
| SkPDFArray* makeIndexedColorSpace(SkColorTable* table) { |
| SkPDFArray* result = new SkPDFArray(); |
| result->reserve(4); |
| result->appendName("Indexed"); |
| result->appendName("DeviceRGB"); |
| result->appendInt(table->count() - 1); |
| |
| // Potentially, this could be represented in fewer bytes with a stream. |
| // Max size as a string is 1.5k. |
| SkString index; |
| for (int i = 0; i < table->count(); i++) { |
| char buf[3]; |
| SkColor color = SkUnPreMultiply::PMColorToColor((*table)[i]); |
| buf[0] = SkGetPackedR32(color); |
| buf[1] = SkGetPackedG32(color); |
| buf[2] = SkGetPackedB32(color); |
| index.append(buf, 3); |
| } |
| result->append(new SkPDFString(index))->unref(); |
| return result; |
| } |
| |
| }; // namespace |
| |
| // static |
| SkPDFImage* SkPDFImage::CreateImage(const SkBitmap& bitmap, |
| const SkIRect& srcRect, |
| EncodeToDCTStream encoder) { |
| if (bitmap.getConfig() == SkBitmap::kNo_Config) { |
| return NULL; |
| } |
| |
| SkStream* imageData = NULL; |
| SkStream* alphaData = NULL; |
| extractImageData(bitmap, srcRect, &imageData, &alphaData); |
| SkAutoUnref unrefImageData(imageData); |
| SkAutoUnref unrefAlphaData(alphaData); |
| if (!imageData) { |
| SkASSERT(!alphaData); |
| return NULL; |
| } |
| |
| SkPDFImage* image = |
| SkNEW_ARGS(SkPDFImage, (imageData, bitmap, srcRect, false, encoder)); |
| |
| if (alphaData != NULL) { |
| // Don't try to use DCT compression with alpha because alpha is small |
| // anyway and it could lead to artifacts. |
| image->addSMask(SkNEW_ARGS(SkPDFImage, (alphaData, bitmap, srcRect, true, NULL)))->unref(); |
| } |
| return image; |
| } |
| |
| SkPDFImage::~SkPDFImage() { |
| fResources.unrefAll(); |
| } |
| |
| SkPDFImage* SkPDFImage::addSMask(SkPDFImage* mask) { |
| fResources.push(mask); |
| mask->ref(); |
| insert("SMask", new SkPDFObjRef(mask))->unref(); |
| return mask; |
| } |
| |
| void SkPDFImage::getResources(const SkTSet<SkPDFObject*>& knownResourceObjects, |
| SkTSet<SkPDFObject*>* newResourceObjects) { |
| GetResourcesHelper(&fResources, knownResourceObjects, newResourceObjects); |
| } |
| |
| SkPDFImage::SkPDFImage(SkStream* imageData, |
| const SkBitmap& bitmap, |
| const SkIRect& srcRect, |
| bool doingAlpha, |
| EncodeToDCTStream encoder) |
| : SkPDFImageStream(imageData, bitmap, srcRect, encoder) { |
| SkBitmap::Config config = bitmap.getConfig(); |
| bool alphaOnly = (config == SkBitmap::kA1_Config || |
| config == SkBitmap::kA8_Config); |
| |
| insertName("Type", "XObject"); |
| insertName("Subtype", "Image"); |
| |
| if (!doingAlpha && alphaOnly) { |
| // For alpha only images, we stretch a single pixel of black for |
| // the color/shape part. |
| SkAutoTUnref<SkPDFInt> one(new SkPDFInt(1)); |
| insert("Width", one.get()); |
| insert("Height", one.get()); |
| } else { |
| insertInt("Width", srcRect.width()); |
| insertInt("Height", srcRect.height()); |
| } |
| |
| // if (!image mask) { |
| if (doingAlpha || alphaOnly) { |
| insertName("ColorSpace", "DeviceGray"); |
| } else if (config == SkBitmap::kIndex8_Config) { |
| SkAutoLockPixels alp(bitmap); |
| insert("ColorSpace", |
| makeIndexedColorSpace(bitmap.getColorTable()))->unref(); |
| } else { |
| insertName("ColorSpace", "DeviceRGB"); |
| } |
| // } |
| |
| int bitsPerComp = 8; |
| if (config == SkBitmap::kARGB_4444_Config) { |
| bitsPerComp = 4; |
| } else if (doingAlpha && config == SkBitmap::kA1_Config) { |
| bitsPerComp = 1; |
| } |
| insertInt("BitsPerComponent", bitsPerComp); |
| |
| if (config == SkBitmap::kRGB_565_Config) { |
| SkAutoTUnref<SkPDFInt> zeroVal(new SkPDFInt(0)); |
| SkAutoTUnref<SkPDFScalar> scale5Val( |
| new SkPDFScalar(SkFloatToScalar(8.2258f))); // 255/2^5-1 |
| SkAutoTUnref<SkPDFScalar> scale6Val( |
| new SkPDFScalar(SkFloatToScalar(4.0476f))); // 255/2^6-1 |
| SkAutoTUnref<SkPDFArray> decodeValue(new SkPDFArray()); |
| decodeValue->reserve(6); |
| decodeValue->append(zeroVal.get()); |
| decodeValue->append(scale5Val.get()); |
| decodeValue->append(zeroVal.get()); |
| decodeValue->append(scale6Val.get()); |
| decodeValue->append(zeroVal.get()); |
| decodeValue->append(scale5Val.get()); |
| insert("Decode", decodeValue.get()); |
| } |
| } |