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