xref: /aosp_15_r20/external/skia/src/codec/SkIcoCodec.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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 = get_short(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     };
94     UniqueVoidPtr dirEntryBuffer(sk_malloc_canfail(sizeof(Entry) * numImages));
95     if (!dirEntryBuffer) {
96         SkCodecPrintf("Error: OOM allocating ICO directory for %i images.\n",
97                       numImages);
98         *result = kInternalError;
99         return nullptr;
100     }
101     auto* directoryEntries = reinterpret_cast<Entry*>(dirEntryBuffer.get());
102 
103     // Iterate over directory entries
104     for (uint32_t i = 0; i < numImages; i++) {
105         const uint8_t* entryBuffer = data->bytes() + kIcoDirectoryBytes + i * kIcoDirEntryBytes;
106         if (data->size() < kIcoDirectoryBytes + (i+1) * kIcoDirEntryBytes) {
107             SkCodecPrintf("Error: Dir entries truncated in ico.\n");
108             *result = kIncompleteInput;
109             return nullptr;
110         }
111 
112         // The directory entry contains information such as width, height,
113         // bits per pixel, and number of colors in the color palette.  We will
114         // ignore these fields since they are repeated in the header of the
115         // embedded image.  In the event of an inconsistency, we would always
116         // defer to the value in the embedded header anyway.
117 
118         // Specifies the size of the embedded image, including the header
119         uint32_t size = get_int(entryBuffer, 8);
120 
121         // Specifies the offset of the embedded image from the start of file.
122         // It does not indicate the start of the pixel data, but rather the
123         // start of the embedded image header.
124         uint32_t offset = get_int(entryBuffer, 12);
125 
126         // Save the vital fields
127         directoryEntries[i].offset = offset;
128         directoryEntries[i].size = size;
129     }
130 
131     // Default Result, if no valid embedded codecs are found.
132     *result = kInvalidInput;
133 
134     // It is "customary" that the embedded images will be stored in order of
135     // increasing offset.  However, the specification does not indicate that
136     // they must be stored in this order, so we will not trust that this is the
137     // case.  Here we sort the embedded images by increasing offset.
138     struct EntryLessThan {
139         bool operator() (Entry a, Entry b) const {
140             return a.offset < b.offset;
141         }
142     };
143     EntryLessThan lessThan;
144     SkTQSort(directoryEntries, directoryEntries + numImages, lessThan);
145 
146     // Now will construct a candidate codec for each of the embedded images
147     uint32_t bytesRead = kIcoDirectoryBytes + numImages * kIcoDirEntryBytes;
148     auto codecs = std::make_unique<TArray<std::unique_ptr<SkCodec>>>(numImages);
149     for (uint32_t i = 0; i < numImages; i++) {
150         uint32_t offset = directoryEntries[i].offset;
151         uint32_t size = directoryEntries[i].size;
152 
153         // Ensure that the offset is valid
154         if (offset < bytesRead) {
155             SkCodecPrintf("Warning: invalid ico offset.\n");
156             continue;
157         }
158 
159         // If we cannot skip, assume we have reached the end of the stream and
160         // stop trying to make codecs
161         if (offset >= data->size()) {
162             SkCodecPrintf("Warning: could not skip to ico offset.\n");
163             break;
164         }
165         bytesRead = offset;
166 
167         if (offset + size > data->size()) {
168             SkCodecPrintf("Warning: could not create embedded stream.\n");
169             *result = kIncompleteInput;
170             break;
171         }
172 
173         sk_sp<SkData> embeddedData(SkData::MakeSubset(data.get(), offset, size));
174         auto embeddedStream = SkMemoryStream::Make(embeddedData);
175         bytesRead += size;
176 
177         // Check if the embedded codec is bmp or png and create the codec
178         std::unique_ptr<SkCodec> codec;
179         Result ignoredResult;
180         if (SkPngDecoder::IsPng(embeddedData->bytes(), embeddedData->size())) {
181             codec = SkPngDecoder::Decode(std::move(embeddedStream), &ignoredResult);
182         } else {
183             codec = SkBmpCodec::MakeFromIco(std::move(embeddedStream), &ignoredResult);
184         }
185 
186         if (nullptr != codec) {
187             codecs->push_back(std::move(codec));
188         }
189     }
190 
191     if (codecs->empty()) {
192         SkCodecPrintf("Error: could not find any valid embedded ico codecs.\n");
193         return nullptr;
194     }
195 
196     // Use the largest codec as a "suggestion" for image info
197     size_t maxSize = 0;
198     int maxIndex = 0;
199     for (int i = 0; i < codecs->size(); i++) {
200         SkImageInfo info = codecs->at(i)->getInfo();
201         size_t size = info.computeMinByteSize();
202 
203         if (size > maxSize) {
204             maxSize = size;
205             maxIndex = i;
206         }
207     }
208 
209     auto maxInfo = codecs->at(maxIndex)->getEncodedInfo().copy();
210 
211     *result = kSuccess;
212     return std::unique_ptr<SkCodec>(
213             new SkIcoCodec(std::move(maxInfo), std::move(stream), std::move(codecs)));
214 }
215 
SkIcoCodec(SkEncodedInfo && info,std::unique_ptr<SkStream> stream,std::unique_ptr<TArray<std::unique_ptr<SkCodec>>> codecs)216 SkIcoCodec::SkIcoCodec(SkEncodedInfo&& info,
217                        std::unique_ptr<SkStream> stream,
218                        std::unique_ptr<TArray<std::unique_ptr<SkCodec>>> codecs)
219         // The source skcms_PixelFormat will not be used. The embedded
220         // codec's will be used instead.
221         : INHERITED(std::move(info), skcms_PixelFormat(), std::move(stream))
222         , fEmbeddedCodecs(std::move(codecs))
223         , fCurrCodec(nullptr) {}
224 
225 /*
226  * Chooses the best dimensions given the desired scale
227  */
onGetScaledDimensions(float desiredScale) const228 SkISize SkIcoCodec::onGetScaledDimensions(float desiredScale) const {
229     // We set the dimensions to the largest candidate image by default.
230     // Regardless of the scale request, this is the largest image that we
231     // will decode.
232     int origWidth = this->dimensions().width();
233     int origHeight = this->dimensions().height();
234     float desiredSize = desiredScale * origWidth * origHeight;
235     // At least one image will have smaller error than this initial value
236     float minError = ((float) (origWidth * origHeight)) - desiredSize + 1.0f;
237     int32_t minIndex = -1;
238     for (int32_t i = 0; i < fEmbeddedCodecs->size(); i++) {
239         auto dimensions = fEmbeddedCodecs->at(i)->dimensions();
240         int width = dimensions.width();
241         int height = dimensions.height();
242         float error = SkTAbs(((float) (width * height)) - desiredSize);
243         if (error < minError) {
244             minError = error;
245             minIndex = i;
246         }
247     }
248     SkASSERT(minIndex >= 0);
249 
250     return fEmbeddedCodecs->at(minIndex)->dimensions();
251 }
252 
chooseCodec(const SkISize & requestedSize,int startIndex)253 int SkIcoCodec::chooseCodec(const SkISize& requestedSize, int startIndex) {
254     SkASSERT(startIndex >= 0);
255 
256     // FIXME: Cache the index from onGetScaledDimensions?
257     for (int i = startIndex; i < fEmbeddedCodecs->size(); i++) {
258         if (fEmbeddedCodecs->at(i)->dimensions() == requestedSize) {
259             return i;
260         }
261     }
262 
263     return -1;
264 }
265 
onDimensionsSupported(const SkISize & dim)266 bool SkIcoCodec::onDimensionsSupported(const SkISize& dim) {
267     return this->chooseCodec(dim, 0) >= 0;
268 }
269 
270 /*
271  * Initiates the Ico decode
272  */
onGetPixels(const SkImageInfo & dstInfo,void * dst,size_t dstRowBytes,const Options & opts,int * rowsDecoded)273 SkCodec::Result SkIcoCodec::onGetPixels(const SkImageInfo& dstInfo,
274                                         void* dst, size_t dstRowBytes,
275                                         const Options& opts,
276                                         int* rowsDecoded) {
277     if (opts.fSubset) {
278         // Subsets are not supported.
279         return kUnimplemented;
280     }
281 
282     int index = 0;
283     SkCodec::Result result = kInvalidScale;
284     while (true) {
285         index = this->chooseCodec(dstInfo.dimensions(), index);
286         if (index < 0) {
287             break;
288         }
289 
290         SkCodec* embeddedCodec = fEmbeddedCodecs->at(index).get();
291         result = embeddedCodec->getPixels(dstInfo, dst, dstRowBytes, &opts);
292         switch (result) {
293             case kSuccess:
294             case kIncompleteInput:
295                 // The embedded codec will handle filling incomplete images, so we will indicate
296                 // that all of the rows are initialized.
297                 *rowsDecoded = dstInfo.height();
298                 return result;
299             default:
300                 // Continue trying to find a valid embedded codec on a failed decode.
301                 break;
302         }
303 
304         index++;
305     }
306 
307     SkCodecPrintf("Error: No matching candidate image in ico.\n");
308     return result;
309 }
310 
onStartScanlineDecode(const SkImageInfo & dstInfo,const SkCodec::Options & options)311 SkCodec::Result SkIcoCodec::onStartScanlineDecode(const SkImageInfo& dstInfo,
312         const SkCodec::Options& options) {
313     int index = 0;
314     SkCodec::Result result = kInvalidScale;
315     while (true) {
316         index = this->chooseCodec(dstInfo.dimensions(), index);
317         if (index < 0) {
318             break;
319         }
320 
321         SkCodec* embeddedCodec = fEmbeddedCodecs->at(index).get();
322         result = embeddedCodec->startScanlineDecode(dstInfo, &options);
323         if (kSuccess == result) {
324             fCurrCodec = embeddedCodec;
325             return result;
326         }
327 
328         index++;
329     }
330 
331     SkCodecPrintf("Error: No matching candidate image in ico.\n");
332     return result;
333 }
334 
onGetScanlines(void * dst,int count,size_t rowBytes)335 int SkIcoCodec::onGetScanlines(void* dst, int count, size_t rowBytes) {
336     SkASSERT(fCurrCodec);
337     return fCurrCodec->getScanlines(dst, count, rowBytes);
338 }
339 
onSkipScanlines(int count)340 bool SkIcoCodec::onSkipScanlines(int count) {
341     SkASSERT(fCurrCodec);
342     return fCurrCodec->skipScanlines(count);
343 }
344 
onStartIncrementalDecode(const SkImageInfo & dstInfo,void * pixels,size_t rowBytes,const SkCodec::Options & options)345 SkCodec::Result SkIcoCodec::onStartIncrementalDecode(const SkImageInfo& dstInfo,
346         void* pixels, size_t rowBytes, const SkCodec::Options& options) {
347     int index = 0;
348     while (true) {
349         index = this->chooseCodec(dstInfo.dimensions(), index);
350         if (index < 0) {
351             break;
352         }
353 
354         SkCodec* embeddedCodec = fEmbeddedCodecs->at(index).get();
355         switch (embeddedCodec->startIncrementalDecode(dstInfo,
356                 pixels, rowBytes, &options)) {
357             case kSuccess:
358                 fCurrCodec = embeddedCodec;
359                 return kSuccess;
360             case kUnimplemented:
361                 // FIXME: embeddedCodec is a BMP. If scanline decoding would work,
362                 // return kUnimplemented so that SkSampledCodec will fall through
363                 // to use the scanline decoder.
364                 // Note that calling startScanlineDecode will require an extra
365                 // rewind. The embedded codec has an SkMemoryStream, which is
366                 // cheap to rewind, though it will do extra work re-reading the
367                 // header.
368                 // Also note that we pass nullptr for Options. This is because
369                 // Options that are valid for incremental decoding may not be
370                 // valid for scanline decoding.
371                 // Once BMP supports incremental decoding this workaround can go
372                 // away.
373                 if (embeddedCodec->startScanlineDecode(dstInfo) == kSuccess) {
374                     return kUnimplemented;
375                 }
376                 // Move on to the next embedded codec.
377                 break;
378             default:
379                 break;
380         }
381 
382         index++;
383     }
384 
385     SkCodecPrintf("Error: No matching candidate image in ico.\n");
386     return kInvalidScale;
387 }
388 
onIncrementalDecode(int * rowsDecoded)389 SkCodec::Result SkIcoCodec::onIncrementalDecode(int* rowsDecoded) {
390     SkASSERT(fCurrCodec);
391     return fCurrCodec->incrementalDecode(rowsDecoded);
392 }
393 
onGetScanlineOrder() const394 SkCodec::SkScanlineOrder SkIcoCodec::onGetScanlineOrder() const {
395     // FIXME: This function will possibly return the wrong value if it is called
396     //        before startScanlineDecode()/startIncrementalDecode().
397     if (fCurrCodec) {
398         return fCurrCodec->getScanlineOrder();
399     }
400 
401     return INHERITED::onGetScanlineOrder();
402 }
403 
getSampler(bool createIfNecessary)404 SkSampler* SkIcoCodec::getSampler(bool createIfNecessary) {
405     if (fCurrCodec) {
406         return fCurrCodec->getSampler(createIfNecessary);
407     }
408 
409     return nullptr;
410 }
411 
412 namespace SkIcoDecoder {
IsIco(const void * data,size_t len)413 bool IsIco(const void* data, size_t len) {
414     return SkIcoCodec::IsIco(data, len);
415 }
416 
Decode(std::unique_ptr<SkStream> stream,SkCodec::Result * outResult,SkCodecs::DecodeContext)417 std::unique_ptr<SkCodec> Decode(std::unique_ptr<SkStream> stream,
418                                 SkCodec::Result* outResult,
419                                 SkCodecs::DecodeContext) {
420     SkCodec::Result resultStorage;
421     if (!outResult) {
422         outResult = &resultStorage;
423     }
424     return SkIcoCodec::MakeFromStream(std::move(stream), outResult);
425 }
426 
Decode(sk_sp<SkData> data,SkCodec::Result * outResult,SkCodecs::DecodeContext)427 std::unique_ptr<SkCodec> Decode(sk_sp<SkData> data,
428                                 SkCodec::Result* outResult,
429                                 SkCodecs::DecodeContext) {
430     if (!data) {
431         if (outResult) {
432             *outResult = SkCodec::kInvalidInput;
433         }
434         return nullptr;
435     }
436     return Decode(SkMemoryStream::Make(std::move(data)), outResult, nullptr);
437 }
438 }  // namespace SkIcoDecoder
439 
440