Home | History | Annotate | Download | only in codec
      1 /*
      2  * Copyright 2015 Google Inc.
      3  *
      4  * Use of this source code is governed by a BSD-style license that can be
      5  * found in the LICENSE file.
      6  */
      7 
      8 #include "SkCodec_libbmp.h"
      9 #include "SkCodec_libico.h"
     10 #include "SkCodec_libpng.h"
     11 #include "SkCodecPriv.h"
     12 #include "SkColorPriv.h"
     13 #include "SkData.h"
     14 #include "SkStream.h"
     15 #include "SkTDArray.h"
     16 #include "SkTSort.h"
     17 
     18 /*
     19  * Checks the start of the stream to see if the image is an Ico or Cur
     20  */
     21 bool SkIcoCodec::IsIco(SkStream* stream) {
     22     const char icoSig[] = { '\x00', '\x00', '\x01', '\x00' };
     23     const char curSig[] = { '\x00', '\x00', '\x02', '\x00' };
     24     char buffer[sizeof(icoSig)];
     25     return stream->read(buffer, sizeof(icoSig)) == sizeof(icoSig) &&
     26             (!memcmp(buffer, icoSig, sizeof(icoSig)) ||
     27             !memcmp(buffer, curSig, sizeof(curSig)));
     28 }
     29 
     30 /*
     31  * Assumes IsIco was called and returned true
     32  * Creates an Ico decoder
     33  * Reads enough of the stream to determine the image format
     34  */
     35 SkCodec* SkIcoCodec::NewFromStream(SkStream* stream) {
     36     // Ensure that we do not leak the input stream
     37     SkAutoTDelete<SkStream> inputStream(stream);
     38 
     39     // Header size constants
     40     static const uint32_t kIcoDirectoryBytes = 6;
     41     static const uint32_t kIcoDirEntryBytes = 16;
     42 
     43     // Read the directory header
     44     SkAutoTDeleteArray<uint8_t> dirBuffer(
     45             SkNEW_ARRAY(uint8_t, kIcoDirectoryBytes));
     46     if (inputStream.get()->read(dirBuffer.get(), kIcoDirectoryBytes) !=
     47             kIcoDirectoryBytes) {
     48         SkCodecPrintf("Error: unable to read ico directory header.\n");
     49         return NULL;
     50     }
     51 
     52     // Process the directory header
     53     const uint16_t numImages = get_short(dirBuffer.get(), 4);
     54     if (0 == numImages) {
     55         SkCodecPrintf("Error: No images embedded in ico.\n");
     56         return NULL;
     57     }
     58 
     59     // Ensure that we can read all of indicated directory entries
     60     SkAutoTDeleteArray<uint8_t> entryBuffer(
     61             SkNEW_ARRAY(uint8_t, numImages*kIcoDirEntryBytes));
     62     if (inputStream.get()->read(entryBuffer.get(), numImages*kIcoDirEntryBytes) !=
     63             numImages*kIcoDirEntryBytes) {
     64         SkCodecPrintf("Error: unable to read ico directory entries.\n");
     65         return NULL;
     66     }
     67 
     68     // This structure is used to represent the vital information about entries
     69     // in the directory header.  We will obtain this information for each
     70     // directory entry.
     71     struct Entry {
     72         uint32_t offset;
     73         uint32_t size;
     74     };
     75     SkAutoTDeleteArray<Entry> directoryEntries(SkNEW_ARRAY(Entry, numImages));
     76 
     77     // Iterate over directory entries
     78     for (uint32_t i = 0; i < numImages; i++) {
     79         // The directory entry contains information such as width, height,
     80         // bits per pixel, and number of colors in the color palette.  We will
     81         // ignore these fields since they are repeated in the header of the
     82         // embedded image.  In the event of an inconsistency, we would always
     83         // defer to the value in the embedded header anyway.
     84 
     85         // Specifies the size of the embedded image, including the header
     86         uint32_t size = get_int(entryBuffer.get(), 8 + i*kIcoDirEntryBytes);
     87 
     88         // Specifies the offset of the embedded image from the start of file.
     89         // It does not indicate the start of the pixel data, but rather the
     90         // start of the embedded image header.
     91         uint32_t offset = get_int(entryBuffer.get(), 12 + i*kIcoDirEntryBytes);
     92 
     93         // Save the vital fields
     94         directoryEntries.get()[i].offset = offset;
     95         directoryEntries.get()[i].size = size;
     96     }
     97 
     98     // It is "customary" that the embedded images will be stored in order of
     99     // increasing offset.  However, the specification does not indicate that
    100     // they must be stored in this order, so we will not trust that this is the
    101     // case.  Here we sort the embedded images by increasing offset.
    102     struct EntryLessThan {
    103         bool operator() (Entry a, Entry b) const {
    104             return a.offset < b.offset;
    105         }
    106     };
    107     EntryLessThan lessThan;
    108     SkTQSort(directoryEntries.get(), directoryEntries.get() + numImages - 1,
    109             lessThan);
    110 
    111     // Now will construct a candidate codec for each of the embedded images
    112     uint32_t bytesRead = kIcoDirectoryBytes + numImages * kIcoDirEntryBytes;
    113     SkAutoTDelete<SkTArray<SkAutoTDelete<SkCodec>, true>> codecs(
    114             SkNEW_ARGS((SkTArray<SkAutoTDelete<SkCodec>, true>), (numImages)));
    115     for (uint32_t i = 0; i < numImages; i++) {
    116         uint32_t offset = directoryEntries.get()[i].offset;
    117         uint32_t size = directoryEntries.get()[i].size;
    118 
    119         // Ensure that the offset is valid
    120         if (offset < bytesRead) {
    121             SkCodecPrintf("Warning: invalid ico offset.\n");
    122             continue;
    123         }
    124 
    125         // If we cannot skip, assume we have reached the end of the stream and
    126         // stop trying to make codecs
    127         if (inputStream.get()->skip(offset - bytesRead) != offset - bytesRead) {
    128             SkCodecPrintf("Warning: could not skip to ico offset.\n");
    129             break;
    130         }
    131         bytesRead = offset;
    132 
    133         // Create a new stream for the embedded codec
    134         SkAutoTUnref<SkData> data(
    135                 SkData::NewFromStream(inputStream.get(), size));
    136         if (NULL == data.get()) {
    137             SkCodecPrintf("Warning: could not create embedded stream.\n");
    138             break;
    139         }
    140         SkAutoTDelete<SkMemoryStream>
    141                 embeddedStream(SkNEW_ARGS(SkMemoryStream, (data.get())));
    142         bytesRead += size;
    143 
    144         // Check if the embedded codec is bmp or png and create the codec
    145         const bool isPng = SkPngCodec::IsPng(embeddedStream);
    146         SkAssertResult(embeddedStream->rewind());
    147         SkCodec* codec = NULL;
    148         if (isPng) {
    149             codec = SkPngCodec::NewFromStream(embeddedStream.detach());
    150         } else {
    151             codec = SkBmpCodec::NewFromIco(embeddedStream.detach());
    152         }
    153 
    154         // Save a valid codec
    155         if (NULL != codec) {
    156             codecs->push_back().reset(codec);
    157         }
    158     }
    159 
    160     // Recognize if there are no valid codecs
    161     if (0 == codecs->count()) {
    162         SkCodecPrintf("Error: could not find any valid embedded ico codecs.\n");
    163         return NULL;
    164     }
    165 
    166     // Use the largest codec as a "suggestion" for image info
    167     uint32_t maxSize = 0;
    168     uint32_t maxIndex = 0;
    169     for (int32_t i = 0; i < codecs->count(); i++) {
    170         SkImageInfo info = codecs->operator[](i)->getInfo();
    171         uint32_t size = info.width() * info.height();
    172         if (size > maxSize) {
    173             maxSize = size;
    174             maxIndex = i;
    175         }
    176     }
    177     SkImageInfo info = codecs->operator[](maxIndex)->getInfo();
    178 
    179     // Note that stream is owned by the embedded codec, the ico does not need
    180     // direct access to the stream.
    181     return SkNEW_ARGS(SkIcoCodec, (info, codecs.detach()));
    182 }
    183 
    184 /*
    185  * Creates an instance of the decoder
    186  * Called only by NewFromStream
    187  */
    188 SkIcoCodec::SkIcoCodec(const SkImageInfo& info,
    189                        SkTArray<SkAutoTDelete<SkCodec>, true>* codecs)
    190     : INHERITED(info, NULL)
    191     , fEmbeddedCodecs(codecs)
    192 {}
    193 
    194 /*
    195  * Chooses the best dimensions given the desired scale
    196  */
    197 SkISize SkIcoCodec::onGetScaledDimensions(float desiredScale) const {
    198     // We set the dimensions to the largest candidate image by default.
    199     // Regardless of the scale request, this is the largest image that we
    200     // will decode.
    201     if (desiredScale >= 1.0) {
    202         return this->getInfo().dimensions();
    203     }
    204 
    205     int origWidth = this->getInfo().width();
    206     int origHeight = this->getInfo().height();
    207     float desiredSize = desiredScale * origWidth * origHeight;
    208     // At least one image will have smaller error than this initial value
    209     float minError = ((float) (origWidth * origHeight)) - desiredSize + 1.0f;
    210     int32_t minIndex = -1;
    211     for (int32_t i = 0; i < fEmbeddedCodecs->count(); i++) {
    212         int width = fEmbeddedCodecs->operator[](i)->getInfo().width();
    213         int height = fEmbeddedCodecs->operator[](i)->getInfo().height();
    214         float error = SkTAbs(((float) (width * height)) - desiredSize);
    215         if (error < minError) {
    216             minError = error;
    217             minIndex = i;
    218         }
    219     }
    220     SkASSERT(minIndex >= 0);
    221 
    222     return fEmbeddedCodecs->operator[](minIndex)->getInfo().dimensions();
    223 }
    224 
    225 /*
    226  * Initiates the Ico decode
    227  */
    228 SkCodec::Result SkIcoCodec::onGetPixels(const SkImageInfo& dstInfo,
    229                                         void* dst, size_t dstRowBytes,
    230                                         const Options& opts, SkPMColor* ct,
    231                                         int* ptr) {
    232     // We return invalid scale if there is no candidate image with matching
    233     // dimensions.
    234     Result result = kInvalidScale;
    235     for (int32_t i = 0; i < fEmbeddedCodecs->count(); i++) {
    236         // If the dimensions match, try to decode
    237         if (dstInfo.dimensions() ==
    238                 fEmbeddedCodecs->operator[](i)->getInfo().dimensions()) {
    239 
    240             // Perform the decode
    241             result = fEmbeddedCodecs->operator[](i)->getPixels(dstInfo,
    242                     dst, dstRowBytes, &opts, ct, ptr);
    243 
    244             // On a fatal error, keep trying to find an image to decode
    245             if (kInvalidConversion == result || kInvalidInput == result ||
    246                     kInvalidScale == result) {
    247                 SkCodecPrintf("Warning: Attempt to decode candidate ico failed.\n");
    248                 continue;
    249             }
    250 
    251             // On success or partial success, return the result
    252             return result;
    253         }
    254     }
    255 
    256     SkCodecPrintf("Error: No matching candidate image in ico.\n");
    257     return result;
    258 }
    259