aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorGravatar msarett <msarett@google.com>2015-03-24 12:24:27 -0700
committerGravatar Commit bot <commit-bot@chromium.org>2015-03-24 12:24:27 -0700
commit15bfd075d38e4422a477e22940d06a137f66cc97 (patch)
treed3354771b661d48358071fac53b93e258dd9cf71 /src
parent46bffd6fbea7cc17c62cf469db145694cc650665 (diff)
Enabling ico decoding with use of png and bmp decoders
Diffstat (limited to 'src')
-rw-r--r--src/codec/SkCodec.cpp2
-rw-r--r--src/codec/SkCodec_libbmp.cpp216
-rw-r--r--src/codec/SkCodec_libbmp.h30
-rw-r--r--src/codec/SkCodec_libico.cpp254
-rw-r--r--src/codec/SkCodec_libico.h62
-rw-r--r--src/codec/SkCodec_libpng.cpp6
6 files changed, 503 insertions, 67 deletions
diff --git a/src/codec/SkCodec.cpp b/src/codec/SkCodec.cpp
index 12341f5929..0411e44069 100644
--- a/src/codec/SkCodec.cpp
+++ b/src/codec/SkCodec.cpp
@@ -8,6 +8,7 @@
#include "SkCodec.h"
#include "SkData.h"
#include "SkCodec_libbmp.h"
+#include "SkCodec_libico.h"
#include "SkCodec_libpng.h"
#include "SkStream.h"
@@ -18,6 +19,7 @@ struct DecoderProc {
static const DecoderProc gDecoderProcs[] = {
{ SkPngCodec::IsPng, SkPngCodec::NewFromStream },
+ { SkIcoCodec::IsIco, SkIcoCodec::NewFromStream },
{ SkBmpCodec::IsBmp, SkBmpCodec::NewFromStream }
};
diff --git a/src/codec/SkCodec_libbmp.cpp b/src/codec/SkCodec_libbmp.cpp
index e9551cbb6f..ac6cf3a443 100644
--- a/src/codec/SkCodec_libbmp.cpp
+++ b/src/codec/SkCodec_libbmp.cpp
@@ -29,9 +29,6 @@ static bool conversion_possible(const SkImageInfo& dst,
return src.alphaType() == dst.alphaType() ||
(kPremul_SkAlphaType == dst.alphaType() &&
kUnpremul_SkAlphaType == src.alphaType());
- case kRGB_565_SkColorType:
- return src.alphaType() == dst.alphaType() &&
- kOpaque_SkAlphaType == dst.alphaType();
default:
return false;
}
@@ -88,11 +85,31 @@ bool SkBmpCodec::IsBmp(SkStream* stream) {
/*
*
* Assumes IsBmp was called and returned true
- * Creates a bitmap decoder
+ * Creates a bmp decoder
* Reads enough of the stream to determine the image format
*
*/
SkCodec* SkBmpCodec::NewFromStream(SkStream* stream) {
+ return SkBmpCodec::NewFromStream(stream, false);
+}
+
+/*
+ *
+ * Creates a bmp decoder for a bmp embedded in ico
+ * Reads enough of the stream to determine the image format
+ *
+ */
+SkCodec* SkBmpCodec::NewFromIco(SkStream* stream) {
+ return SkBmpCodec::NewFromStream(stream, true);
+}
+
+/*
+ *
+ * Creates a bmp decoder
+ * Reads enough of the stream to determine the image format
+ *
+ */
+SkCodec* SkBmpCodec::NewFromStream(SkStream* stream, bool isIco) {
// Header size constants
static const uint32_t kBmpHeaderBytes = 14;
static const uint32_t kBmpHeaderBytesPlusFour = kBmpHeaderBytes + 4;
@@ -106,37 +123,68 @@ SkCodec* SkBmpCodec::NewFromStream(SkStream* stream) {
static const uint32_t kBmpInfoV5Bytes = 124;
static const uint32_t kBmpMaskBytes = 12;
- // Read the first header and the size of the second header
- SkAutoTDeleteArray<uint8_t> hBuffer(
- SkNEW_ARRAY(uint8_t, kBmpHeaderBytesPlusFour));
- if (stream->read(hBuffer.get(), kBmpHeaderBytesPlusFour) !=
- kBmpHeaderBytesPlusFour) {
- SkDebugf("Error: unable to read first bitmap header.\n");
- return NULL;
- }
-
// The total bytes in the bmp file
- // We only need to use this value for RLE decoding, so we will only check
- // that it is valid in the RLE case.
- const uint32_t totalBytes = get_int(hBuffer.get(), 2);
-
+ // We only need to use this value for RLE decoding, so we will only
+ // check that it is valid in the RLE case.
+ uint32_t totalBytes;
// The offset from the start of the file where the pixel data begins
- const uint32_t offset = get_int(hBuffer.get(), 10);
- if (offset < kBmpHeaderBytes + kBmpOS2V1Bytes) {
- SkDebugf("Error: invalid starting location for pixel data\n");
- return NULL;
- }
-
+ uint32_t offset;
// The size of the second (info) header in bytes
- // The size is the first field of the second header, so we have already
- // read the first four infoBytes.
- const uint32_t infoBytes = get_int(hBuffer.get(), 14);
- if (infoBytes < kBmpOS2V1Bytes) {
- SkDebugf("Error: invalid second header size.\n");
- return NULL;
+ uint32_t infoBytes;
+
+ // Bmps embedded in Icos skip the first Bmp header
+ if (!isIco) {
+ // Read the first header and the size of the second header
+ SkAutoTDeleteArray<uint8_t> hBuffer(
+ SkNEW_ARRAY(uint8_t, kBmpHeaderBytesPlusFour));
+ if (stream->read(hBuffer.get(), kBmpHeaderBytesPlusFour) !=
+ kBmpHeaderBytesPlusFour) {
+ SkDebugf("Error: unable to read first bitmap header.\n");
+ return NULL;
+ }
+
+ totalBytes = get_int(hBuffer.get(), 2);
+ offset = get_int(hBuffer.get(), 10);
+ if (offset < kBmpHeaderBytes + kBmpOS2V1Bytes) {
+ SkDebugf("Error: invalid starting location for pixel data\n");
+ return NULL;
+ }
+
+ // The size of the second (info) header in bytes
+ // The size is the first field of the second header, so we have already
+ // read the first four infoBytes.
+ infoBytes = get_int(hBuffer.get(), 14);
+ if (infoBytes < kBmpOS2V1Bytes) {
+ SkDebugf("Error: invalid second header size.\n");
+ return NULL;
+ }
+ } else {
+ // This value is only used by RLE compression. Bmp in Ico files do not
+ // use RLE. If the compression field is incorrectly signaled as RLE,
+ // we will catch this and signal an error below.
+ totalBytes = 0;
+
+ // Bmps in Ico cannot specify an offset. We will always assume that
+ // pixel data begins immediately after the color table. This value
+ // will be corrected below.
+ offset = 0;
+
+ // Read the size of the second header
+ SkAutoTDeleteArray<uint8_t> hBuffer(
+ SkNEW_ARRAY(uint8_t, 4));
+ if (stream->read(hBuffer.get(), 4) != 4) {
+ SkDebugf("Error: unable to read size of second bitmap header.\n");
+ return NULL;
+ }
+ infoBytes = get_int(hBuffer.get(), 0);
+ if (infoBytes < kBmpOS2V1Bytes) {
+ SkDebugf("Error: invalid second header size.\n");
+ return NULL;
+ }
}
+
+ // We already read the first four bytes of the info header to get the size
const uint32_t infoBytesRemaining = infoBytes - 4;
- hBuffer.free();
// Read the second header
SkAutoTDeleteArray<uint8_t> iBuffer(
@@ -243,6 +291,11 @@ SkCodec* SkBmpCodec::NewFromStream(SkStream* stream) {
height = -height;
rowOrder = kTopDown_RowOrder;
}
+ // The height field for bmp in ico is double the actual height because they
+ // contain an XOR mask followed by an AND mask
+ if (isIco) {
+ height /= 2;
+ }
static const int kBmpMaxDim = 1 << 16;
if (width < 0 || width >= kBmpMaxDim || height >= kBmpMaxDim) {
// TODO: Decide if we want to support really large bmps.
@@ -344,16 +397,19 @@ SkCodec* SkBmpCodec::NewFromStream(SkStream* stream) {
// Most versions of bmps should be rendered as opaque. Either they do
// not have an alpha channel, or they expect the alpha channel to be
- // ignored. V4+ bmp files introduce an alpha mask and allow the creator
+ // ignored. V3+ bmp files introduce an alpha mask and allow the creator
// of the image to use the alpha channels. However, many of these images
- // leave the alpha channel blank and expect to be rendered as opaque. For
- // this reason, we set the alpha type to kUnknown for V4+ bmps and figure
- // out the alpha type during the decode.
+ // leave the alpha channel blank and expect to be rendered as opaque. This
+ // is the case for almost all V3 images, so we render these as opaque. For
+ // V4+, we will use the alpha channel, and fix the image later if it turns
+ // out to be fully transparent.
+ // As an exception, V3 bmp-in-ico may use an alpha mask.
SkAlphaType alphaType = kOpaque_SkAlphaType;
- if (kInfoV4_BitmapHeaderType == headerType ||
+ if ((kInfoV3_BitmapHeaderType == headerType && isIco) ||
+ kInfoV4_BitmapHeaderType == headerType ||
kInfoV5_BitmapHeaderType == headerType) {
// Header types are matched based on size. If the header is
- // V4+, we are guaranteed to be able to read at least this size.
+ // V3+, we are guaranteed to be able to read at least this size.
SkASSERT(infoBytesRemaining > 52);
inputMasks.alpha = get_int(iBuffer.get(), 48);
if (inputMasks.alpha != 0) {
@@ -362,6 +418,11 @@ SkCodec* SkBmpCodec::NewFromStream(SkStream* stream) {
}
iBuffer.free();
+ // Additionally, 32 bit bmp-in-icos use the alpha channel
+ if (isIco && 32 == bitsPerPixel) {
+ alphaType = kUnpremul_SkAlphaType;
+ }
+
// Check for valid bits per pixel input
switch (bitsPerPixel) {
// In addition to more standard pixel compression formats, bmp supports
@@ -406,7 +467,7 @@ SkCodec* SkBmpCodec::NewFromStream(SkStream* stream) {
// Calculate the number of bytes read so far
const uint32_t bytesRead = kBmpHeaderBytes + infoBytes + maskBytes;
- if (offset < bytesRead) {
+ if (!isIco && offset < bytesRead) {
SkDebugf("Error: pixel data offset less than header size.\n");
return NULL;
}
@@ -420,7 +481,7 @@ SkCodec* SkBmpCodec::NewFromStream(SkStream* stream) {
return SkNEW_ARGS(SkBmpCodec, (imageInfo, stream, bitsPerPixel,
inputFormat, masks.detach(), numColors,
bytesPerColor, offset - bytesRead,
- rowOrder, RLEBytes));
+ rowOrder, RLEBytes, isIco));
}
/*
@@ -433,7 +494,7 @@ SkBmpCodec::SkBmpCodec(const SkImageInfo& info, SkStream* stream,
uint16_t bitsPerPixel, BitmapInputFormat inputFormat,
SkMasks* masks, uint32_t numColors,
uint32_t bytesPerColor, uint32_t offset,
- RowOrder rowOrder, size_t RLEBytes)
+ RowOrder rowOrder, size_t RLEBytes, bool isIco)
: INHERITED(info, stream)
, fBitsPerPixel(bitsPerPixel)
, fInputFormat(inputFormat)
@@ -444,6 +505,8 @@ SkBmpCodec::SkBmpCodec(const SkImageInfo& info, SkStream* stream,
, fOffset(offset)
, fRowOrder(rowOrder)
, fRLEBytes(RLEBytes)
+ , fIsIco(isIco)
+
{}
/*
@@ -459,11 +522,11 @@ SkCodec::Result SkBmpCodec::onGetPixels(const SkImageInfo& dstInfo,
if (!this->rewindIfNeeded()) {
return kCouldNotRewind;
}
- if (dstInfo.dimensions() != this->getOriginalInfo().dimensions()) {
+ if (dstInfo.dimensions() != this->getInfo().dimensions()) {
SkDebugf("Error: scaling not supported.\n");
return kInvalidScale;
}
- if (!conversion_possible(dstInfo, this->getOriginalInfo())) {
+ if (!conversion_possible(dstInfo, this->getInfo())) {
SkDebugf("Error: cannot convert input type to output type.\n");
return kInvalidConversion;
}
@@ -553,26 +616,29 @@ SkCodec::Result SkBmpCodec::onGetPixels(const SkImageInfo& dstInfo,
}
}
- // Check that we have not read past the pixel array offset
- if(fOffset < colorBytes) {
- // This may occur on OS 2.1 and other old versions where the color
- // table defaults to max size, and the bmp tries to use a smaller color
- // table. This is invalid, and our decision is to indicate an error,
- // rather than try to guess the intended size of the color table.
- SkDebugf("Error: pixel data offset less than color table size.\n");
- return false;
- }
+ // Bmp-in-Ico files do not use an offset to indicate where the pixel data
+ // begins. Pixel data always begins immediately after the color table.
+ if (!fIsIco) {
+ // Check that we have not read past the pixel array offset
+ if(fOffset < colorBytes) {
+ // This may occur on OS 2.1 and other old versions where the color
+ // table defaults to max size, and the bmp tries to use a smaller
+ // color table. This is invalid, and our decision is to indicate
+ // an error, rather than try to guess the intended size of the
+ // color table.
+ SkDebugf("Error: pixel data offset less than color table size.\n");
+ return false;
+ }
- // After reading the color table, skip to the start of the pixel array
- if (stream()->skip(fOffset - colorBytes) != fOffset - colorBytes) {
- SkDebugf("Error: unable to skip to image data.\n");
- return false;
+ // After reading the color table, skip to the start of the pixel array
+ if (stream()->skip(fOffset - colorBytes) != fOffset - colorBytes) {
+ SkDebugf("Error: unable to skip to image data.\n");
+ return false;
+ }
}
// Set the color table and return true on success
- if (maxColors > 0) {
- fColorTable.reset(SkNEW_ARGS(SkColorTable, (colorTable, maxColors)));
- }
+ fColorTable.reset(SkNEW_ARGS(SkColorTable, (colorTable, maxColors)));
return true;
}
@@ -989,7 +1055,6 @@ SkCodec::Result SkBmpCodec::decode(const SkImageInfo& dstInfo,
// SkSwizzler does not support. Firstly, all bmp images that contain
// alpha are masked by the alpha mask. Secondly, many fully transparent
// bmp images are intended to be opaque. Here, we make those corrections.
- // Modifying alpha is safe because colors are stored unpremultiplied.
/*
SkPMColor* dstRow = (SkPMColor*) dst;
if (SkSwizzler::kBGRA == config) {
@@ -1006,6 +1071,41 @@ SkCodec::Result SkBmpCodec::decode(const SkImageInfo& dstInfo,
}
*/
+ // Finally, apply the AND mask for bmp-in-ico images
+ if (fIsIco) {
+ // The AND mask is always 1 bit per pixel
+ const size_t rowBytes = SkAlign4(compute_row_bytes(width, 1));
+
+ SkPMColor* dstPtr = (SkPMColor*) dst;
+ for (int y = 0; y < height; y++) {
+ // The srcBuffer will at least be large enough
+ if (stream()->read(srcBuffer.get(), rowBytes) != rowBytes) {
+ SkDebugf("Warning: incomplete AND mask for bmp-in-ico.\n");
+ return kIncompleteInput;
+ }
+
+ int row;
+ if (kBottomUp_RowOrder == fRowOrder) {
+ row = height - y - 1;
+ } else {
+ row = y;
+ }
+
+ SkPMColor* dstRow =
+ SkTAddOffset<SkPMColor>(dstPtr, row * dstRowBytes);
+
+ for (int x = 0; x < width; x++) {
+ int quotient;
+ int modulus;
+ SkTDivMod(x, 8, &quotient, &modulus);
+ uint32_t shift = 7 - modulus;
+ uint32_t alphaBit =
+ (srcBuffer.get()[quotient] >> shift) & 0x1;
+ dstRow[x] &= alphaBit - 1;
+ }
+ }
+ }
+
// Finished decoding the entire image
return kSuccess;
}
diff --git a/src/codec/SkCodec_libbmp.h b/src/codec/SkCodec_libbmp.h
index fb23716f77..4dda117d5a 100644
--- a/src/codec/SkCodec_libbmp.h
+++ b/src/codec/SkCodec_libbmp.h
@@ -7,7 +7,6 @@
#include "SkCodec.h"
#include "SkColorTable.h"
-#include "SkEncodedFormat.h"
#include "SkImageInfo.h"
#include "SkMaskSwizzler.h"
#include "SkStream.h"
@@ -36,7 +35,7 @@ public:
/*
*
- * Checks the start of the stream to see if the image is a bitmap
+ * Checks the start of the stream to see if the image is a bmp
*
*/
static bool IsBmp(SkStream*);
@@ -44,17 +43,25 @@ public:
/*
*
* Assumes IsBmp was called and returned true
- * Creates a bitmap decoder
+ * Creates a bmp decoder
* Reads enough of the stream to determine the image format
*
*/
static SkCodec* NewFromStream(SkStream*);
+ /*
+ *
+ * Creates a bmp decoder for a bmp embedded in ico
+ * Reads enough of the stream to determine the image format
+ *
+ */
+ static SkCodec* NewFromIco(SkStream*);
+
protected:
/*
*
- * Initiates the bitmap decode
+ * Initiates the bmp decode
*
*/
virtual Result onGetPixels(const SkImageInfo& dstInfo, void* dst,
@@ -62,11 +69,12 @@ protected:
int*) SK_OVERRIDE;
SkEncodedFormat onGetEncodedFormat() const SK_OVERRIDE { return kBMP_SkEncodedFormat; }
+
private:
/*
*
- * Used to define the input format of the bitmap
+ * Used to define the input format of the bmp
*
*/
enum BitmapInputFormat {
@@ -85,6 +93,14 @@ private:
/*
*
+ * Creates a bmp decoder
+ * Reads enough of the stream to determine the image format
+ *
+ */
+ static SkCodec* NewFromStream(SkStream*, bool isIco);
+
+ /*
+ *
* Performs the bitmap decoding for bit masks input format
*
*/
@@ -149,7 +165,8 @@ private:
SkBmpCodec(const SkImageInfo& srcInfo, SkStream* stream,
uint16_t bitsPerPixel, BitmapInputFormat format,
SkMasks* masks, uint32_t numColors, uint32_t bytesPerColor,
- uint32_t offset, RowOrder rowOrder, size_t RLEByes);
+ uint32_t offset, RowOrder rowOrder, size_t RLEBytes,
+ bool isIco);
// Fields
const uint16_t fBitsPerPixel;
@@ -161,6 +178,7 @@ private:
const uint32_t fOffset;
const RowOrder fRowOrder;
const size_t fRLEBytes;
+ const bool fIsIco;
typedef SkCodec INHERITED;
};
diff --git a/src/codec/SkCodec_libico.cpp b/src/codec/SkCodec_libico.cpp
new file mode 100644
index 0000000000..2adfa9cfde
--- /dev/null
+++ b/src/codec/SkCodec_libico.cpp
@@ -0,0 +1,254 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkCodec_libbmp.h"
+#include "SkCodec_libico.h"
+#include "SkCodec_libpng.h"
+#include "SkCodecPriv.h"
+#include "SkColorPriv.h"
+#include "SkData.h"
+#include "SkStream.h"
+#include "SkTDArray.h"
+#include "SkTSort.h"
+
+/*
+ * Checks the start of the stream to see if the image is an Ico or Cur
+ */
+bool SkIcoCodec::IsIco(SkStream* stream) {
+ const char icoSig[] = { '\x00', '\x00', '\x01', '\x00' };
+ const char curSig[] = { '\x00', '\x00', '\x02', '\x00' };
+ char buffer[sizeof(icoSig)];
+ return stream->read(buffer, sizeof(icoSig)) == sizeof(icoSig) &&
+ (!memcmp(buffer, icoSig, sizeof(icoSig)) ||
+ !memcmp(buffer, curSig, sizeof(curSig)));
+}
+
+/*
+ * Assumes IsIco was called and returned true
+ * Creates an Ico decoder
+ * Reads enough of the stream to determine the image format
+ */
+SkCodec* SkIcoCodec::NewFromStream(SkStream* stream) {
+ // Header size constants
+ static const uint32_t kIcoDirectoryBytes = 6;
+ static const uint32_t kIcoDirEntryBytes = 16;
+
+ // Read the directory header
+ SkAutoTDeleteArray<uint8_t> dirBuffer(
+ SkNEW_ARRAY(uint8_t, kIcoDirectoryBytes));
+ if (stream->read(dirBuffer.get(), kIcoDirectoryBytes) !=
+ kIcoDirectoryBytes) {
+ SkDebugf("Error: unable to read ico directory header.\n");
+ return NULL;
+ }
+
+ // Process the directory header
+ const uint16_t numImages = get_short(dirBuffer.get(), 4);
+ if (0 == numImages) {
+ SkDebugf("Error: No images embedded in ico.\n");
+ return NULL;
+ }
+
+ // Ensure that we can read all of indicated directory entries
+ SkAutoTDeleteArray<uint8_t> entryBuffer(
+ SkNEW_ARRAY(uint8_t, numImages*kIcoDirEntryBytes));
+ if (stream->read(entryBuffer.get(), numImages*kIcoDirEntryBytes) !=
+ numImages*kIcoDirEntryBytes) {
+ SkDebugf("Error: unable to read ico directory entries.\n");
+ return NULL;
+ }
+
+ // This structure is used to represent the vital information about entries
+ // in the directory header. We will obtain this information for each
+ // directory entry.
+ struct Entry {
+ uint32_t offset;
+ uint32_t size;
+ };
+ SkAutoTDeleteArray<Entry> directoryEntries(SkNEW_ARRAY(Entry, numImages));
+
+ // Iterate over directory entries
+ for (uint32_t i = 0; i < numImages; i++) {
+ // The directory entry contains information such as width, height,
+ // bits per pixel, and number of colors in the color palette. We will
+ // ignore these fields since they are repeated in the header of the
+ // embedded image. In the event of an inconsistency, we would always
+ // defer to the value in the embedded header anyway.
+
+ // Specifies the size of the embedded image, including the header
+ uint32_t size = get_int(entryBuffer.get(), 8 + i*kIcoDirEntryBytes);
+
+ // Specifies the offset of the embedded image from the start of file.
+ // It does not indicate the start of the pixel data, but rather the
+ // start of the embedded image header.
+ uint32_t offset = get_int(entryBuffer.get(), 12 + i*kIcoDirEntryBytes);
+
+ // Save the vital fields
+ directoryEntries.get()[i].offset = offset;
+ directoryEntries.get()[i].size = size;
+ }
+
+ // It is "customary" that the embedded images will be stored in order of
+ // increasing offset. However, the specification does not indicate that
+ // they must be stored in this order, so we will not trust that this is the
+ // case. Here we sort the embedded images by increasing offset.
+ struct EntryLessThan {
+ bool operator() (Entry a, Entry b) const {
+ return a.offset < b.offset;
+ }
+ };
+ EntryLessThan lessThan;
+ SkTQSort(directoryEntries.get(), directoryEntries.get() + numImages - 1,
+ lessThan);
+
+ // Now will construct a candidate codec for each of the embedded images
+ uint32_t bytesRead = kIcoDirectoryBytes + numImages * kIcoDirEntryBytes;
+ SkAutoTDelete<SkTArray<SkAutoTDelete<SkCodec>, true>> codecs(
+ SkNEW_ARGS((SkTArray<SkAutoTDelete<SkCodec>, true>), (numImages)));
+ for (uint32_t i = 0; i < numImages; i++) {
+ uint32_t offset = directoryEntries.get()[i].offset;
+ uint32_t size = directoryEntries.get()[i].size;
+
+ // Ensure that the offset is valid
+ if (offset < bytesRead) {
+ SkDebugf("Warning: invalid ico offset.\n");
+ continue;
+ }
+
+ // If we cannot skip, assume we have reached the end of the stream and
+ // stop trying to make codecs
+ if (stream->skip(offset - bytesRead) != offset - bytesRead) {
+ SkDebugf("Warning: could not skip to ico offset.\n");
+ break;
+ }
+ bytesRead = offset;
+
+ // Create a new stream for the embedded codec
+ SkAutoTUnref<SkData> data(SkData::NewFromStream(stream, size));
+ if (NULL == data.get()) {
+ SkDebugf("Warning: could not create embedded stream.\n");
+ break;
+ }
+ SkAutoTDelete<SkMemoryStream>
+ embeddedStream(SkNEW_ARGS(SkMemoryStream, (data.get())));
+ bytesRead += size;
+
+ // Check if the embedded codec is bmp or png and create the codec
+ const bool isPng = SkPngCodec::IsPng(embeddedStream);
+ SkAssertResult(embeddedStream->rewind());
+ SkCodec* codec = NULL;
+ if (isPng) {
+ codec = SkPngCodec::NewFromStream(embeddedStream.detach());
+ } else {
+ codec = SkBmpCodec::NewFromIco(embeddedStream.detach());
+ }
+
+ // Save a valid codec
+ if (NULL != codec) {
+ codecs->push_back().reset(codec);
+ }
+ }
+
+ // Recognize if there are no valid codecs
+ if (0 == codecs->count()) {
+ SkDebugf("Error: could not find any valid embedded ico codecs.\n");
+ return NULL;
+ }
+
+ // Use the largest codec as a "suggestion" for image info
+ uint32_t maxSize = 0;
+ uint32_t maxIndex = 0;
+ for (int32_t i = 0; i < codecs->count(); i++) {
+ SkImageInfo info = codecs->operator[](i)->getInfo();
+ uint32_t size = info.width() * info.height();
+ if (size > maxSize) {
+ maxSize = size;
+ maxIndex = i;
+ }
+ }
+ SkImageInfo info = codecs->operator[](maxIndex)->getInfo();
+
+ // Note that stream is owned by the embedded codec, the ico does not need
+ // direct access to the stream.
+ return SkNEW_ARGS(SkIcoCodec, (info, codecs.detach()));
+}
+
+/*
+ * Creates an instance of the decoder
+ * Called only by NewFromStream
+ */
+SkIcoCodec::SkIcoCodec(const SkImageInfo& info,
+ SkTArray<SkAutoTDelete<SkCodec>, true>* codecs)
+ : INHERITED(info, NULL)
+ , fEmbeddedCodecs(codecs)
+{}
+
+/*
+ * Chooses the best dimensions given the desired scale
+ */
+SkISize SkIcoCodec::onGetScaledDimensions(float desiredScale) const {
+ // We set the dimensions to the largest candidate image by default.
+ // Regardless of the scale request, this is the largest image that we
+ // will decode.
+ if (desiredScale >= 1.0) {
+ return this->getInfo().dimensions();
+ }
+
+ int origWidth = this->getInfo().width();
+ int origHeight = this->getInfo().height();
+ float desiredSize = desiredScale * origWidth * origHeight;
+ // At least one image will have smaller error than this initial value
+ float minError = ((float) (origWidth * origHeight)) - desiredSize + 1.0f;
+ int32_t minIndex = -1;
+ for (int32_t i = 0; i < fEmbeddedCodecs->count(); i++) {
+ int width = fEmbeddedCodecs->operator[](i)->getInfo().width();
+ int height = fEmbeddedCodecs->operator[](i)->getInfo().height();
+ float error = SkTAbs(((float) (width * height)) - desiredSize);
+ if (error < minError) {
+ minError = error;
+ minIndex = i;
+ }
+ }
+ SkASSERT(minIndex >= 0);
+
+ return fEmbeddedCodecs->operator[](minIndex)->getInfo().dimensions();
+}
+
+/*
+ * Initiates the Ico decode
+ */
+SkCodec::Result SkIcoCodec::onGetPixels(const SkImageInfo& dstInfo,
+ void* dst, size_t dstRowBytes,
+ const Options& opts, SkPMColor* ct,
+ int* ptr) {
+ // We return invalid scale if there is no candidate image with matching
+ // dimensions.
+ Result result = kInvalidScale;
+ for (int32_t i = 0; i < fEmbeddedCodecs->count(); i++) {
+ // If the dimensions match, try to decode
+ if (dstInfo.dimensions() ==
+ fEmbeddedCodecs->operator[](i)->getInfo().dimensions()) {
+
+ // Perform the decode
+ result = fEmbeddedCodecs->operator[](i)->getPixels(dstInfo,
+ dst, dstRowBytes, &opts, ct, ptr);
+
+ // On a fatal error, keep trying to find an image to decode
+ if (kInvalidConversion == result || kInvalidInput == result ||
+ kInvalidScale == result) {
+ SkDebugf("Warning: Attempt to decode candidate ico failed.\n");
+ continue;
+ }
+
+ // On success or partial success, return the result
+ return result;
+ }
+ }
+
+ SkDebugf("Error: No matching candidate image in ico.\n");
+ return result;
+}
diff --git a/src/codec/SkCodec_libico.h b/src/codec/SkCodec_libico.h
new file mode 100644
index 0000000000..778bcd2c54
--- /dev/null
+++ b/src/codec/SkCodec_libico.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkCodec.h"
+#include "SkImageInfo.h"
+#include "SkStream.h"
+#include "SkTypes.h"
+
+/*
+ * This class implements the decoding for bmp images
+ */
+class SkIcoCodec : public SkCodec {
+public:
+
+ /*
+ * Checks the start of the stream to see if the image is a Ico or Cur
+ */
+ static bool IsIco(SkStream*);
+
+ /*
+ * Assumes IsIco was called and returned true
+ * Creates an Ico decoder
+ * Reads enough of the stream to determine the image format
+ */
+ static SkCodec* NewFromStream(SkStream*);
+
+protected:
+
+ /*
+ * Chooses the best dimensions given the desired scale
+ */
+ SkISize onGetScaledDimensions(float desiredScale) const SK_OVERRIDE;
+
+ /*
+ * Initiates the Ico decode
+ */
+ Result onGetPixels(const SkImageInfo& dstInfo, void* dst,
+ size_t dstRowBytes, const Options&, SkPMColor*, int*)
+ SK_OVERRIDE;
+
+ SkEncodedFormat onGetEncodedFormat() const SK_OVERRIDE {
+ return kICO_SkEncodedFormat;
+ }
+
+private:
+
+ /*
+ * Constructor called by NewFromStream
+ * @param embeddedCodecs codecs for the embedded images, takes ownership
+ */
+ SkIcoCodec(const SkImageInfo& srcInfo,
+ SkTArray<SkAutoTDelete<SkCodec>, true>* embeddedCodecs);
+
+ SkAutoTDelete<SkTArray<SkAutoTDelete<SkCodec>, true>>
+ fEmbeddedCodecs; // owned
+
+ typedef SkCodec INHERITED;
+};
diff --git a/src/codec/SkCodec_libpng.cpp b/src/codec/SkCodec_libpng.cpp
index 57653e74df..e113a0e1b6 100644
--- a/src/codec/SkCodec_libpng.cpp
+++ b/src/codec/SkCodec_libpng.cpp
@@ -368,10 +368,10 @@ SkCodec::Result SkPngCodec::onGetPixels(const SkImageInfo& requestedInfo, void*
if (!this->rewindIfNeeded()) {
return kCouldNotRewind;
}
- if (requestedInfo.dimensions() != this->getOriginalInfo().dimensions()) {
+ if (requestedInfo.dimensions() != this->getInfo().dimensions()) {
return kInvalidScale;
}
- if (!conversion_possible(requestedInfo, this->getOriginalInfo())) {
+ if (!conversion_possible(requestedInfo, this->getInfo())) {
return kInvalidConversion;
}
@@ -424,7 +424,7 @@ SkCodec::Result SkPngCodec::onGetPixels(const SkImageInfo& requestedInfo, void*
// told png to upscale.
SkASSERT(PNG_COLOR_TYPE_GRAY == pngColorType);
sc = SkSwizzler::kGray;
- } else if (this->getOriginalInfo().alphaType() == kOpaque_SkAlphaType) {
+ } else if (this->getInfo().alphaType() == kOpaque_SkAlphaType) {
sc = SkSwizzler::kRGBX;
} else {
sc = SkSwizzler::kRGBA;