• 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 "src/codec/SkIcoCodec.h"
9 
10 #include "include/codec/SkIcoDecoder.h"
11 #include "include/codec/SkPngDecoder.h"
12 #include "include/core/SkData.h"
13 #include "include/core/SkImageInfo.h"
14 #include "include/core/SkRefCnt.h"
15 #include "include/core/SkStream.h"
16 #include "include/private/SkEncodedInfo.h"
17 #include "include/private/base/SkMalloc.h"
18 #include "include/private/base/SkTemplates.h"
19 #include "src/base/SkTSort.h"
20 #include "src/codec/SkBmpCodec.h"
21 #include "src/codec/SkCodecPriv.h"
22 #include "src/core/SkStreamPriv.h"
23 
24 #include "modules/skcms/skcms.h"
25 #include <cstdint>
26 #include <cstring>
27 #include <memory>
28 #include <utility>
29 
30 using namespace skia_private;
31 
32 class SkSampler;
33 
34 /*
35  * Checks the start of the stream to see if the image is an Ico or Cur
36  */
IsIco(const void * buffer,size_t bytesRead)37 bool SkIcoCodec::IsIco(const void* buffer, size_t bytesRead) {
38     const char icoSig[] = { '\x00', '\x00', '\x01', '\x00' };
39     const char curSig[] = { '\x00', '\x00', '\x02', '\x00' };
40     return bytesRead >= sizeof(icoSig) &&
41             (!memcmp(buffer, icoSig, sizeof(icoSig)) ||
42             !memcmp(buffer, curSig, sizeof(curSig)));
43 }
44 
MakeFromStream(std::unique_ptr<SkStream> stream,Result * result)45 std::unique_ptr<SkCodec> SkIcoCodec::MakeFromStream(std::unique_ptr<SkStream> stream,
46                                                     Result* result) {
47     SkASSERT(result);
48     if (!stream) {
49         *result = SkCodec::kInvalidInput;
50         return nullptr;
51     }
52     // It is helpful to have the entire stream in a contiguous buffer. In some cases,
53     // this is already the case anyway, so this method is faster. In others, this is
54     // safer than the old method, which required allocating a block of memory whose
55     // byte size is stored in the stream as a uint32_t, and may result in a large or
56     // failed allocation.
57     sk_sp<SkData> data = nullptr;
58     if (stream->getMemoryBase()) {
59         // It is safe to make without copy because we'll hold onto the stream.
60         data = SkData::MakeWithoutCopy(stream->getMemoryBase(), stream->getLength());
61     } else {
62         data = SkCopyStreamToData(stream.get());
63 
64         // If we are forced to copy the stream to a data, we can go ahead and delete the stream.
65         stream.reset(nullptr);
66     }
67 
68     // Header size constants
69     constexpr uint32_t kIcoDirectoryBytes = 6;
70     constexpr uint32_t kIcoDirEntryBytes = 16;
71 
72     // Read the directory header
73     if (data->size() < kIcoDirectoryBytes) {
74         SkCodecPrintf("Error: unable to read ico directory header.\n");
75         *result = kIncompleteInput;
76         return nullptr;
77     }
78 
79     // Process the directory header
80     const uint16_t numImages = SkCodecPriv::UnsafeGetShort(data->bytes(), 4);
81     if (0 == numImages) {
82         SkCodecPrintf("Error: No images embedded in ico.\n");
83         *result = kInvalidInput;
84         return nullptr;
85     }
86 
87     // This structure is used to represent the vital information about entries
88     // in the directory header.  We will obtain this information for each
89     // directory entry.
90     struct Entry {
91         uint32_t offset;
92         uint32_t size;
93 #ifdef ICO_CODEC_HW_HIGH_QUALITY_DECODE
94         uint16_t bitsPerPixel;
95         int width;
96         int height;
97 #endif
98     };
99     UniqueVoidPtr dirEntryBuffer(sk_malloc_canfail(sizeof(Entry) * numImages));
100     if (!dirEntryBuffer) {
101         SkCodecPrintf("Error: OOM allocating ICO directory for %i images.\n",
102                       numImages);
103         *result = kInternalError;
104         return nullptr;
105     }
106     auto* directoryEntries = reinterpret_cast<Entry*>(dirEntryBuffer.get());
107 
108     // Iterate over directory entries
109     for (uint32_t i = 0; i < numImages; i++) {
110         const uint8_t* entryBuffer = data->bytes() + kIcoDirectoryBytes + i * kIcoDirEntryBytes;
111         if (data->size() < kIcoDirectoryBytes + (i+1) * kIcoDirEntryBytes) {
112             SkCodecPrintf("Error: Dir entries truncated in ico.\n");
113             *result = kIncompleteInput;
114             return nullptr;
115         }
116 
117         // The directory entry contains information such as width, height,
118         // bits per pixel, and number of colors in the color palette.  We will
119         // ignore these fields since they are repeated in the header of the
120         // embedded image.  In the event of an inconsistency, we would always
121         // defer to the value in the embedded header anyway.
122 
123         // Specifies the size of the embedded image, including the header
124         uint32_t size = SkCodecPriv::UnsafeGetInt(entryBuffer, 8);
125 
126         // Specifies the offset of the embedded image from the start of file.
127         // It does not indicate the start of the pixel data, but rather the
128         // start of the embedded image header.
129         uint32_t offset = SkCodecPriv::UnsafeGetInt(entryBuffer, 12);
130 
131         // Save the vital fields
132         directoryEntries[i].offset = offset;
133         directoryEntries[i].size = size;
134 #ifdef ICO_CODEC_HW_HIGH_QUALITY_DECODE
135         // store bitsPerPixel, width, height and save the vital fields
136         uint16_t bitsPerPixel = SkCodecPriv::UnsafeGetShort(entryBuffer, 6);
137         // Storing them in int (instead of matching uint8_t) is so we can record
138         // dimensions of size 256 (which is what a zero byte really means)
139         static const int maxSize = 256;
140         int width = static_cast<int>(SkCodecPriv::UnsafeGetByte(entryBuffer, 0));
141         int height = static_cast<int>(SkCodecPriv::UnsafeGetByte(entryBuffer, 1));
142         if (width == 0) {
143             width = maxSize;
144         }
145         if (height == 0) {
146             height = maxSize;
147         }
148 
149         directoryEntries[i].bitsPerPixel = bitsPerPixel;
150         directoryEntries[i].width = width;
151         directoryEntries[i].height = height;
152 #endif
153     }
154 
155     // Default Result, if no valid embedded codecs are found.
156     *result = kInvalidInput;
157 
158     // It is "customary" that the embedded images will be stored in order of
159     // increasing offset.  However, the specification does not indicate that
160     // they must be stored in this order, so we will not trust that this is the
161     // case.  Here we sort the embedded images by increasing offset.
162 #ifdef ICO_CODEC_HW_HIGH_QUALITY_DECODE
163     struct EntryGreaterThan {
164         bool operator()(Entry a, Entry b) const {
165             return (a.width * a.height == b.width * b.height) ? (a.bitsPerPixel > b.bitsPerPixel) :
166                 (a.width * a.height > b.width * b.height);
167         }
168     };
169     EntryGreaterThan greaterThan;
170     SkTQSort(directoryEntries, directoryEntries + numImages, greaterThan);
171 #else
172     struct EntryLessThan {
173         bool operator() (Entry a, Entry b) const {
174             return a.offset < b.offset;
175         }
176     };
177     EntryLessThan lessThan;
178     SkTQSort(directoryEntries, directoryEntries + numImages, lessThan);
179 #endif
180 
181     // Now will construct a candidate codec for each of the embedded images
182     uint32_t bytesRead = kIcoDirectoryBytes + numImages * kIcoDirEntryBytes;
183     auto codecs = std::make_unique<TArray<std::unique_ptr<SkCodec>>>(numImages);
184     for (uint32_t i = 0; i < numImages; i++) {
185         uint32_t offset = directoryEntries[i].offset;
186         uint32_t size = directoryEntries[i].size;
187 
188         // Ensure that the offset is valid
189         if (offset < bytesRead) {
190             SkCodecPrintf("Warning: invalid ico offset.\n");
191             continue;
192         }
193 
194         // If we cannot skip, assume we have reached the end of the stream and
195         // stop trying to make codecs
196         if (offset >= data->size()) {
197             SkCodecPrintf("Warning: could not skip to ico offset.\n");
198             break;
199         }
200         bytesRead = offset;
201 
202         if (offset + size > data->size()) {
203             SkCodecPrintf("Warning: could not create embedded stream.\n");
204             *result = kIncompleteInput;
205             break;
206         }
207 
208         sk_sp<SkData> embeddedData(SkData::MakeSubset(data.get(), offset, size));
209         auto embeddedStream = SkMemoryStream::Make(embeddedData);
210         bytesRead += size;
211 
212         // Check if the embedded codec is bmp or png and create the codec
213         std::unique_ptr<SkCodec> codec;
214         Result ignoredResult;
215         if (SkPngDecoder::IsPng(embeddedData->bytes(), embeddedData->size())) {
216             codec = SkPngDecoder::Decode(std::move(embeddedStream), &ignoredResult);
217         } else {
218             codec = SkBmpCodec::MakeFromIco(std::move(embeddedStream), &ignoredResult);
219         }
220 
221         if (nullptr != codec) {
222             codecs->push_back(std::move(codec));
223         }
224     }
225 
226     if (codecs->empty()) {
227         SkCodecPrintf("Error: could not find any valid embedded ico codecs.\n");
228         return nullptr;
229     }
230 
231     // Use the largest codec as a "suggestion" for image info
232     size_t maxSize = 0;
233     int maxIndex = 0;
234     for (int i = 0; i < codecs->size(); i++) {
235         SkImageInfo info = codecs->at(i)->getInfo();
236         size_t size = info.computeMinByteSize();
237 
238         if (size > maxSize) {
239             maxSize = size;
240             maxIndex = i;
241         }
242     }
243 
244     auto maxInfo = codecs->at(maxIndex)->getEncodedInfo().copy();
245 
246     *result = kSuccess;
247     return std::unique_ptr<SkCodec>(
248             new SkIcoCodec(std::move(maxInfo), std::move(stream), std::move(codecs)));
249 }
250 
SkIcoCodec(SkEncodedInfo && info,std::unique_ptr<SkStream> stream,std::unique_ptr<TArray<std::unique_ptr<SkCodec>>> codecs)251 SkIcoCodec::SkIcoCodec(SkEncodedInfo&& info,
252                        std::unique_ptr<SkStream> stream,
253                        std::unique_ptr<TArray<std::unique_ptr<SkCodec>>> codecs)
254         // The source skcms_PixelFormat will not be used. The embedded
255         // codec's will be used instead.
256         : INHERITED(std::move(info), skcms_PixelFormat(), std::move(stream))
257         , fEmbeddedCodecs(std::move(codecs))
258         , fCurrCodec(nullptr) {}
259 
260 /*
261  * Chooses the best dimensions given the desired scale
262  */
onGetScaledDimensions(float desiredScale) const263 SkISize SkIcoCodec::onGetScaledDimensions(float desiredScale) const {
264     // We set the dimensions to the largest candidate image by default.
265     // Regardless of the scale request, this is the largest image that we
266     // will decode.
267     int origWidth = this->dimensions().width();
268     int origHeight = this->dimensions().height();
269 #ifdef ICO_CODEC_HW_HIGH_QUALITY_DECODE
270     // desiredScale is max(desireWidth/origWidth, desireHeight/origHeight)
271     float desiredSize = desiredScale * origWidth * desiredScale * origHeight;
272 #else
273     float desiredSize = desiredScale * origWidth * origHeight;
274 #endif
275     // At least one image will have smaller error than this initial value
276     float minError = std::numeric_limits<float>::max();
277     int32_t minIndex = -1;
278     for (int32_t i = 0; i < fEmbeddedCodecs->size(); i++) {
279         auto dimensions = fEmbeddedCodecs->at(i)->dimensions();
280         int width = dimensions.width();
281         int height = dimensions.height();
282         float error = SkTAbs(((float) (width * height)) - desiredSize);
283         if (error < minError) {
284             minError = error;
285             minIndex = i;
286         }
287     }
288     SkASSERT(minIndex >= 0);
289 
290     return fEmbeddedCodecs->at(minIndex)->dimensions();
291 }
292 
chooseCodec(const SkISize & requestedSize,int startIndex)293 int SkIcoCodec::chooseCodec(const SkISize& requestedSize, int startIndex) {
294     SkASSERT(startIndex >= 0);
295 
296     // FIXME: Cache the index from onGetScaledDimensions?
297     for (int i = startIndex; i < fEmbeddedCodecs->size(); i++) {
298         if (fEmbeddedCodecs->at(i)->dimensions() == requestedSize) {
299             return i;
300         }
301     }
302 
303     return -1;
304 }
305 
onDimensionsSupported(const SkISize & dim)306 bool SkIcoCodec::onDimensionsSupported(const SkISize& dim) {
307     return this->chooseCodec(dim, 0) >= 0;
308 }
309 
310 /*
311  * Initiates the Ico decode
312  */
onGetPixels(const SkImageInfo & dstInfo,void * dst,size_t dstRowBytes,const Options & opts,int * rowsDecoded)313 SkCodec::Result SkIcoCodec::onGetPixels(const SkImageInfo& dstInfo,
314                                         void* dst, size_t dstRowBytes,
315                                         const Options& opts,
316                                         int* rowsDecoded) {
317     if (opts.fSubset) {
318         // Subsets are not supported.
319         return kUnimplemented;
320     }
321 
322     int index = 0;
323     SkCodec::Result result = kInvalidScale;
324     while (true) {
325         index = this->chooseCodec(dstInfo.dimensions(), index);
326         if (index < 0) {
327             break;
328         }
329 
330         SkCodec* embeddedCodec = fEmbeddedCodecs->at(index).get();
331         result = embeddedCodec->getPixels(dstInfo, dst, dstRowBytes, &opts);
332         switch (result) {
333             case kSuccess:
334             case kIncompleteInput:
335                 // The embedded codec will handle filling incomplete images, so we will indicate
336                 // that all of the rows are initialized.
337                 *rowsDecoded = dstInfo.height();
338                 return result;
339             default:
340                 // Continue trying to find a valid embedded codec on a failed decode.
341                 break;
342         }
343 
344         index++;
345     }
346 
347     SkCodecPrintf("Error: No matching candidate image in ico.\n");
348     return result;
349 }
350 
onStartScanlineDecode(const SkImageInfo & dstInfo,const SkCodec::Options & options)351 SkCodec::Result SkIcoCodec::onStartScanlineDecode(const SkImageInfo& dstInfo,
352         const SkCodec::Options& options) {
353     int index = 0;
354     SkCodec::Result result = kInvalidScale;
355     while (true) {
356         index = this->chooseCodec(dstInfo.dimensions(), index);
357         if (index < 0) {
358             break;
359         }
360 
361         SkCodec* embeddedCodec = fEmbeddedCodecs->at(index).get();
362         result = embeddedCodec->startScanlineDecode(dstInfo, &options);
363         if (kSuccess == result) {
364             fCurrCodec = embeddedCodec;
365             return result;
366         }
367 
368         index++;
369     }
370 
371     SkCodecPrintf("Error: No matching candidate image in ico.\n");
372     return result;
373 }
374 
onGetScanlines(void * dst,int count,size_t rowBytes)375 int SkIcoCodec::onGetScanlines(void* dst, int count, size_t rowBytes) {
376     SkASSERT(fCurrCodec);
377     return fCurrCodec->getScanlines(dst, count, rowBytes);
378 }
379 
onSkipScanlines(int count)380 bool SkIcoCodec::onSkipScanlines(int count) {
381     SkASSERT(fCurrCodec);
382     return fCurrCodec->skipScanlines(count);
383 }
384 
onStartIncrementalDecode(const SkImageInfo & dstInfo,void * pixels,size_t rowBytes,const SkCodec::Options & options)385 SkCodec::Result SkIcoCodec::onStartIncrementalDecode(const SkImageInfo& dstInfo,
386         void* pixels, size_t rowBytes, const SkCodec::Options& options) {
387     int index = 0;
388     while (true) {
389         index = this->chooseCodec(dstInfo.dimensions(), index);
390         if (index < 0) {
391             break;
392         }
393 
394         SkCodec* embeddedCodec = fEmbeddedCodecs->at(index).get();
395         switch (embeddedCodec->startIncrementalDecode(dstInfo,
396                 pixels, rowBytes, &options)) {
397             case kSuccess:
398                 fCurrCodec = embeddedCodec;
399                 return kSuccess;
400             case kUnimplemented:
401                 // FIXME: embeddedCodec is a BMP. If scanline decoding would work,
402                 // return kUnimplemented so that SkSampledCodec will fall through
403                 // to use the scanline decoder.
404                 // Note that calling startScanlineDecode will require an extra
405                 // rewind. The embedded codec has an SkMemoryStream, which is
406                 // cheap to rewind, though it will do extra work re-reading the
407                 // header.
408                 // Also note that we pass nullptr for Options. This is because
409                 // Options that are valid for incremental decoding may not be
410                 // valid for scanline decoding.
411                 // Once BMP supports incremental decoding this workaround can go
412                 // away.
413                 if (embeddedCodec->startScanlineDecode(dstInfo) == kSuccess) {
414                     return kUnimplemented;
415                 }
416                 // Move on to the next embedded codec.
417                 break;
418             default:
419                 break;
420         }
421 
422         index++;
423     }
424 
425     SkCodecPrintf("Error: No matching candidate image in ico.\n");
426     return kInvalidScale;
427 }
428 
onIncrementalDecode(int * rowsDecoded)429 SkCodec::Result SkIcoCodec::onIncrementalDecode(int* rowsDecoded) {
430     SkASSERT(fCurrCodec);
431     return fCurrCodec->incrementalDecode(rowsDecoded);
432 }
433 
onGetScanlineOrder() const434 SkCodec::SkScanlineOrder SkIcoCodec::onGetScanlineOrder() const {
435     // FIXME: This function will possibly return the wrong value if it is called
436     //        before startScanlineDecode()/startIncrementalDecode().
437     if (fCurrCodec) {
438         return fCurrCodec->getScanlineOrder();
439     }
440 
441     return INHERITED::onGetScanlineOrder();
442 }
443 
getSampler(bool createIfNecessary)444 SkSampler* SkIcoCodec::getSampler(bool createIfNecessary) {
445     if (fCurrCodec) {
446         return fCurrCodec->getSampler(createIfNecessary);
447     }
448 
449     return nullptr;
450 }
451 
452 namespace SkIcoDecoder {
IsIco(const void * data,size_t len)453 bool IsIco(const void* data, size_t len) {
454     return SkIcoCodec::IsIco(data, len);
455 }
456 
Decode(std::unique_ptr<SkStream> stream,SkCodec::Result * outResult,SkCodecs::DecodeContext)457 std::unique_ptr<SkCodec> Decode(std::unique_ptr<SkStream> stream,
458                                 SkCodec::Result* outResult,
459                                 SkCodecs::DecodeContext) {
460     SkCodec::Result resultStorage;
461     if (!outResult) {
462         outResult = &resultStorage;
463     }
464     return SkIcoCodec::MakeFromStream(std::move(stream), outResult);
465 }
466 
Decode(sk_sp<SkData> data,SkCodec::Result * outResult,SkCodecs::DecodeContext)467 std::unique_ptr<SkCodec> Decode(sk_sp<SkData> data,
468                                 SkCodec::Result* outResult,
469                                 SkCodecs::DecodeContext) {
470     if (!data) {
471         if (outResult) {
472             *outResult = SkCodec::kInvalidInput;
473         }
474         return nullptr;
475     }
476     return Decode(SkMemoryStream::Make(std::move(data)), outResult, nullptr);
477 }
478 }  // namespace SkIcoDecoder
479 
480