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