blob: f2b105d473ced808fb802874aa9e133245a5b495 [file] [log] [blame]
/*
* 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 "SkData.h"
#include "SkFlate.h"
#include "SkPDFCatalog.h"
#include "SkRect.h"
#include "SkStream.h"
#include "SkString.h"
#include "SkUnPreMultiply.h"
static const int kNoColorTransform = 0;
static bool skip_compression(SkPDFCatalog* catalog) {
return SkToBool(catalog->getDocumentFlags() &
SkPDFDocument::kFavorSpeedOverSize_Flags);
}
static size_t get_uncompressed_size(const SkBitmap& bitmap,
const SkIRect& srcRect) {
switch (bitmap.config()) {
case SkBitmap::kIndex8_Config:
return srcRect.width() * srcRect.height();
case SkBitmap::kARGB_4444_Config:
return ((srcRect.width() * 3 + 1) / 2) * srcRect.height();
case SkBitmap::kRGB_565_Config:
return srcRect.width() * 3 * srcRect.height();
case SkBitmap::kARGB_8888_Config:
return srcRect.width() * 3 * srcRect.height();
case SkBitmap::kA1_Config:
case SkBitmap::kA8_Config:
return 1;
default:
SkASSERT(false);
return 0;
}
}
static SkStream* extract_index8_image(const SkBitmap& bitmap,
const SkIRect& srcRect) {
const int rowBytes = srcRect.width();
SkStream* stream = SkNEW_ARGS(SkMemoryStream,
(get_uncompressed_size(bitmap, srcRect)));
uint8_t* dst = (uint8_t*)stream->getMemoryBase();
for (int y = srcRect.fTop; y < srcRect.fBottom; y++) {
memcpy(dst, bitmap.getAddr8(srcRect.fLeft, y), rowBytes);
dst += rowBytes;
}
return stream;
}
static SkStream* extract_argb4444_data(const SkBitmap& bitmap,
const SkIRect& srcRect,
bool extractAlpha,
bool* isOpaque,
bool* isTransparent) {
SkStream* stream;
uint8_t* dst = NULL;
if (extractAlpha) {
const int alphaRowBytes = (srcRect.width() + 1) / 2;
stream = SkNEW_ARGS(SkMemoryStream,
(alphaRowBytes * srcRect.height()));
} else {
stream = SkNEW_ARGS(SkMemoryStream,
(get_uncompressed_size(bitmap, srcRect)));
}
dst = (uint8_t*)stream->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) {
if (extractAlpha) {
dst[0] = (SkGetPackedA4444(src[x]) << 4) |
SkGetPackedA4444(src[x + 1]);
*isOpaque &= dst[0] == SK_AlphaOPAQUE;
*isTransparent &= dst[0] == SK_AlphaTRANSPARENT;
dst++;
} else {
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;
}
}
if (srcRect.width() & 1) {
if (extractAlpha) {
dst[0] = (SkGetPackedA4444(src[x]) << 4);
*isOpaque &= dst[0] == (SK_AlphaOPAQUE & 0xF0);
*isTransparent &= dst[0] == (SK_AlphaTRANSPARENT & 0xF0);
dst++;
} else {
dst[0] = (SkGetPackedR4444(src[x]) << 4) |
SkGetPackedG4444(src[x]);
dst[1] = (SkGetPackedB4444(src[x]) << 4);
dst += 2;
}
}
}
return stream;
}
static SkStream* extract_rgb565_image(const SkBitmap& bitmap,
const SkIRect& srcRect) {
SkStream* stream = SkNEW_ARGS(SkMemoryStream,
(get_uncompressed_size(bitmap,
srcRect)));
uint8_t* dst = (uint8_t*)stream->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;
}
}
return stream;
}
static SkStream* extract_argb8888_data(const SkBitmap& bitmap,
const SkIRect& srcRect,
bool extractAlpha,
bool* isOpaque,
bool* isTransparent) {
SkStream* stream;
if (extractAlpha) {
stream = SkNEW_ARGS(SkMemoryStream,
(srcRect.width() * srcRect.height()));
} else {
stream = SkNEW_ARGS(SkMemoryStream,
(get_uncompressed_size(bitmap, srcRect)));
}
uint8_t* dst = (uint8_t*)stream->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++) {
if (extractAlpha) {
dst[0] = SkGetPackedA32(src[x]);
*isOpaque &= dst[0] == SK_AlphaOPAQUE;
*isTransparent &= dst[0] == SK_AlphaTRANSPARENT;
dst++;
} else {
dst[0] = SkGetPackedR32(src[x]);
dst[1] = SkGetPackedG32(src[x]);
dst[2] = SkGetPackedB32(src[x]);
dst += 3;
}
}
}
return stream;
}
static SkStream* extract_a1_alpha(const SkBitmap& bitmap,
const SkIRect& srcRect,
bool* isOpaque,
bool* isTransparent) {
const int alphaRowBytes = (srcRect.width() + 7) / 8;
SkStream* stream = SkNEW_ARGS(SkMemoryStream,
(alphaRowBytes * srcRect.height()));
uint8_t* alphaDst = (uint8_t*)stream->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) {
*isOpaque &= alphaDst[0] == SK_AlphaOPAQUE;
*isTransparent &= alphaDst[0] == SK_AlphaTRANSPARENT;
}
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) {
*isOpaque &= (alphaDst[-1] & mask) == (SK_AlphaOPAQUE & mask);
*isTransparent &=
(alphaDst[-1] & mask) == (SK_AlphaTRANSPARENT & mask);
}
}
return stream;
}
static SkStream* extract_a8_alpha(const SkBitmap& bitmap,
const SkIRect& srcRect,
bool* isOpaque,
bool* isTransparent) {
const int alphaRowBytes = srcRect.width();
SkStream* stream = SkNEW_ARGS(SkMemoryStream,
(alphaRowBytes * srcRect.height()));
uint8_t* alphaDst = (uint8_t*)stream->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];
*isOpaque &= alphaDst[0] == SK_AlphaOPAQUE;
*isTransparent &= alphaDst[0] == SK_AlphaTRANSPARENT;
alphaDst++;
}
}
return stream;
}
static SkStream* create_black_image() {
SkStream* stream = SkNEW_ARGS(SkMemoryStream, (1));
((uint8_t*)stream->getMemoryBase())[0] = 0;
return stream;
}
/**
* Extract either the color or image data from a SkBitmap into a SkStream.
* @param bitmap Bitmap to extract data from.
* @param srcRect Region in the bitmap to extract.
* @param extractAlpha Set to true to extract the alpha data or false to
* extract the color data.
* @param isTransparent Pointer to a bool to output whether the alpha is
* completely transparent. May be NULL. Only valid when
* extractAlpha == true.
* @return Unencoded image data, or NULL if either data was not
* available or alpha data was requested but the image was
* entirely transparent or opaque.
*/
static SkStream* extract_image_data(const SkBitmap& bitmap,
const SkIRect& srcRect,
bool extractAlpha, bool* isTransparent) {
SkBitmap::Config config = bitmap.config();
if (extractAlpha && (config == SkBitmap::kIndex8_Config ||
config == SkBitmap::kRGB_565_Config)) {
if (isTransparent != NULL) {
*isTransparent = false;
}
return NULL;
}
bool isOpaque = true;
bool transparent = extractAlpha;
SkStream* stream = NULL;
bitmap.lockPixels();
switch (config) {
case SkBitmap::kIndex8_Config:
if (!extractAlpha) {
stream = extract_index8_image(bitmap, srcRect);
}
break;
case SkBitmap::kARGB_4444_Config:
stream = extract_argb4444_data(bitmap, srcRect, extractAlpha,
&isOpaque, &transparent);
break;
case SkBitmap::kRGB_565_Config:
if (!extractAlpha) {
stream = extract_rgb565_image(bitmap, srcRect);
}
break;
case SkBitmap::kARGB_8888_Config:
stream = extract_argb8888_data(bitmap, srcRect, extractAlpha,
&isOpaque, &transparent);
break;
case SkBitmap::kA1_Config:
if (!extractAlpha) {
stream = create_black_image();
} else {
stream = extract_a1_alpha(bitmap, srcRect,
&isOpaque, &transparent);
}
break;
case SkBitmap::kA8_Config:
if (!extractAlpha) {
stream = create_black_image();
} else {
stream = extract_a8_alpha(bitmap, srcRect,
&isOpaque, &transparent);
}
break;
default:
SkASSERT(false);
}
bitmap.unlockPixels();
if (isTransparent != NULL) {
*isTransparent = transparent;
}
if (extractAlpha && (transparent || isOpaque)) {
SkSafeUnref(stream);
return NULL;
}
return stream;
}
static SkPDFArray* make_indexed_color_space(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;
}
/**
* Removes the alpha component of an ARGB color (including unpremultiply) while
* keeping the output in the same format as the input.
*/
static uint32_t remove_alpha_argb8888(uint32_t pmColor) {
SkColor color = SkUnPreMultiply::PMColorToColor(pmColor);
return SkPackARGB32NoCheck(SK_AlphaOPAQUE,
SkColorGetR(color),
SkColorGetG(color),
SkColorGetB(color));
}
static uint16_t remove_alpha_argb4444(uint16_t pmColor) {
return SkPixel32ToPixel4444(
remove_alpha_argb8888(SkPixel4444ToPixel32(pmColor)));
}
static uint32_t get_argb8888_neighbor_avg_color(const SkBitmap& bitmap,
int xOrig, int yOrig) {
uint8_t count = 0;
uint16_t r = 0;
uint16_t g = 0;
uint16_t b = 0;
for (int y = yOrig - 1; y <= yOrig + 1; y++) {
if (y < 0 || y >= bitmap.height()) {
continue;
}
uint32_t* src = bitmap.getAddr32(0, y);
for (int x = xOrig - 1; x <= xOrig + 1; x++) {
if (x < 0 || x >= bitmap.width()) {
continue;
}
if (SkGetPackedA32(src[x]) != SK_AlphaTRANSPARENT) {
uint32_t color = remove_alpha_argb8888(src[x]);
r += SkGetPackedR32(color);
g += SkGetPackedG32(color);
b += SkGetPackedB32(color);
count++;
}
}
}
if (count == 0) {
return SkPackARGB32NoCheck(SK_AlphaOPAQUE, 0, 0, 0);
} else {
return SkPackARGB32NoCheck(SK_AlphaOPAQUE,
r / count, g / count, b / count);
}
}
static uint16_t get_argb4444_neighbor_avg_color(const SkBitmap& bitmap,
int xOrig, int yOrig) {
uint8_t count = 0;
uint8_t r = 0;
uint8_t g = 0;
uint8_t b = 0;
for (int y = yOrig - 1; y <= yOrig + 1; y++) {
if (y < 0 || y >= bitmap.height()) {
continue;
}
uint16_t* src = bitmap.getAddr16(0, y);
for (int x = xOrig - 1; x <= xOrig + 1; x++) {
if (x < 0 || x >= bitmap.width()) {
continue;
}
if ((SkGetPackedA4444(src[x]) & 0x0F) != SK_AlphaTRANSPARENT) {
uint16_t color = remove_alpha_argb4444(src[x]);
r += SkGetPackedR4444(color);
g += SkGetPackedG4444(color);
b += SkGetPackedB4444(color);
count++;
}
}
}
if (count == 0) {
return SkPackARGB4444(SK_AlphaOPAQUE & 0x0F, 0, 0, 0);
} else {
return SkPackARGB4444(SK_AlphaOPAQUE & 0x0F,
r / count, g / count, b / count);
}
}
static SkBitmap unpremultiply_bitmap(const SkBitmap& bitmap,
const SkIRect& srcRect) {
SkBitmap outBitmap;
outBitmap.setConfig(bitmap.config(), srcRect.width(), srcRect.height());
outBitmap.allocPixels();
int dstRow = 0;
outBitmap.lockPixels();
bitmap.lockPixels();
switch (bitmap.config()) {
case SkBitmap::kARGB_4444_Config: {
for (int y = srcRect.fTop; y < srcRect.fBottom; y++) {
uint16_t* dst = outBitmap.getAddr16(0, dstRow);
uint16_t* src = bitmap.getAddr16(0, y);
for (int x = srcRect.fLeft; x < srcRect.fRight; x++) {
uint8_t a = SkGetPackedA4444(src[x]);
// It is necessary to average the color component of
// transparent pixels with their surrounding neighbors
// since the PDF renderer may separately re-sample the
// alpha and color channels when the image is not
// displayed at its native resolution. Since an alpha of
// zero gives no information about the color component,
// the pathological case is a white image with sharp
// transparency bounds - the color channel goes to black,
// and the should-be-transparent pixels are rendered
// as grey because of the separate soft mask and color
// resizing.
if (a == (SK_AlphaTRANSPARENT & 0x0F)) {
*dst = get_argb4444_neighbor_avg_color(bitmap, x, y);
} else {
*dst = remove_alpha_argb4444(src[x]);
}
dst++;
}
dstRow++;
}
break;
}
case SkBitmap::kARGB_8888_Config: {
for (int y = srcRect.fTop; y < srcRect.fBottom; y++) {
uint32_t* dst = outBitmap.getAddr32(0, dstRow);
uint32_t* src = bitmap.getAddr32(0, y);
for (int x = srcRect.fLeft; x < srcRect.fRight; x++) {
uint8_t a = SkGetPackedA32(src[x]);
if (a == SK_AlphaTRANSPARENT) {
*dst = get_argb8888_neighbor_avg_color(bitmap, x, y);
} else {
*dst = remove_alpha_argb8888(src[x]);
}
dst++;
}
dstRow++;
}
break;
}
default:
SkASSERT(false);
}
bitmap.unlockPixels();
outBitmap.unlockPixels();
outBitmap.setImmutable();
return outBitmap;
}
// static
SkPDFImage* SkPDFImage::CreateImage(const SkBitmap& bitmap,
const SkIRect& srcRect,
SkPicture::EncodeBitmap encoder) {
if (bitmap.config() == SkBitmap::kNo_Config) {
return NULL;
}
bool isTransparent = false;
SkAutoTUnref<SkStream> alphaData;
if (!bitmap.isOpaque()) {
// Note that isOpaque is not guaranteed to return false for bitmaps
// with alpha support but a completely opaque alpha channel,
// so alphaData may still be NULL if we have a completely opaque
// (or transparent) bitmap.
alphaData.reset(
extract_image_data(bitmap, srcRect, true, &isTransparent));
}
if (isTransparent) {
return NULL;
}
SkPDFImage* image;
SkBitmap::Config config = bitmap.config();
if (alphaData.get() != NULL && (config == SkBitmap::kARGB_8888_Config ||
config == SkBitmap::kARGB_4444_Config)) {
SkBitmap unpremulBitmap = unpremultiply_bitmap(bitmap, srcRect);
image = SkNEW_ARGS(SkPDFImage, (NULL, unpremulBitmap, false,
SkIRect::MakeWH(srcRect.width(), srcRect.height()),
encoder));
} else {
image = SkNEW_ARGS(SkPDFImage, (NULL, bitmap, false, srcRect, encoder));
}
if (alphaData.get() != NULL) {
SkAutoTUnref<SkPDFImage> mask(
SkNEW_ARGS(SkPDFImage, (alphaData.get(), bitmap,
true, srcRect, NULL)));
image->addSMask(mask);
}
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* stream,
const SkBitmap& bitmap,
bool isAlpha,
const SkIRect& srcRect,
SkPicture::EncodeBitmap encoder)
: fIsAlpha(isAlpha),
fSrcRect(srcRect),
fEncoder(encoder) {
if (bitmap.isImmutable()) {
fBitmap = bitmap;
} else {
bitmap.deepCopyTo(&fBitmap, bitmap.config());
fBitmap.setImmutable();
}
if (stream != NULL) {
setData(stream);
fStreamValid = true;
} else {
fStreamValid = false;
}
SkBitmap::Config config = fBitmap.config();
insertName("Type", "XObject");
insertName("Subtype", "Image");
bool alphaOnly = (config == SkBitmap::kA1_Config ||
config == SkBitmap::kA8_Config);
if (!isAlpha && 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", fSrcRect.width());
insertInt("Height", fSrcRect.height());
}
if (isAlpha || alphaOnly) {
insertName("ColorSpace", "DeviceGray");
} else if (config == SkBitmap::kIndex8_Config) {
SkAutoLockPixels alp(fBitmap);
insert("ColorSpace",
make_indexed_color_space(fBitmap.getColorTable()))->unref();
} else {
insertName("ColorSpace", "DeviceRGB");
}
int bitsPerComp = 8;
if (config == SkBitmap::kARGB_4444_Config) {
bitsPerComp = 4;
} else if (isAlpha && config == SkBitmap::kA1_Config) {
bitsPerComp = 1;
}
insertInt("BitsPerComponent", bitsPerComp);
if (config == SkBitmap::kRGB_565_Config) {
SkASSERT(!isAlpha);
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());
}
}
SkPDFImage::SkPDFImage(SkPDFImage& pdfImage)
: SkPDFStream(pdfImage),
fBitmap(pdfImage.fBitmap),
fIsAlpha(pdfImage.fIsAlpha),
fSrcRect(pdfImage.fSrcRect),
fEncoder(pdfImage.fEncoder),
fStreamValid(pdfImage.fStreamValid) {
// Nothing to do here - the image params are already copied in SkPDFStream's
// constructor, and the bitmap will be regenerated and encoded in
// populate.
}
bool SkPDFImage::populate(SkPDFCatalog* catalog) {
if (getState() == kUnused_State) {
// Initializing image data for the first time.
SkDynamicMemoryWStream dctCompressedWStream;
if (!skip_compression(catalog) && fEncoder &&
get_uncompressed_size(fBitmap, fSrcRect) > 1) {
SkBitmap subset;
// Extract subset
if (!fBitmap.extractSubset(&subset, fSrcRect)) {
// TODO(edisonn) It fails only for kA1_Config, if that is a
// major concern we will fix it later, so far it is NYI.
return false;
}
size_t pixelRefOffset = 0;
SkAutoTUnref<SkData> data(fEncoder(&pixelRefOffset, subset));
if (data.get() && data->size() < get_uncompressed_size(fBitmap,
fSrcRect)) {
SkAutoTUnref<SkStream> stream(SkNEW_ARGS(SkMemoryStream,
(data)));
setData(stream.get());
insertName("Filter", "DCTDecode");
insertInt("ColorTransform", kNoColorTransform);
insertInt("Length", getData()->getLength());
setState(kCompressed_State);
return true;
}
}
// Fallback method
if (!fStreamValid) {
SkAutoTUnref<SkStream> stream(
extract_image_data(fBitmap, fSrcRect, fIsAlpha, NULL));
setData(stream);
fStreamValid = true;
}
return INHERITED::populate(catalog);
} else if (getState() == kNoCompression_State &&
!skip_compression(catalog) &&
(SkFlate::HaveFlate() || fEncoder)) {
// Compression has not been requested when the stream was first created,
// but the new catalog wants it compressed.
if (!getSubstitute()) {
SkPDFStream* substitute = SkNEW_ARGS(SkPDFImage, (*this));
setSubstitute(substitute);
catalog->setSubstitute(this, substitute);
}
return false;
}
return true;
}