diff options
author | msarett <msarett@google.com> | 2015-03-24 12:24:27 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-03-24 12:24:27 -0700 |
commit | 15bfd075d38e4422a477e22940d06a137f66cc97 (patch) | |
tree | d3354771b661d48358071fac53b93e258dd9cf71 /src | |
parent | 46bffd6fbea7cc17c62cf469db145694cc650665 (diff) |
Enabling ico decoding with use of png and bmp decoders
BUG=skia:3257
Review URL: https://codereview.chromium.org/1011343003
Diffstat (limited to 'src')
-rw-r--r-- | src/codec/SkCodec.cpp | 2 | ||||
-rw-r--r-- | src/codec/SkCodec_libbmp.cpp | 216 | ||||
-rw-r--r-- | src/codec/SkCodec_libbmp.h | 30 | ||||
-rw-r--r-- | src/codec/SkCodec_libico.cpp | 254 | ||||
-rw-r--r-- | src/codec/SkCodec_libico.h | 62 | ||||
-rw-r--r-- | src/codec/SkCodec_libpng.cpp | 6 |
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, "ient, &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; |