diff options
author | 2011-01-31 19:35:43 +0000 | |
---|---|---|
committer | 2011-01-31 19:35:43 +0000 | |
commit | 1cfa2c458626abe952a63b1a9397d8e496a134d6 (patch) | |
tree | bd599e72c61b3ebde044724466029bf427d34752 | |
parent | 5f6ee1a8071346be874042a15dbf9352d0e8c520 (diff) |
[PDF] Support image alpha channel plus a couple small fixes.
Fix bug in rendering paths with cubic segments.
Only compress data if the compressed size is smaller than the uncompressed size.
Review URL: http://codereview.appspot.com/4079048
git-svn-id: http://skia.googlecode.com/svn/trunk@747 2bbb7eff-a529-9590-31e7-b0007b416f81
-rw-r--r-- | include/pdf/SkPDFImage.h | 41 | ||||
-rw-r--r-- | src/pdf/SkPDFDevice.cpp | 6 | ||||
-rw-r--r-- | src/pdf/SkPDFGraphicState.cpp | 3 | ||||
-rw-r--r-- | src/pdf/SkPDFImage.cpp | 260 | ||||
-rw-r--r-- | src/pdf/SkPDFStream.cpp | 6 |
5 files changed, 248 insertions, 68 deletions
diff --git a/include/pdf/SkPDFImage.h b/include/pdf/SkPDFImage.h index 48d6398616..9b4e4f9558 100644 --- a/include/pdf/SkPDFImage.h +++ b/include/pdf/SkPDFImage.h @@ -36,19 +36,45 @@ class SkPDFCatalog; // and settings used from the paint to canonicalize image objects. class SkPDFImage : public SkPDFObject { public: - /** Create a PDF image XObject. Entries for the image properties are - * automatically added to the stream dictionary. - * @param bitmap The image to use. - * @param paint Used to calculate alpha, masks, etc. + /** Create a new Image XObject to represent the passed bitmap. + * @param bitmap The image to encode. + * @param srcRect The rectangle to cut out of bitmap. + * @param paint Used to calculate alpha, masks, etc. + * @return The image XObject or NUll if there is nothing to draw for + * the given parameters. */ - SkPDFImage(const SkBitmap& bitmap, const SkIRect& srcRect, - const SkPaint& paint); + static SkPDFImage* CreateImage(const SkBitmap& bitmap, + const SkIRect& srcRect, + const SkPaint& paint); + virtual ~SkPDFImage(); + /** Add a Soft Mask (alpha or shape channel) to the image. + * @param mask A gray scale image representing the mask. + */ + void addSMask(SkPDFImage* mask); + // The SkPDFObject interface. virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog, bool indirect); virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect); + virtual void getResources(SkTDArray<SkPDFObject*>* resourceList); + +private: + SkRefPtr<SkPDFStream> fStream; + SkTDArray<SkPDFObject*> fResources; + + /** Create a PDF image XObject. Entries for the image properties are + * automatically added to the stream dictionary. + * @param imageData The final raw bits representing the image. + * @param bitmap The image parameters to use (Config, etc). + * @param srcRect The clipping applied to bitmap before generating + * imageData. + * @param alpha Is this the alpha channel of the bitmap. + * @param paint Used to calculate alpha, masks, etc. + */ + SkPDFImage(SkStream* imageData, const SkBitmap& bitmap, + const SkIRect& srcRect, bool alpha, const SkPaint& paint); /** Add the value to the stream dictionary with the given key. * @param key The key for this dictionary entry. @@ -61,9 +87,6 @@ public: * @param value The value for this dictionary entry. */ void insert(const char key[], SkPDFObject* value); - -private: - SkRefPtr<SkPDFStream> fStream; }; #endif diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp index b98e4afb09..d3a15a8b43 100644 --- a/src/pdf/SkPDFDevice.cpp +++ b/src/pdf/SkPDFDevice.cpp @@ -702,6 +702,7 @@ void SkPDFDevice::appendCubic(SkScalar ctl1X, SkScalar ctl1Y, fContent.appendScalar(dstX); fContent.append(" "); fContent.appendScalar(dstY); + fContent.append(" "); fContent.append(cmd); } @@ -817,6 +818,10 @@ void SkPDFDevice::internalDrawBitmap(const SkMatrix& matrix, if (srcRect && !subset.intersect(*srcRect)) return; + SkPDFImage* image = SkPDFImage::CreateImage(bitmap, subset, paint); + if (!image) + return; + SkMatrix scaled; // Adjust for origin flip. scaled.setScale(1, -1); @@ -826,7 +831,6 @@ void SkPDFDevice::internalDrawBitmap(const SkMatrix& matrix, scaled.postConcat(matrix); SkMatrix curTransform = setTransform(scaled); - SkPDFImage* image = new SkPDFImage(bitmap, subset, paint); fXObjectResources.push(image); // Transfer reference. fContent.append("/X"); fContent.appendS32(fXObjectResources.count() - 1); diff --git a/src/pdf/SkPDFGraphicState.cpp b/src/pdf/SkPDFGraphicState.cpp index 286468bb62..8cc8314f0e 100644 --- a/src/pdf/SkPDFGraphicState.cpp +++ b/src/pdf/SkPDFGraphicState.cpp @@ -83,9 +83,8 @@ void SkPDFGraphicState::populateDict() { typeName->unref(); // SkRefPtr and new both took a reference. insert("Type", typeName.get()); - SkScalar maxAlpha = SkIntToScalar(0xFF); SkRefPtr<SkPDFScalar> alpha = - new SkPDFScalar(SkColorGetA(fPaint.getColor())/maxAlpha); + new SkPDFScalar(fPaint.getAlpha() * SkScalarInvert(0xFF)); alpha->unref(); // SkRefPtr and new both took a reference. insert("CA", alpha.get()); insert("ca", alpha.get()); diff --git a/src/pdf/SkPDFImage.cpp b/src/pdf/SkPDFImage.cpp index 51bf8aeccd..6c35f5a6a6 100644 --- a/src/pdf/SkPDFImage.cpp +++ b/src/pdf/SkPDFImage.cpp @@ -29,16 +29,19 @@ namespace { -SkMemoryStream* extractImageData(const SkBitmap& bitmap, - const SkIRect& srcRect) { - SkMemoryStream* result = NULL; +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(); - result = new SkMemoryStream(rowBytes * srcRect.height()); - uint8_t* dst = (uint8_t*)result->getMemoryBase(); + 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; @@ -47,8 +50,8 @@ SkMemoryStream* extractImageData(const SkBitmap& bitmap, } case SkBitmap::kRLE_Index8_Config: { const int rowBytes = srcRect.width(); - result = new SkMemoryStream(rowBytes * srcRect.height()); - uint8_t* dst = (uint8_t*)result->getMemoryBase(); + image = new SkMemoryStream(rowBytes * srcRect.height()); + uint8_t* dst = (uint8_t*)image->getMemoryBase(); const SkBitmap::RLEPixels* rle = (const SkBitmap::RLEPixels*)bitmap.getPixels(); for (int y = srcRect.fTop; y < srcRect.fBottom; y++) { @@ -59,9 +62,13 @@ SkMemoryStream* extractImageData(const SkBitmap& bitmap, break; } case SkBitmap::kARGB_4444_Config: { + isTransparent = true; const int rowBytes = (srcRect.width() * 3 + 1) / 2; - result = new SkMemoryStream(rowBytes * srcRect.height()); - uint8_t* dst = (uint8_t*)result->getMemoryBase(); + 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; @@ -73,19 +80,31 @@ SkMemoryStream* extractImageData(const SkBitmap& bitmap, 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); + alphaDst[0] = (SkGetPackedA4444(src[x]) << 4); + if (alphaDst[0] != 0xF0) + hasAlpha = true; + if (alphaDst[0] & 0xF0) + isTransparent = false; } } break; } case SkBitmap::kRGB_565_Config: { const int rowBytes = srcRect.width() * 3; - result = new SkMemoryStream(rowBytes * srcRect.height()); - uint8_t* dst = (uint8_t*)result->getMemoryBase(); + 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++) { @@ -98,9 +117,12 @@ SkMemoryStream* extractImageData(const SkBitmap& bitmap, break; } case SkBitmap::kARGB_8888_Config: { + isTransparent = true; const int rowBytes = srcRect.width() * 3; - result = new SkMemoryStream(rowBytes * srcRect.height()); - uint8_t* dst = (uint8_t*)result->getMemoryBase(); + 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++) { @@ -108,6 +130,71 @@ SkMemoryStream* extractImageData(const SkBitmap& bitmap, 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; @@ -116,7 +203,18 @@ SkMemoryStream* extractImageData(const SkBitmap& bitmap, SkASSERT(false); } bitmap.unlockPixels(); - return result; + + if (isTransparent) { + SkSafeUnref(image); + } else { + *imageData = image; + } + + if (isTransparent || !hasAlpha) { + SkSafeUnref(alpha); + } else { + *alphaData = alpha; + } } SkPDFArray* makeIndexedColorSpace(SkColorTable* table) { @@ -153,20 +251,78 @@ SkPDFArray* makeIndexedColorSpace(SkColorTable* table) { }; // namespace -SkPDFImage::SkPDFImage(const SkBitmap& bitmap, const SkIRect& srcRect, - const SkPaint& paint) { - SkBitmap::Config config = bitmap.getConfig(); +// static +SkPDFImage* SkPDFImage::CreateImage(const SkBitmap& bitmap, + const SkIRect& srcRect, + const SkPaint& paint) { + 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 = + new SkPDFImage(imageData, bitmap, srcRect, false, paint); + + if (alphaData != NULL) { + SkRefPtr<SkPDFImage> alphaImage = + new SkPDFImage(alphaData, bitmap, srcRect, true, paint); + alphaImage->unref(); // SkRefPtr and new both took a reference. + image->addSMask(alphaImage.get()); + } + return image; +} + +SkPDFImage::~SkPDFImage() { + fResources.unrefAll(); +} + +void SkPDFImage::addSMask(SkPDFImage* mask) { + fResources.push(mask); + mask->ref(); + + SkRefPtr<SkPDFObjRef> maskRef = new SkPDFObjRef(mask); + maskRef->unref(); // SkRefPtr and new both took a reference. + insert("SMask", maskRef.get()); +} + +void SkPDFImage::emitObject(SkWStream* stream, SkPDFCatalog* catalog, + bool indirect) { + if (indirect) + return emitIndirectObject(stream, catalog); + + fStream->emitObject(stream, catalog, indirect); +} + +size_t SkPDFImage::getOutputSize(SkPDFCatalog* catalog, bool indirect) { + if (indirect) + return getIndirectOutputSize(catalog); - // TODO(vandebo) Handle alpha and alpha only images correctly. - SkASSERT(config == SkBitmap::kRGB_565_Config || - config == SkBitmap::kARGB_4444_Config || - config == SkBitmap::kARGB_8888_Config || - config == SkBitmap::kIndex8_Config || - config == SkBitmap::kRLE_Index8_Config); + return fStream->getOutputSize(catalog, indirect); +} - SkMemoryStream* image_data = extractImageData(bitmap, srcRect); - SkAutoUnref image_data_unref(image_data); - fStream = new SkPDFStream(image_data); +void SkPDFImage::getResources(SkTDArray<SkPDFObject*>* resourceList) { + if (fResources.count()) { + resourceList->setReserve(resourceList->count() + fResources.count()); + for (int i = 0; i < fResources.count(); i++) { + resourceList->push(fResources[i]); + fResources[i]->ref(); + fResources[i]->getResources(resourceList); + } + } +} + +SkPDFImage::SkPDFImage(SkStream* imageData, const SkBitmap& bitmap, + const SkIRect& srcRect, bool doingAlpha, + const SkPaint& paint) { + fStream = new SkPDFStream(imageData); fStream->unref(); // SkRefPtr and new both took a reference. SkRefPtr<SkPDFName> typeValue = new SkPDFName("XObject"); @@ -177,17 +333,31 @@ SkPDFImage::SkPDFImage(const SkBitmap& bitmap, const SkIRect& srcRect, subTypeValue->unref(); // SkRefPtr and new both took a reference. insert("Subtype", subTypeValue.get()); - SkRefPtr<SkPDFInt> widthValue = new SkPDFInt(srcRect.width()); + SkBitmap::Config config = bitmap.getConfig(); + bool alphaOnly = (config == SkBitmap::kA1_Config || + config == SkBitmap::kA8_Config); + + SkRefPtr<SkPDFInt> widthValue; + SkRefPtr<SkPDFInt> heightValue; + if (!doingAlpha && alphaOnly) { + // For alpha only images, we stretch a single pixel of black for + // the color/shape part. + widthValue = new SkPDFInt(1); + heightValue = widthValue.get(); + } else { + widthValue = new SkPDFInt(srcRect.width()); + heightValue = new SkPDFInt(srcRect.height()); + heightValue->unref(); // SkRefPtr and new both took a reference. + } widthValue->unref(); // SkRefPtr and new both took a reference. insert("Width", widthValue.get()); - - SkRefPtr<SkPDFInt> heightValue = new SkPDFInt(srcRect.height()); - heightValue->unref(); // SkRefPtr and new both took a reference. insert("Height", heightValue.get()); // if (!image mask) { SkRefPtr<SkPDFObject> colorSpaceValue; - if (config == SkBitmap::kIndex8_Config || + if (doingAlpha || alphaOnly) { + colorSpaceValue = new SkPDFName("DeviceGray"); + } else if (config == SkBitmap::kIndex8_Config || config == SkBitmap::kRLE_Index8_Config) { colorSpaceValue = makeIndexedColorSpace(bitmap.getColorTable()); } else { @@ -197,14 +367,11 @@ SkPDFImage::SkPDFImage(const SkBitmap& bitmap, const SkIRect& srcRect, insert("ColorSpace", colorSpaceValue.get()); // } - int bitsPerComp = bitmap.bytesPerPixel() * 2; - if (bitsPerComp == 0) { - SkASSERT(config == SkBitmap::kA1_Config); + int bitsPerComp = 8; + if (config == SkBitmap::kARGB_4444_Config) + bitsPerComp = 4; + else if (doingAlpha && config == SkBitmap::kA1_Config) bitsPerComp = 1; - } else if (bitsPerComp == 2 || - (bitsPerComp == 4 && config == SkBitmap::kRGB_565_Config)) { - bitsPerComp = 8; - } SkRefPtr<SkPDFInt> bitsPerCompValue = new SkPDFInt(bitsPerComp); bitsPerCompValue->unref(); // SkRefPtr and new both took a reference. insert("BitsPerComponent", bitsPerCompValue.get()); @@ -229,23 +396,6 @@ SkPDFImage::SkPDFImage(const SkBitmap& bitmap, const SkIRect& srcRect, } } -SkPDFImage::~SkPDFImage() {} - -void SkPDFImage::emitObject(SkWStream* stream, SkPDFCatalog* catalog, - bool indirect) { - if (indirect) - return emitIndirectObject(stream, catalog); - - fStream->emitObject(stream, catalog, indirect); -} - -size_t SkPDFImage::getOutputSize(SkPDFCatalog* catalog, bool indirect) { - if (indirect) - return getIndirectOutputSize(catalog); - - return fStream->getOutputSize(catalog, indirect); -} - void SkPDFImage::insert(SkPDFName* key, SkPDFObject* value) { fStream->insert(key, value); } diff --git a/src/pdf/SkPDFStream.cpp b/src/pdf/SkPDFStream.cpp index 6947ae4df9..cbba068816 100644 --- a/src/pdf/SkPDFStream.cpp +++ b/src/pdf/SkPDFStream.cpp @@ -20,14 +20,18 @@ #include "SkStream.h" SkPDFStream::SkPDFStream(SkStream* stream) { - if (SkFlate::HaveFlate()) { + if (SkFlate::HaveFlate()) SkAssertResult(SkFlate::Deflate(stream, &fCompressedData)); + + if (SkFlate::HaveFlate() && + fCompressedData.getOffset() < stream->getLength()) { fLength = fCompressedData.getOffset(); SkRefPtr<SkPDFName> flateFilter = new SkPDFName("FlateDecode"); flateFilter->unref(); // SkRefPtr and new both took a reference. fDict.insert("Filter", flateFilter.get()); } else { + fCompressedData.reset(); fPlainData = stream; fLength = fPlainData->getLength(); } |