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