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 "SkCodec_libbmp.h" 9 #include "SkCodec_libico.h" 10 #include "SkCodec_libpng.h" 11 #include "SkCodecPriv.h" 12 #include "SkColorPriv.h" 13 #include "SkData.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 */ 21 bool SkIcoCodec::IsIco(SkStream* stream) { 22 const char icoSig[] = { '\x00', '\x00', '\x01', '\x00' }; 23 const char curSig[] = { '\x00', '\x00', '\x02', '\x00' }; 24 char buffer[sizeof(icoSig)]; 25 return stream->read(buffer, sizeof(icoSig)) == sizeof(icoSig) && 26 (!memcmp(buffer, icoSig, sizeof(icoSig)) || 27 !memcmp(buffer, curSig, sizeof(curSig))); 28 } 29 30 /* 31 * Assumes IsIco was called and returned true 32 * Creates an Ico decoder 33 * Reads enough of the stream to determine the image format 34 */ 35 SkCodec* SkIcoCodec::NewFromStream(SkStream* stream) { 36 // Ensure that we do not leak the input stream 37 SkAutoTDelete<SkStream> inputStream(stream); 38 39 // Header size constants 40 static const uint32_t kIcoDirectoryBytes = 6; 41 static const uint32_t kIcoDirEntryBytes = 16; 42 43 // Read the directory header 44 SkAutoTDeleteArray<uint8_t> dirBuffer( 45 SkNEW_ARRAY(uint8_t, kIcoDirectoryBytes)); 46 if (inputStream.get()->read(dirBuffer.get(), kIcoDirectoryBytes) != 47 kIcoDirectoryBytes) { 48 SkCodecPrintf("Error: unable to read ico directory header.\n"); 49 return NULL; 50 } 51 52 // Process the directory header 53 const uint16_t numImages = get_short(dirBuffer.get(), 4); 54 if (0 == numImages) { 55 SkCodecPrintf("Error: No images embedded in ico.\n"); 56 return NULL; 57 } 58 59 // Ensure that we can read all of indicated directory entries 60 SkAutoTDeleteArray<uint8_t> entryBuffer( 61 SkNEW_ARRAY(uint8_t, numImages*kIcoDirEntryBytes)); 62 if (inputStream.get()->read(entryBuffer.get(), numImages*kIcoDirEntryBytes) != 63 numImages*kIcoDirEntryBytes) { 64 SkCodecPrintf("Error: unable to read ico directory entries.\n"); 65 return NULL; 66 } 67 68 // This structure is used to represent the vital information about entries 69 // in the directory header. We will obtain this information for each 70 // directory entry. 71 struct Entry { 72 uint32_t offset; 73 uint32_t size; 74 }; 75 SkAutoTDeleteArray<Entry> directoryEntries(SkNEW_ARRAY(Entry, numImages)); 76 77 // Iterate over directory entries 78 for (uint32_t i = 0; i < numImages; i++) { 79 // The directory entry contains information such as width, height, 80 // bits per pixel, and number of colors in the color palette. We will 81 // ignore these fields since they are repeated in the header of the 82 // embedded image. In the event of an inconsistency, we would always 83 // defer to the value in the embedded header anyway. 84 85 // Specifies the size of the embedded image, including the header 86 uint32_t size = get_int(entryBuffer.get(), 8 + i*kIcoDirEntryBytes); 87 88 // Specifies the offset of the embedded image from the start of file. 89 // It does not indicate the start of the pixel data, but rather the 90 // start of the embedded image header. 91 uint32_t offset = get_int(entryBuffer.get(), 12 + i*kIcoDirEntryBytes); 92 93 // Save the vital fields 94 directoryEntries.get()[i].offset = offset; 95 directoryEntries.get()[i].size = size; 96 } 97 98 // It is "customary" that the embedded images will be stored in order of 99 // increasing offset. However, the specification does not indicate that 100 // they must be stored in this order, so we will not trust that this is the 101 // case. Here we sort the embedded images by increasing offset. 102 struct EntryLessThan { 103 bool operator() (Entry a, Entry b) const { 104 return a.offset < b.offset; 105 } 106 }; 107 EntryLessThan lessThan; 108 SkTQSort(directoryEntries.get(), directoryEntries.get() + numImages - 1, 109 lessThan); 110 111 // Now will construct a candidate codec for each of the embedded images 112 uint32_t bytesRead = kIcoDirectoryBytes + numImages * kIcoDirEntryBytes; 113 SkAutoTDelete<SkTArray<SkAutoTDelete<SkCodec>, true>> codecs( 114 SkNEW_ARGS((SkTArray<SkAutoTDelete<SkCodec>, true>), (numImages))); 115 for (uint32_t i = 0; i < numImages; i++) { 116 uint32_t offset = directoryEntries.get()[i].offset; 117 uint32_t size = directoryEntries.get()[i].size; 118 119 // Ensure that the offset is valid 120 if (offset < bytesRead) { 121 SkCodecPrintf("Warning: invalid ico offset.\n"); 122 continue; 123 } 124 125 // If we cannot skip, assume we have reached the end of the stream and 126 // stop trying to make codecs 127 if (inputStream.get()->skip(offset - bytesRead) != offset - bytesRead) { 128 SkCodecPrintf("Warning: could not skip to ico offset.\n"); 129 break; 130 } 131 bytesRead = offset; 132 133 // Create a new stream for the embedded codec 134 SkAutoTUnref<SkData> data( 135 SkData::NewFromStream(inputStream.get(), size)); 136 if (NULL == data.get()) { 137 SkCodecPrintf("Warning: could not create embedded stream.\n"); 138 break; 139 } 140 SkAutoTDelete<SkMemoryStream> 141 embeddedStream(SkNEW_ARGS(SkMemoryStream, (data.get()))); 142 bytesRead += size; 143 144 // Check if the embedded codec is bmp or png and create the codec 145 const bool isPng = SkPngCodec::IsPng(embeddedStream); 146 SkAssertResult(embeddedStream->rewind()); 147 SkCodec* codec = NULL; 148 if (isPng) { 149 codec = SkPngCodec::NewFromStream(embeddedStream.detach()); 150 } else { 151 codec = SkBmpCodec::NewFromIco(embeddedStream.detach()); 152 } 153 154 // Save a valid codec 155 if (NULL != 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 NULL; 164 } 165 166 // Use the largest codec as a "suggestion" for image info 167 uint32_t maxSize = 0; 168 uint32_t maxIndex = 0; 169 for (int32_t i = 0; i < codecs->count(); i++) { 170 SkImageInfo info = codecs->operator[](i)->getInfo(); 171 uint32_t size = info.width() * info.height(); 172 if (size > maxSize) { 173 maxSize = size; 174 maxIndex = i; 175 } 176 } 177 SkImageInfo info = codecs->operator[](maxIndex)->getInfo(); 178 179 // Note that stream is owned by the embedded codec, the ico does not need 180 // direct access to the stream. 181 return SkNEW_ARGS(SkIcoCodec, (info, codecs.detach())); 182 } 183 184 /* 185 * Creates an instance of the decoder 186 * Called only by NewFromStream 187 */ 188 SkIcoCodec::SkIcoCodec(const SkImageInfo& info, 189 SkTArray<SkAutoTDelete<SkCodec>, true>* codecs) 190 : INHERITED(info, NULL) 191 , fEmbeddedCodecs(codecs) 192 {} 193 194 /* 195 * Chooses the best dimensions given the desired scale 196 */ 197 SkISize SkIcoCodec::onGetScaledDimensions(float desiredScale) const { 198 // We set the dimensions to the largest candidate image by default. 199 // Regardless of the scale request, this is the largest image that we 200 // will decode. 201 if (desiredScale >= 1.0) { 202 return this->getInfo().dimensions(); 203 } 204 205 int origWidth = this->getInfo().width(); 206 int origHeight = this->getInfo().height(); 207 float desiredSize = desiredScale * origWidth * origHeight; 208 // At least one image will have smaller error than this initial value 209 float minError = ((float) (origWidth * origHeight)) - desiredSize + 1.0f; 210 int32_t minIndex = -1; 211 for (int32_t i = 0; i < fEmbeddedCodecs->count(); i++) { 212 int width = fEmbeddedCodecs->operator[](i)->getInfo().width(); 213 int height = fEmbeddedCodecs->operator[](i)->getInfo().height(); 214 float error = SkTAbs(((float) (width * height)) - desiredSize); 215 if (error < minError) { 216 minError = error; 217 minIndex = i; 218 } 219 } 220 SkASSERT(minIndex >= 0); 221 222 return fEmbeddedCodecs->operator[](minIndex)->getInfo().dimensions(); 223 } 224 225 /* 226 * Initiates the Ico decode 227 */ 228 SkCodec::Result SkIcoCodec::onGetPixels(const SkImageInfo& dstInfo, 229 void* dst, size_t dstRowBytes, 230 const Options& opts, SkPMColor* ct, 231 int* ptr) { 232 // We return invalid scale if there is no candidate image with matching 233 // dimensions. 234 Result result = kInvalidScale; 235 for (int32_t i = 0; i < fEmbeddedCodecs->count(); i++) { 236 // If the dimensions match, try to decode 237 if (dstInfo.dimensions() == 238 fEmbeddedCodecs->operator[](i)->getInfo().dimensions()) { 239 240 // Perform the decode 241 result = fEmbeddedCodecs->operator[](i)->getPixels(dstInfo, 242 dst, dstRowBytes, &opts, ct, ptr); 243 244 // On a fatal error, keep trying to find an image to decode 245 if (kInvalidConversion == result || kInvalidInput == result || 246 kInvalidScale == result) { 247 SkCodecPrintf("Warning: Attempt to decode candidate ico failed.\n"); 248 continue; 249 } 250 251 // On success or partial success, return the result 252 return result; 253 } 254 } 255 256 SkCodecPrintf("Error: No matching candidate image in ico.\n"); 257 return result; 258 } 259