Index: src/codec/SkCodec_libico.cpp |
diff --git a/src/codec/SkCodec_libico.cpp b/src/codec/SkCodec_libico.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..2be575a6c5fdd405b80ce42863bccee5b86100fa |
--- /dev/null |
+++ b/src/codec/SkCodec_libico.cpp |
@@ -0,0 +1,204 @@ |
+/* |
+ * 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" |
+ |
+/* |
+ * |
+ * 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; |
+ } |
+ |
+ // Ico files may contain multiple embedded images (Bmp or Png). We will |
+ // decode the "best" of these embedded images. The largest image in size |
scroggo
2015/03/18 21:39:19
In getPixels, the client will specify a desired Sk
msarett
2015/03/20 18:35:56
Done.
|
+ // is considered the "best". If two images have equal size, the image with |
+ // more bits per pixel is the "best". Note that bitsPerPixel is often left |
scroggo
2015/03/18 21:39:18
If the client wanted 565, maybe an 8 bit image is
msarett
2015/03/20 18:35:56
Acknowledged.
|
+ // blank, and we try to infer it from the color palette. This inferred |
+ // value is not what we use for the decode, it is just to guess the "best" |
+ // image. |
+ uint32_t bytesRead = kIcoDirectoryBytes + numImages*kIcoDirEntryBytes; |
+ int bestWidth = 0; |
+ int bestHeight = 0; |
+ int bestSize = 0; |
+ uint16_t bestBitsPerPixel = 0; |
+ uint32_t offset = 0; |
+ for (uint32_t i = 0; i < numImages; i++) { |
+ // Read the width and height |
+ // Width and height are both stored in a single byte, 0 is used as 256 |
+ int width = get_byte(entryBuffer.get(), 0 + i*kIcoDirEntryBytes); |
+ if (width == 0) { |
+ width = 256; |
+ } |
+ int height = get_byte(entryBuffer.get(), 1 + i*kIcoDirEntryBytes); |
+ if (height == 0) { |
+ height = 256; |
+ } |
+ int size = width * height; |
+ |
+ // Read or infer the bit depth |
+ uint32_t bitsPerPixel = get_short(entryBuffer.get(), |
+ 6 + i*kIcoDirEntryBytes); |
+ if (0 == bitsPerPixel) { |
+ // Number of colors in the color table |
+ uint32_t numColors = get_byte(entryBuffer.get(), |
+ 2 + i*kIcoDirEntryBytes); |
+ // This is not documented in the spec but used by many real images |
+ if (0 == numColors) { |
+ numColors = 256; |
+ } |
+ // Guess the number of bits per pixel |
+ for (uint32_t i = numColors - 1; i != 0; i >>= 1) { |
+ bitsPerPixel++; |
+ } |
+ } |
+ |
+ // Determine if the new entry is "best" |
+ bool best = (bestSize == size) ? |
+ (bitsPerPixel > bestBitsPerPixel) : (size > bestSize); |
+ |
+ if (best) { |
+ bestWidth = width; |
+ bestHeight = height; |
+ bestSize = size; |
+ bestBitsPerPixel = bitsPerPixel; |
+ |
+ // Offset of the image data from the start of the stream |
+ offset = get_int(entryBuffer.get(), |
+ 12 + i*kIcoDirEntryBytes); |
+ if (offset < bytesRead) { |
+ SkDebugf("Error: invalid image offset.\n"); |
+ return NULL; |
+ } |
+ } |
+ } |
+ |
+ // Create a codec for the "best" embedded image |
+ if (stream->skip(offset - bytesRead) != offset - bytesRead) { |
+ SkDebugf("Error: could not skip to image data.\n"); |
+ return NULL; |
+ } |
+ // We will pass the current stream to the embedded decoder |
+ const bool isPng = SkPngCodec::IsPng(stream); |
+ if (!stream->move(-PNG_BYTES_TO_CHECK)) { |
scroggo
2015/03/18 21:39:18
Unfortunately, we cannot depend on this call on An
msarett
2015/03/20 18:35:56
I have added a FIXME.
|
+ SkDebugf("Error: could not rewind embedded ico stream.\n"); |
+ return NULL; |
+ } |
+ SkCodec* codec; |
+ if (isPng) { |
+ codec = SkPngCodec::NewFromStream(stream); |
+ } else { |
+ // Assume the embedded image is a Bmp. Call a special constructor |
+ // because Bmp in Ico images are stored without the first header. |
+ codec = SkBmpCodec::NewFromIco(stream); |
+ } |
+ // Check for a valid result |
+ if (NULL == codec) { |
+ SkDebugf("Error: could not create embedded codec.\n"); |
+ return NULL; |
+ } |
+ SkAutoTDelete<SkCodec> embeddedCodec(codec); |
+ if (embeddedCodec->getOriginalInfo().width() != bestWidth || |
+ embeddedCodec->getOriginalInfo().height() != bestHeight) { |
+ SkDebugf("Warning: embedded dimensions do not match.\n"); |
+ // We will defer to the embedded dimensions |
+ bestWidth = embeddedCodec->getOriginalInfo().width(); |
+ bestHeight = embeddedCodec->getOriginalInfo().height(); |
+ } |
+ |
+ const SkImageInfo& imageInfo = SkImageInfo::Make(bestWidth, bestHeight, |
scroggo
2015/03/18 21:39:18
This should not be a reference.
msarett
2015/03/20 18:35:56
Done.
|
+ embeddedCodec->getOriginalInfo().colorType(), |
+ embeddedCodec->getOriginalInfo().alphaType()); |
+ // Note that stream is owned by the embedded codec, the ico does not need |
+ // direct access to the stream. |
+ return SkNEW_ARGS(SkIcoCodec, (imageInfo, NULL, embeddedCodec.detach())); |
+} |
+ |
+/* |
+ * |
+ * Creates an instance of the decoder |
+ * Called only by NewFromStream |
+ * |
+ */ |
+SkIcoCodec::SkIcoCodec(const SkImageInfo& info, SkStream* stream, |
+ SkCodec* embeddedCodec) |
+ : INHERITED(info, stream) |
scroggo
2015/03/18 21:39:19
If stream is always NULL, you can remove it from t
msarett
2015/03/20 18:35:56
Done.
|
+ , fEmbeddedCodec(embeddedCodec) |
+{} |
+ |
+/* |
+ * |
+ * Initiates the Ico decode |
+ * |
+ */ |
+SkCodec::Result SkIcoCodec::onGetPixels(const SkImageInfo& dstInfo, |
+ void* dst, size_t dstRowBytes, |
+ const Options& opts, SkPMColor* ct, |
+ int* ptr) { |
+ if (!this->rewindIfNeeded()) { |
scroggo
2015/03/18 21:39:18
How does this work? rewindIfNeeded will attempt to
msarett
2015/03/20 18:35:56
Giving the caller an option of which image to pick
|
+ return kCouldNotRewind; |
+ } |
+ if (dstInfo.dimensions() != this->getOriginalInfo().dimensions()) { |
+ SkDebugf("Error: scaling not supported.\n"); |
+ return kInvalidScale; |
+ } |
+ // We will not check if the conversion is possible because the embedded bmp |
+ // or png codec will make this check for us. |
+ |
+ // Decode the embedded image |
+ return fEmbeddedCodec->getPixels(dstInfo, dst, dstRowBytes); |
msarett
2015/03/18 19:59:25
Forgot to mention:
I'm not sure about the implicat
scroggo
2015/03/18 21:39:18
There are two versions of getPixels. You should ca
msarett
2015/03/20 18:35:56
Done.
|
+} |