• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "SkBmpCodec.h"
9 #include "SkCodecPriv.h"
10 #include "SkColorPriv.h"
11 #include "SkData.h"
12 #include "SkIcoCodec.h"
13 #include "SkPngCodec.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  */
IsIco(const void * buffer,size_t bytesRead)21 bool SkIcoCodec::IsIco(const void* buffer, size_t bytesRead) {
22     const char icoSig[] = { '\x00', '\x00', '\x01', '\x00' };
23     const char curSig[] = { '\x00', '\x00', '\x02', '\x00' };
24     return bytesRead >= sizeof(icoSig) &&
25             (!memcmp(buffer, icoSig, sizeof(icoSig)) ||
26             !memcmp(buffer, curSig, sizeof(curSig)));
27 }
28 
NewFromStream(SkStream * stream,Result * result)29 SkCodec* SkIcoCodec::NewFromStream(SkStream* stream, Result* result) {
30     // Ensure that we do not leak the input stream
31     std::unique_ptr<SkStream> inputStream(stream);
32 
33     // Header size constants
34     static const uint32_t kIcoDirectoryBytes = 6;
35     static const uint32_t kIcoDirEntryBytes = 16;
36 
37     // Read the directory header
38     std::unique_ptr<uint8_t[]> dirBuffer(new uint8_t[kIcoDirectoryBytes]);
39     if (inputStream.get()->read(dirBuffer.get(), kIcoDirectoryBytes) !=
40             kIcoDirectoryBytes) {
41         SkCodecPrintf("Error: unable to read ico directory header.\n");
42         *result = kIncompleteInput;
43         return nullptr;
44     }
45 
46     // Process the directory header
47     const uint16_t numImages = get_short(dirBuffer.get(), 4);
48     if (0 == numImages) {
49         SkCodecPrintf("Error: No images embedded in ico.\n");
50         *result = kInvalidInput;
51         return nullptr;
52     }
53 
54     // This structure is used to represent the vital information about entries
55     // in the directory header.  We will obtain this information for each
56     // directory entry.
57     struct Entry {
58         uint32_t offset;
59         uint32_t size;
60     };
61     SkAutoFree dirEntryBuffer(sk_malloc_canfail(sizeof(Entry) * numImages));
62     if (!dirEntryBuffer) {
63         SkCodecPrintf("Error: OOM allocating ICO directory for %i images.\n",
64                       numImages);
65         *result = kInternalError;
66         return nullptr;
67     }
68     auto* directoryEntries = reinterpret_cast<Entry*>(dirEntryBuffer.get());
69 
70     // Iterate over directory entries
71     for (uint32_t i = 0; i < numImages; i++) {
72         uint8_t entryBuffer[kIcoDirEntryBytes];
73         if (inputStream->read(entryBuffer, kIcoDirEntryBytes) !=
74                 kIcoDirEntryBytes) {
75             SkCodecPrintf("Error: Dir entries truncated in ico.\n");
76             *result = kIncompleteInput;
77             return nullptr;
78         }
79 
80         // The directory entry contains information such as width, height,
81         // bits per pixel, and number of colors in the color palette.  We will
82         // ignore these fields since they are repeated in the header of the
83         // embedded image.  In the event of an inconsistency, we would always
84         // defer to the value in the embedded header anyway.
85 
86         // Specifies the size of the embedded image, including the header
87         uint32_t size = get_int(entryBuffer, 8);
88 
89         // Specifies the offset of the embedded image from the start of file.
90         // It does not indicate the start of the pixel data, but rather the
91         // start of the embedded image header.
92         uint32_t offset = get_int(entryBuffer, 12);
93 
94         // Save the vital fields
95         directoryEntries[i].offset = offset;
96         directoryEntries[i].size = size;
97     }
98 
99     // Default Result, if no valid embedded codecs are found.
100     *result = kInvalidInput;
101 
102     // It is "customary" that the embedded images will be stored in order of
103     // increasing offset.  However, the specification does not indicate that
104     // they must be stored in this order, so we will not trust that this is the
105     // case.  Here we sort the embedded images by increasing offset.
106     struct EntryLessThan {
107         bool operator() (Entry a, Entry b) const {
108             return a.offset < b.offset;
109         }
110     };
111     EntryLessThan lessThan;
112     SkTQSort(directoryEntries, &directoryEntries[numImages - 1], lessThan);
113 
114     // Now will construct a candidate codec for each of the embedded images
115     uint32_t bytesRead = kIcoDirectoryBytes + numImages * kIcoDirEntryBytes;
116     std::unique_ptr<SkTArray<std::unique_ptr<SkCodec>, true>> codecs(
117             new (SkTArray<std::unique_ptr<SkCodec>, true>)(numImages));
118     for (uint32_t i = 0; i < numImages; i++) {
119         uint32_t offset = directoryEntries[i].offset;
120         uint32_t size = directoryEntries[i].size;
121 
122         // Ensure that the offset is valid
123         if (offset < bytesRead) {
124             SkCodecPrintf("Warning: invalid ico offset.\n");
125             continue;
126         }
127 
128         // If we cannot skip, assume we have reached the end of the stream and
129         // stop trying to make codecs
130         if (inputStream.get()->skip(offset - bytesRead) != offset - bytesRead) {
131             SkCodecPrintf("Warning: could not skip to ico offset.\n");
132             break;
133         }
134         bytesRead = offset;
135 
136         // Create a new stream for the embedded codec
137         SkAutoFree buffer(sk_malloc_canfail(size));
138         if (!buffer) {
139             SkCodecPrintf("Warning: OOM trying to create embedded stream.\n");
140             break;
141         }
142 
143         if (inputStream->read(buffer.get(), size) != size) {
144             SkCodecPrintf("Warning: could not create embedded stream.\n");
145             *result = kIncompleteInput;
146             break;
147         }
148 
149         sk_sp<SkData> data(SkData::MakeFromMalloc(buffer.release(), size));
150         std::unique_ptr<SkMemoryStream> embeddedStream(new SkMemoryStream(data));
151         bytesRead += size;
152 
153         // Check if the embedded codec is bmp or png and create the codec
154         SkCodec* codec = nullptr;
155         Result dummyResult;
156         if (SkPngCodec::IsPng((const char*) data->bytes(), data->size())) {
157             codec = SkPngCodec::NewFromStream(embeddedStream.release(), &dummyResult);
158         } else {
159             codec = SkBmpCodec::NewFromIco(embeddedStream.release(), &dummyResult);
160         }
161 
162         // Save a valid codec
163         if (nullptr != codec) {
164             codecs->push_back().reset(codec);
165         }
166     }
167 
168     // Recognize if there are no valid codecs
169     if (0 == codecs->count()) {
170         SkCodecPrintf("Error: could not find any valid embedded ico codecs.\n");
171         return nullptr;
172     }
173 
174     // Use the largest codec as a "suggestion" for image info
175     size_t maxSize = 0;
176     int maxIndex = 0;
177     for (int i = 0; i < codecs->count(); i++) {
178         SkImageInfo info = codecs->operator[](i)->getInfo();
179         size_t size = info.getSafeSize(info.minRowBytes());
180 
181         if (size > maxSize) {
182             maxSize = size;
183             maxIndex = i;
184         }
185     }
186     int width = codecs->operator[](maxIndex)->getInfo().width();
187     int height = codecs->operator[](maxIndex)->getInfo().height();
188     SkEncodedInfo info = codecs->operator[](maxIndex)->getEncodedInfo();
189     SkColorSpace* colorSpace = codecs->operator[](maxIndex)->getInfo().colorSpace();
190 
191     *result = kSuccess;
192     // The original stream is no longer needed, because the embedded codecs own their
193     // own streams.
194     return new SkIcoCodec(width, height, info, codecs.release(), sk_ref_sp(colorSpace));
195 }
196 
197 /*
198  * Creates an instance of the decoder
199  * Called only by NewFromStream
200  */
SkIcoCodec(int width,int height,const SkEncodedInfo & info,SkTArray<std::unique_ptr<SkCodec>,true> * codecs,sk_sp<SkColorSpace> colorSpace)201 SkIcoCodec::SkIcoCodec(int width, int height, const SkEncodedInfo& info,
202                        SkTArray<std::unique_ptr<SkCodec>, true>* codecs,
203                        sk_sp<SkColorSpace> colorSpace)
204     // The source SkColorSpaceXform::ColorFormat will not be used. The embedded
205     // codec's will be used instead.
206     : INHERITED(width, height, info, SkColorSpaceXform::ColorFormat(), nullptr,
207                 std::move(colorSpace))
208     , fEmbeddedCodecs(codecs)
209     , fCurrScanlineCodec(nullptr)
210     , fCurrIncrementalCodec(nullptr)
211 {}
212 
213 /*
214  * Chooses the best dimensions given the desired scale
215  */
onGetScaledDimensions(float desiredScale) const216 SkISize SkIcoCodec::onGetScaledDimensions(float desiredScale) const {
217     // We set the dimensions to the largest candidate image by default.
218     // Regardless of the scale request, this is the largest image that we
219     // will decode.
220     int origWidth = this->getInfo().width();
221     int origHeight = this->getInfo().height();
222     float desiredSize = desiredScale * origWidth * origHeight;
223     // At least one image will have smaller error than this initial value
224     float minError = ((float) (origWidth * origHeight)) - desiredSize + 1.0f;
225     int32_t minIndex = -1;
226     for (int32_t i = 0; i < fEmbeddedCodecs->count(); i++) {
227         int width = fEmbeddedCodecs->operator[](i)->getInfo().width();
228         int height = fEmbeddedCodecs->operator[](i)->getInfo().height();
229         float error = SkTAbs(((float) (width * height)) - desiredSize);
230         if (error < minError) {
231             minError = error;
232             minIndex = i;
233         }
234     }
235     SkASSERT(minIndex >= 0);
236 
237     return fEmbeddedCodecs->operator[](minIndex)->getInfo().dimensions();
238 }
239 
chooseCodec(const SkISize & requestedSize,int startIndex)240 int SkIcoCodec::chooseCodec(const SkISize& requestedSize, int startIndex) {
241     SkASSERT(startIndex >= 0);
242 
243     // FIXME: Cache the index from onGetScaledDimensions?
244     for (int i = startIndex; i < fEmbeddedCodecs->count(); i++) {
245         if (fEmbeddedCodecs->operator[](i)->getInfo().dimensions() == requestedSize) {
246             return i;
247         }
248     }
249 
250     return -1;
251 }
252 
onDimensionsSupported(const SkISize & dim)253 bool SkIcoCodec::onDimensionsSupported(const SkISize& dim) {
254     return this->chooseCodec(dim, 0) >= 0;
255 }
256 
257 /*
258  * Initiates the Ico decode
259  */
onGetPixels(const SkImageInfo & dstInfo,void * dst,size_t dstRowBytes,const Options & opts,int * rowsDecoded)260 SkCodec::Result SkIcoCodec::onGetPixels(const SkImageInfo& dstInfo,
261                                         void* dst, size_t dstRowBytes,
262                                         const Options& opts,
263                                         int* rowsDecoded) {
264     if (opts.fSubset) {
265         // Subsets are not supported.
266         return kUnimplemented;
267     }
268 
269     int index = 0;
270     SkCodec::Result result = kInvalidScale;
271     while (true) {
272         index = this->chooseCodec(dstInfo.dimensions(), index);
273         if (index < 0) {
274             break;
275         }
276 
277         SkCodec* embeddedCodec = fEmbeddedCodecs->operator[](index).get();
278         result = embeddedCodec->getPixels(dstInfo, dst, dstRowBytes, &opts);
279         switch (result) {
280             case kSuccess:
281             case kIncompleteInput:
282                 // The embedded codec will handle filling incomplete images, so we will indicate
283                 // that all of the rows are initialized.
284                 *rowsDecoded = dstInfo.height();
285                 return result;
286             default:
287                 // Continue trying to find a valid embedded codec on a failed decode.
288                 break;
289         }
290 
291         index++;
292     }
293 
294     SkCodecPrintf("Error: No matching candidate image in ico.\n");
295     return result;
296 }
297 
onStartScanlineDecode(const SkImageInfo & dstInfo,const SkCodec::Options & options)298 SkCodec::Result SkIcoCodec::onStartScanlineDecode(const SkImageInfo& dstInfo,
299         const SkCodec::Options& options) {
300     int index = 0;
301     SkCodec::Result result = kInvalidScale;
302     while (true) {
303         index = this->chooseCodec(dstInfo.dimensions(), index);
304         if (index < 0) {
305             break;
306         }
307 
308         SkCodec* embeddedCodec = fEmbeddedCodecs->operator[](index).get();
309         result = embeddedCodec->startScanlineDecode(dstInfo, &options);
310         if (kSuccess == result) {
311             fCurrScanlineCodec = embeddedCodec;
312             fCurrIncrementalCodec = nullptr;
313             return result;
314         }
315 
316         index++;
317     }
318 
319     SkCodecPrintf("Error: No matching candidate image in ico.\n");
320     return result;
321 }
322 
onGetScanlines(void * dst,int count,size_t rowBytes)323 int SkIcoCodec::onGetScanlines(void* dst, int count, size_t rowBytes) {
324     SkASSERT(fCurrScanlineCodec);
325     return fCurrScanlineCodec->getScanlines(dst, count, rowBytes);
326 }
327 
onSkipScanlines(int count)328 bool SkIcoCodec::onSkipScanlines(int count) {
329     SkASSERT(fCurrScanlineCodec);
330     return fCurrScanlineCodec->skipScanlines(count);
331 }
332 
onStartIncrementalDecode(const SkImageInfo & dstInfo,void * pixels,size_t rowBytes,const SkCodec::Options & options)333 SkCodec::Result SkIcoCodec::onStartIncrementalDecode(const SkImageInfo& dstInfo,
334         void* pixels, size_t rowBytes, const SkCodec::Options& options) {
335     int index = 0;
336     while (true) {
337         index = this->chooseCodec(dstInfo.dimensions(), index);
338         if (index < 0) {
339             break;
340         }
341 
342         SkCodec* embeddedCodec = fEmbeddedCodecs->operator[](index).get();
343         switch (embeddedCodec->startIncrementalDecode(dstInfo,
344                 pixels, rowBytes, &options)) {
345             case kSuccess:
346                 fCurrIncrementalCodec = embeddedCodec;
347                 fCurrScanlineCodec = nullptr;
348                 return kSuccess;
349             case kUnimplemented:
350                 // FIXME: embeddedCodec is a BMP. If scanline decoding would work,
351                 // return kUnimplemented so that SkSampledCodec will fall through
352                 // to use the scanline decoder.
353                 // Note that calling startScanlineDecode will require an extra
354                 // rewind. The embedded codec has an SkMemoryStream, which is
355                 // cheap to rewind, though it will do extra work re-reading the
356                 // header.
357                 // Also note that we pass nullptr for Options. This is because
358                 // Options that are valid for incremental decoding may not be
359                 // valid for scanline decoding.
360                 // Once BMP supports incremental decoding this workaround can go
361                 // away.
362                 if (embeddedCodec->startScanlineDecode(dstInfo) == kSuccess) {
363                     return kUnimplemented;
364                 }
365                 // Move on to the next embedded codec.
366                 break;
367             default:
368                 break;
369         }
370 
371         index++;
372     }
373 
374     SkCodecPrintf("Error: No matching candidate image in ico.\n");
375     return kInvalidScale;
376 }
377 
onIncrementalDecode(int * rowsDecoded)378 SkCodec::Result SkIcoCodec::onIncrementalDecode(int* rowsDecoded) {
379     SkASSERT(fCurrIncrementalCodec);
380     return fCurrIncrementalCodec->incrementalDecode(rowsDecoded);
381 }
382 
onGetScanlineOrder() const383 SkCodec::SkScanlineOrder SkIcoCodec::onGetScanlineOrder() const {
384     // FIXME: This function will possibly return the wrong value if it is called
385     //        before startScanlineDecode()/startIncrementalDecode().
386     if (fCurrScanlineCodec) {
387         SkASSERT(!fCurrIncrementalCodec);
388         return fCurrScanlineCodec->getScanlineOrder();
389     }
390 
391     if (fCurrIncrementalCodec) {
392         return fCurrIncrementalCodec->getScanlineOrder();
393     }
394 
395     return INHERITED::onGetScanlineOrder();
396 }
397 
getSampler(bool createIfNecessary)398 SkSampler* SkIcoCodec::getSampler(bool createIfNecessary) {
399     if (fCurrScanlineCodec) {
400         SkASSERT(!fCurrIncrementalCodec);
401         return fCurrScanlineCodec->getSampler(createIfNecessary);
402     }
403 
404     if (fCurrIncrementalCodec) {
405         return fCurrIncrementalCodec->getSampler(createIfNecessary);
406     }
407 
408     return nullptr;
409 }
410