Chromium Code Reviews| 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.
|
| +} |