OLD | NEW |
1 /* | 1 /* |
2 * Copyright 2015 Google Inc. | 2 * Copyright 2015 Google Inc. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
6 */ | 6 */ |
7 | 7 |
8 #include "SkCodec_libgif.h" | 8 #include "SkCodec_libgif.h" |
9 #include "SkCodecPriv.h" | 9 #include "SkCodecPriv.h" |
10 #include "SkColorPriv.h" | 10 #include "SkColorPriv.h" |
11 #include "SkColorTable.h" | 11 #include "SkColorTable.h" |
12 #include "SkScaledCodec.h" | |
13 #include "SkStream.h" | 12 #include "SkStream.h" |
14 #include "SkSwizzler.h" | 13 #include "SkSwizzler.h" |
15 #include "SkUtils.h" | 14 #include "SkUtils.h" |
16 | 15 |
17 /* | 16 /* |
18 * Checks the start of the stream to see if the image is a gif | 17 * Checks the start of the stream to see if the image is a gif |
19 */ | 18 */ |
20 bool SkGifCodec::IsGif(SkStream* stream) { | 19 bool SkGifCodec::IsGif(SkStream* stream) { |
21 char buf[GIF_STAMP_LEN]; | 20 char buf[GIF_STAMP_LEN]; |
22 if (stream->read(buf, GIF_STAMP_LEN) == GIF_STAMP_LEN) { | 21 if (stream->read(buf, GIF_STAMP_LEN) == GIF_STAMP_LEN) { |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
54 return (int32_t) stream->read(out, size); | 53 return (int32_t) stream->read(out, size); |
55 } | 54 } |
56 | 55 |
57 /* | 56 /* |
58 * Open the gif file | 57 * Open the gif file |
59 */ | 58 */ |
60 static GifFileType* open_gif(SkStream* stream) { | 59 static GifFileType* open_gif(SkStream* stream) { |
61 return DGifOpen(stream, read_bytes_callback, nullptr); | 60 return DGifOpen(stream, read_bytes_callback, nullptr); |
62 } | 61 } |
63 | 62 |
| 63 /* |
| 64 * This function cleans up the gif object after the decode completes |
| 65 * It is used in a SkAutoTCallIProc template |
| 66 */ |
| 67 void SkGifCodec::CloseGif(GifFileType* gif) { |
| 68 DGifCloseFile(gif, nullptr); |
| 69 } |
| 70 |
| 71 /* |
| 72 * This function free extension data that has been saved to assist the image |
| 73 * decoder |
| 74 */ |
| 75 void SkGifCodec::FreeExtension(SavedImage* image) { |
| 76 if (nullptr != image->ExtensionBlocks) { |
| 77 GifFreeExtensions(&image->ExtensionBlockCount, &image->ExtensionBlocks); |
| 78 } |
| 79 } |
| 80 |
64 /* | 81 /* |
65 * Check if a there is an index of the color table for a transparent pixel | 82 * Check if a there is an index of the color table for a transparent pixel |
66 */ | 83 */ |
67 static uint32_t find_trans_index(const SavedImage& image) { | 84 static uint32_t find_trans_index(const SavedImage& image) { |
68 // If there is a transparent index specified, it will be contained in an | 85 // If there is a transparent index specified, it will be contained in an |
69 // extension block. We will loop through extension blocks in reverse order | 86 // extension block. We will loop through extension blocks in reverse order |
70 // to check the most recent extension blocks first. | 87 // to check the most recent extension blocks first. |
71 for (int32_t i = image.ExtensionBlockCount - 1; i >= 0; i--) { | 88 for (int32_t i = image.ExtensionBlockCount - 1; i >= 0; i--) { |
72 // Get an extension block | 89 // Get an extension block |
73 const ExtensionBlock& extBlock = image.ExtensionBlocks[i]; | 90 const ExtensionBlock& extBlock = image.ExtensionBlocks[i]; |
74 | 91 |
75 // Specifically, we need to check for a graphics control extension, | 92 // Specifically, we need to check for a graphics control extension, |
76 // which may contain transparency information. Also, note that a valid | 93 // which may contain transparency information. Also, note that a valid |
77 // graphics control extension is always four bytes. The fourth byte | 94 // graphics control extension is always four bytes. The fourth byte |
78 // is the transparent index (if it exists), so we need at least four | 95 // is the transparent index (if it exists), so we need at least four |
79 // bytes. | 96 // bytes. |
80 if (GRAPHICS_EXT_FUNC_CODE == extBlock.Function && extBlock.ByteCount >=
4) { | 97 if (GRAPHICS_EXT_FUNC_CODE == extBlock.Function && extBlock.ByteCount >=
4) { |
| 98 |
81 // Check the transparent color flag which indicates whether a | 99 // Check the transparent color flag which indicates whether a |
82 // transparent index exists. It is the least significant bit of | 100 // transparent index exists. It is the least significant bit of |
83 // the first byte of the extension block. | 101 // the first byte of the extension block. |
84 if (1 == (extBlock.Bytes[0] & 1)) { | 102 if (1 == (extBlock.Bytes[0] & 1)) { |
| 103 |
85 // Use uint32_t to prevent sign extending | 104 // Use uint32_t to prevent sign extending |
86 return extBlock.Bytes[3]; | 105 return extBlock.Bytes[3]; |
87 } | 106 } |
88 | 107 |
89 // There should only be one graphics control extension for the image
frame | 108 // There should only be one graphics control extension for the image
frame |
90 break; | 109 break; |
91 } | 110 } |
92 } | 111 } |
93 | 112 |
94 // Use maximum unsigned int (surely an invalid index) to indicate that a val
id | 113 // Use maximum unsigned int (surely an invalid index) to indicate that a val
id |
(...skipping 20 matching lines...) Expand all Loading... |
115 } | 134 } |
116 // Third pass | 135 // Third pass |
117 if (encodedRow * 2 < height) { | 136 if (encodedRow * 2 < height) { |
118 return 2 + 4 * (encodedRow - ceil_div(height, 4)); | 137 return 2 + 4 * (encodedRow - ceil_div(height, 4)); |
119 } | 138 } |
120 // Fourth pass | 139 // Fourth pass |
121 return 1 + 2 * (encodedRow - ceil_div(height, 2)); | 140 return 1 + 2 * (encodedRow - ceil_div(height, 2)); |
122 } | 141 } |
123 | 142 |
124 /* | 143 /* |
125 * This function cleans up the gif object after the decode completes | |
126 * It is used in a SkAutoTCallIProc template | |
127 */ | |
128 void SkGifCodec::CloseGif(GifFileType* gif) { | |
129 DGifCloseFile(gif, NULL); | |
130 } | |
131 | |
132 /* | |
133 * This function free extension data that has been saved to assist the image | |
134 * decoder | |
135 */ | |
136 void SkGifCodec::FreeExtension(SavedImage* image) { | |
137 if (NULL != image->ExtensionBlocks) { | |
138 GifFreeExtensions(&image->ExtensionBlockCount, &image->ExtensionBlocks); | |
139 } | |
140 } | |
141 | |
142 /* | |
143 * Read enough of the stream to initialize the SkGifCodec. | 144 * Read enough of the stream to initialize the SkGifCodec. |
144 * Returns a bool representing success or failure. | 145 * Returns a bool representing success or failure. |
145 * | 146 * |
146 * @param codecOut | 147 * @param codecOut |
147 * If it returned true, and codecOut was not nullptr, | 148 * If it returned true, and codecOut was not nullptr, |
148 * codecOut will be set to a new SkGifCodec. | 149 * codecOut will be set to a new SkGifCodec. |
149 * | 150 * |
150 * @param gifOut | 151 * @param gifOut |
151 * If it returned true, and codecOut was nullptr, | 152 * If it returned true, and codecOut was nullptr, |
152 * gifOut must be non-nullptr and gifOut will be set to a new | 153 * gifOut must be non-nullptr and gifOut will be set to a new |
153 * GifFileType pointer. | 154 * GifFileType pointer. |
154 * | 155 * |
155 * @param stream | 156 * @param stream |
156 * Deleted on failure. | 157 * Deleted on failure. |
157 * codecOut will take ownership of it in the case where we created a codec. | 158 * codecOut will take ownership of it in the case where we created a codec. |
158 * Ownership is unchanged when we returned a gifOut. | 159 * Ownership is unchanged when we returned a gifOut. |
159 * | 160 * |
160 */ | 161 */ |
161 bool SkGifCodec::ReadHeader(SkStream* stream, SkCodec** codecOut, GifFileType**
gifOut) { | 162 bool SkGifCodec::ReadHeader(SkStream* stream, SkCodec** codecOut, GifFileType**
gifOut) { |
162 SkAutoTDelete<SkStream> streamDeleter(stream); | 163 SkAutoTDelete<SkStream> streamDeleter(stream); |
163 | 164 |
164 // Read gif header, logical screen descriptor, and global color table | 165 // Read gif header, logical screen descriptor, and global color table |
165 SkAutoTCallVProc<GifFileType, CloseGif> gif(open_gif(stream)); | 166 SkAutoTCallVProc<GifFileType, CloseGif> gif(open_gif(stream)); |
166 | 167 |
167 if (nullptr == gif) { | 168 if (nullptr == gif) { |
168 gif_error("DGifOpen failed.\n"); | 169 gif_error("DGifOpen failed.\n"); |
169 return false; | 170 return false; |
170 } | 171 } |
171 | 172 |
172 // Read through gif extensions to get to the image data. Set the | |
173 // transparent index based on the extension data. | |
174 uint32_t transIndex; | |
175 SkCodec::Result result = ReadUpToFirstImage(gif, &transIndex); | |
176 if (kSuccess != result){ | |
177 return false; | |
178 } | |
179 | |
180 // Read the image descriptor | |
181 if (GIF_ERROR == DGifGetImageDesc(gif)) { | |
182 return false; | |
183 } | |
184 // If reading the image descriptor is successful, the image count will be | |
185 // incremented. | |
186 SkASSERT(gif->ImageCount >= 1); | |
187 | |
188 if (nullptr != codecOut) { | 173 if (nullptr != codecOut) { |
189 // Get fields from header | 174 // Get fields from header |
190 const int32_t width = gif->SWidth; | 175 const int32_t width = gif->SWidth; |
191 const int32_t height = gif->SHeight; | 176 const int32_t height = gif->SHeight; |
192 if (width <= 0 || height <= 0) { | 177 if (width <= 0 || height <= 0) { |
193 gif_error("Invalid dimensions.\n"); | 178 gif_error("Invalid dimensions.\n"); |
194 return false; | 179 return false; |
195 } | 180 } |
196 | 181 |
197 // Determine the recommended alpha type. The transIndex might be valid
if it less | |
198 // than 256. We are not certain that the index is valid until we proces
s the color | |
199 // table, since some gifs have color tables with less than 256 colors.
If | |
200 // there might be a valid transparent index, we must indicate that the i
mage has | |
201 // alpha. | |
202 // In the case where we must support alpha, we have the option to set th
e | |
203 // suggested alpha type to kPremul or kUnpremul. Both are valid since t
he alpha | |
204 // component will always be 0xFF or the entire 32-bit pixel will be set
to zero. | |
205 // We prefer kPremul because we support kPremul, and it is more efficien
t to use | |
206 // kPremul directly even when kUnpremul is supported. | |
207 SkAlphaType alphaType = (transIndex < 256) ? kPremul_SkAlphaType : kOpaq
ue_SkAlphaType; | |
208 | |
209 // Return the codec | 182 // Return the codec |
210 // kIndex is the most natural color type for gifs, so we set this as | 183 // kIndex is the most natural color type for gifs, so we set this as |
211 // the default. | 184 // the default. |
| 185 // Many gifs specify a color table index for transparent pixels. Every |
| 186 // other pixel is guaranteed to be opaque. Despite this, because of the |
| 187 // possiblity of transparent pixels, we cannot assume that the image is |
| 188 // opaque. We have the option to set the alpha type as kPremul or |
| 189 // kUnpremul. Both are valid since the alpha component will always be |
| 190 // 0xFF or the entire 32-bit pixel will be set to zero. We prefer |
| 191 // kPremul because we support kPremul, and it is more efficient to |
| 192 // use kPremul directly even when kUnpremul is supported. |
212 const SkImageInfo& imageInfo = SkImageInfo::Make(width, height, | 193 const SkImageInfo& imageInfo = SkImageInfo::Make(width, height, |
213 kIndex_8_SkColorType, alphaType); | 194 kIndex_8_SkColorType, k
Premul_SkAlphaType); |
214 *codecOut = new SkGifCodec(imageInfo, streamDeleter.detach(), gif.detach
(), transIndex); | 195 *codecOut = new SkGifCodec(imageInfo, streamDeleter.detach(), gif.detach
()); |
215 } else { | 196 } else { |
216 SkASSERT(nullptr != gifOut); | 197 SkASSERT(nullptr != gifOut); |
217 streamDeleter.detach(); | 198 streamDeleter.detach(); |
218 *gifOut = gif.detach(); | 199 *gifOut = gif.detach(); |
219 } | 200 } |
220 return true; | 201 return true; |
221 } | 202 } |
222 | 203 |
223 /* | 204 /* |
224 * Assumes IsGif was called and returned true | 205 * Assumes IsGif was called and returned true |
225 * Creates a gif decoder | 206 * Creates a gif decoder |
226 * Reads enough of the stream to determine the image format | 207 * Reads enough of the stream to determine the image format |
227 */ | 208 */ |
228 SkCodec* SkGifCodec::NewFromStream(SkStream* stream) { | 209 SkCodec* SkGifCodec::NewFromStream(SkStream* stream) { |
229 SkCodec* codec = nullptr; | 210 SkCodec* codec = nullptr; |
230 if (ReadHeader(stream, &codec, nullptr)) { | 211 if (ReadHeader(stream, &codec, nullptr)) { |
231 return codec; | 212 return codec; |
232 } | 213 } |
233 return nullptr; | 214 return nullptr; |
234 } | 215 } |
235 | 216 |
236 SkGifCodec::SkGifCodec(const SkImageInfo& srcInfo, SkStream* stream, GifFileType
* gif, | 217 SkGifCodec::SkGifCodec(const SkImageInfo& srcInfo, SkStream* stream, GifFileType
* gif) |
237 uint32_t transIndex) | |
238 : INHERITED(srcInfo, stream) | 218 : INHERITED(srcInfo, stream) |
239 , fGif(gif) | 219 , fGif(gif) |
240 , fSrcBuffer(new uint8_t[this->getInfo().width()]) | |
241 // If it is valid, fTransIndex will be used to set fFillIndex. We don't kno
w if | |
242 // fTransIndex is valid until we process the color table, since fTransIndex
may | |
243 // be greater than the size of the color table. | |
244 , fTransIndex(transIndex) | |
245 // Default fFillIndex is 0. We will overwrite this if fTransIndex is valid,
or if | |
246 // there is a valid background color. | |
247 , fFillIndex(0) | |
248 , fFrameDims(SkIRect::MakeEmpty()) | |
249 , fFrameIsSubset(false) | |
250 , fColorTable(NULL) | |
251 , fSwizzler(NULL) | |
252 {} | 220 {} |
253 | 221 |
254 bool SkGifCodec::onRewind() { | 222 bool SkGifCodec::onRewind() { |
255 GifFileType* gifOut = nullptr; | 223 GifFileType* gifOut = nullptr; |
256 if (!ReadHeader(this->stream(), nullptr, &gifOut)) { | 224 if (!ReadHeader(this->stream(), nullptr, &gifOut)) { |
257 return false; | 225 return false; |
258 } | 226 } |
259 | 227 |
260 SkASSERT(nullptr != gifOut); | 228 SkASSERT(nullptr != gifOut); |
261 fGif.reset(gifOut); | 229 fGif.reset(gifOut); |
262 return true; | 230 return true; |
263 } | 231 } |
264 | 232 |
265 SkCodec::Result SkGifCodec::ReadUpToFirstImage(GifFileType* gif, uint32_t* trans
Index) { | 233 /* |
| 234 * Initiates the gif decode |
| 235 */ |
| 236 SkCodec::Result SkGifCodec::onGetPixels(const SkImageInfo& dstInfo, |
| 237 void* dst, size_t dstRowBytes, |
| 238 const Options& opts, |
| 239 SkPMColor* inputColorPtr, |
| 240 int* inputColorCount) { |
| 241 // Rewind if necessary |
| 242 if (!this->rewindIfNeeded()) { |
| 243 return kCouldNotRewind; |
| 244 } |
| 245 |
| 246 // Check for valid input parameters |
| 247 if (opts.fSubset) { |
| 248 // Subsets are not supported. |
| 249 return kUnimplemented; |
| 250 } |
| 251 if (dstInfo.dimensions() != this->getInfo().dimensions()) { |
| 252 return gif_error("Scaling not supported.\n", kInvalidScale); |
| 253 } |
| 254 if (!conversion_possible(dstInfo, this->getInfo())) { |
| 255 return gif_error("Cannot convert input type to output type.\n", kInvalid
Conversion); |
| 256 } |
| 257 |
266 // Use this as a container to hold information about any gif extension | 258 // Use this as a container to hold information about any gif extension |
267 // blocks. This generally stores transparency and animation instructions. | 259 // blocks. This generally stores transparency and animation instructions. |
268 SavedImage saveExt; | 260 SavedImage saveExt; |
269 SkAutoTCallVProc<SavedImage, FreeExtension> autoFreeExt(&saveExt); | 261 SkAutoTCallVProc<SavedImage, FreeExtension> autoFreeExt(&saveExt); |
270 saveExt.ExtensionBlocks = nullptr; | 262 saveExt.ExtensionBlocks = nullptr; |
271 saveExt.ExtensionBlockCount = 0; | 263 saveExt.ExtensionBlockCount = 0; |
272 GifByteType* extData; | 264 GifByteType* extData; |
273 int32_t extFunction; | 265 int32_t extFunction; |
274 | 266 |
275 // We will loop over components of gif images until we find an image. Once | 267 // We will loop over components of gif images until we find an image. Once |
276 // we find an image, we will decode and return it. While many gif files | 268 // we find an image, we will decode and return it. While many gif files |
277 // contain more than one image, we will simply decode the first image. | 269 // contain more than one image, we will simply decode the first image. |
| 270 const int32_t width = dstInfo.width(); |
| 271 const int32_t height = dstInfo.height(); |
278 GifRecordType recordType; | 272 GifRecordType recordType; |
279 do { | 273 do { |
280 // Get the current record type | 274 // Get the current record type |
281 if (GIF_ERROR == DGifGetRecordType(gif, &recordType)) { | 275 if (GIF_ERROR == DGifGetRecordType(fGif, &recordType)) { |
282 return gif_error("DGifGetRecordType failed.\n", kInvalidInput); | 276 return gif_error("DGifGetRecordType failed.\n", kInvalidInput); |
283 } | 277 } |
| 278 |
284 switch (recordType) { | 279 switch (recordType) { |
285 case IMAGE_DESC_RECORD_TYPE: { | 280 case IMAGE_DESC_RECORD_TYPE: { |
286 *transIndex = find_trans_index(saveExt); | 281 // Read the image descriptor |
| 282 if (GIF_ERROR == DGifGetImageDesc(fGif)) { |
| 283 return gif_error("DGifGetImageDesc failed.\n", kInvalidInput
); |
| 284 } |
| 285 |
| 286 // If reading the image descriptor is successful, the image |
| 287 // count will be incremented |
| 288 SkASSERT(fGif->ImageCount >= 1); |
| 289 SavedImage* image = &fGif->SavedImages[fGif->ImageCount - 1]; |
| 290 |
| 291 // Process the descriptor |
| 292 const GifImageDesc& desc = image->ImageDesc; |
| 293 int32_t imageLeft = desc.Left; |
| 294 int32_t imageTop = desc.Top; |
| 295 int32_t innerWidth = desc.Width; |
| 296 int32_t innerHeight = desc.Height; |
| 297 // Fail on non-positive dimensions |
| 298 if (innerWidth <= 0 || innerHeight <= 0) { |
| 299 return gif_error("Invalid dimensions for inner image.\n", kI
nvalidInput); |
| 300 } |
| 301 // Treat the following cases as warnings and try to fix |
| 302 if (innerWidth > width) { |
| 303 gif_warning("Inner image too wide, shrinking.\n"); |
| 304 innerWidth = width; |
| 305 imageLeft = 0; |
| 306 } else if (imageLeft + innerWidth > width) { |
| 307 gif_warning("Shifting inner image to left to fit.\n"); |
| 308 imageLeft = width - innerWidth; |
| 309 } else if (imageLeft < 0) { |
| 310 gif_warning("Shifting image to right to fit\n"); |
| 311 imageLeft = 0; |
| 312 } |
| 313 if (innerHeight > height) { |
| 314 gif_warning("Inner image too tall, shrinking.\n"); |
| 315 innerHeight = height; |
| 316 imageTop = 0; |
| 317 } else if (imageTop + innerHeight > height) { |
| 318 gif_warning("Shifting inner image up to fit.\n"); |
| 319 imageTop = height - innerHeight; |
| 320 } else if (imageTop < 0) { |
| 321 gif_warning("Shifting image down to fit\n"); |
| 322 imageTop = 0; |
| 323 } |
| 324 |
| 325 // Create a color table to store colors the giflib colorMap |
| 326 SkPMColor alternateColorPtr[256]; |
| 327 SkPMColor* colorTable; |
| 328 SkColorType dstColorType = dstInfo.colorType(); |
| 329 if (kIndex_8_SkColorType == dstColorType) { |
| 330 SkASSERT(nullptr != inputColorPtr); |
| 331 SkASSERT(nullptr != inputColorCount); |
| 332 colorTable = inputColorPtr; |
| 333 } else { |
| 334 colorTable = alternateColorPtr; |
| 335 } |
| 336 |
| 337 // Set up the color table |
| 338 uint32_t colorCount = 0; |
| 339 // Allocate maximum storage to deal with invalid indices safely |
| 340 const uint32_t maxColors = 256; |
| 341 ColorMapObject* colorMap = fGif->Image.ColorMap; |
| 342 // If there is no local color table, use the global color table |
| 343 if (nullptr == colorMap) { |
| 344 colorMap = fGif->SColorMap; |
| 345 } |
| 346 if (nullptr != colorMap) { |
| 347 colorCount = colorMap->ColorCount; |
| 348 SkASSERT(colorCount == (unsigned) (1 << (colorMap->BitsPerPi
xel))); |
| 349 SkASSERT(colorCount <= 256); |
| 350 for (uint32_t i = 0; i < colorCount; i++) { |
| 351 colorTable[i] = SkPackARGB32(0xFF, |
| 352 colorMap->Colors[i].Red, |
| 353 colorMap->Colors[i].Green, |
| 354 colorMap->Colors[i].Blue); |
| 355 } |
| 356 } |
| 357 |
| 358 // This is used to fill unspecified pixels in the image data. |
| 359 uint32_t fillIndex = fGif->SBackGroundColor; |
| 360 ZeroInitialized zeroInit = opts.fZeroInitialized; |
| 361 |
| 362 // Gifs have the option to specify the color at a single |
| 363 // index of the color table as transparent. |
| 364 { |
| 365 // Get the transparent index. If the return value of this |
| 366 // function is greater than the colorCount, we know that |
| 367 // there is no valid transparent color in the color table. |
| 368 // This occurs if there is no graphics control extension or |
| 369 // if the index specified by the graphics control extension |
| 370 // is out of range. |
| 371 uint32_t transIndex = find_trans_index(saveExt); |
| 372 |
| 373 if (transIndex < colorCount) { |
| 374 colorTable[transIndex] = SK_ColorTRANSPARENT; |
| 375 // If there is a transparent index, we also use this as |
| 376 // the fill index. |
| 377 fillIndex = transIndex; |
| 378 } else if (fillIndex >= colorCount) { |
| 379 // If the fill index is invalid, we default to 0. This |
| 380 // behavior is unspecified but matches SkImageDecoder. |
| 381 fillIndex = 0; |
| 382 } |
| 383 } |
| 384 |
| 385 // Fill in the color table for indices greater than color count. |
| 386 // This allows for predictable, safe behavior. |
| 387 for (uint32_t i = colorCount; i < maxColors; i++) { |
| 388 colorTable[i] = colorTable[fillIndex]; |
| 389 } |
| 390 |
| 391 // Check if image is only a subset of the image frame |
| 392 SkAutoTDelete<SkSwizzler> swizzler(nullptr); |
| 393 if (innerWidth < width || innerHeight < height) { |
| 394 |
| 395 // Modify the destination info |
| 396 const SkImageInfo subsetDstInfo = dstInfo.makeWH(innerWidth,
innerHeight); |
| 397 |
| 398 // Fill the destination with the fill color |
| 399 // FIXME: This may not be the behavior that we want for |
| 400 // animated gifs where we draw on top of the |
| 401 // previous frame. |
| 402 SkSwizzler::Fill(dst, dstInfo, dstRowBytes, height, fillInde
x, colorTable, |
| 403 zeroInit); |
| 404 |
| 405 // Modify the dst pointer |
| 406 const int32_t dstBytesPerPixel = SkColorTypeBytesPerPixel(ds
tColorType); |
| 407 dst = SkTAddOffset<void*>(dst, |
| 408 dstRowBytes * imageTop + |
| 409 dstBytesPerPixel * imageLeft); |
| 410 |
| 411 // Create the subset swizzler |
| 412 swizzler.reset(SkSwizzler::CreateSwizzler( |
| 413 SkSwizzler::kIndex, colorTable, subsetDstInfo, |
| 414 zeroInit, this->getInfo())); |
| 415 } else { |
| 416 // Create the fully dimensional swizzler |
| 417 swizzler.reset(SkSwizzler::CreateSwizzler( |
| 418 SkSwizzler::kIndex, colorTable, dstInfo, |
| 419 zeroInit, this->getInfo())); |
| 420 } |
| 421 |
| 422 // Stores output from dgiflib and input to the swizzler |
| 423 SkAutoTDeleteArray<uint8_t> buffer(new uint8_t[innerWidth]); |
| 424 |
| 425 // Check the interlace flag and iterate over rows of the input |
| 426 if (fGif->Image.Interlace) { |
| 427 for (int32_t y = 0; y < innerHeight; y++) { |
| 428 if (GIF_ERROR == DGifGetLine(fGif, buffer.get(), innerWi
dth)) { |
| 429 // Recover from error by filling remainder of image |
| 430 memset(buffer.get(), fillIndex, innerWidth); |
| 431 for (; y < innerHeight; y++) { |
| 432 void* dstRow = SkTAddOffset<void>(dst, dstRowByt
es * |
| 433 get_output_row_interlaced(y, innerHeight
)); |
| 434 swizzler->swizzle(dstRow, buffer.get()); |
| 435 } |
| 436 return gif_error(SkStringPrintf( |
| 437 "Could not decode line %d of %d.\n", |
| 438 y, height - 1).c_str(), kIncompleteInput); |
| 439 } |
| 440 void* dstRow = SkTAddOffset<void>(dst, |
| 441 dstRowBytes * get_output_row_interlaced(y, inner
Height)); |
| 442 swizzler->swizzle(dstRow, buffer.get()); |
| 443 } |
| 444 } else { |
| 445 // Standard mode |
| 446 void* dstRow = dst; |
| 447 for (int32_t y = 0; y < innerHeight; y++) { |
| 448 if (GIF_ERROR == DGifGetLine(fGif, buffer.get(), innerWi
dth)) { |
| 449 SkSwizzler::Fill(dstRow, dstInfo, dstRowBytes, inner
Height - y, |
| 450 fillIndex, colorTable, zeroInit); |
| 451 return gif_error(SkStringPrintf( |
| 452 "Could not decode line %d of %d.\n", |
| 453 y, height - 1).c_str(), kIncompleteInput); |
| 454 } |
| 455 swizzler->swizzle(dstRow, buffer.get()); |
| 456 dstRow = SkTAddOffset<void>(dstRow, dstRowBytes); |
| 457 } |
| 458 } |
| 459 |
287 // FIXME: Gif files may have multiple images stored in a single | 460 // FIXME: Gif files may have multiple images stored in a single |
288 // file. This is most commonly used to enable | 461 // file. This is most commonly used to enable |
289 // animations. Since we are leaving animated gifs as a | 462 // animations. Since we are leaving animated gifs as a |
290 // TODO, we will return kSuccess after decoding the | 463 // TODO, we will return kSuccess after decoding the |
291 // first image in the file. This is the same behavior | 464 // first image in the file. This is the same behavior |
292 // as SkImageDecoder_libgif. | 465 // as SkImageDecoder_libgif. |
293 // | 466 // |
294 // Most times this works pretty well, but sometimes it | 467 // Most times this works pretty well, but sometimes it |
295 // doesn't. For example, I have an animated test image | 468 // doesn't. For example, I have an animated test image |
296 // where the first image in the file is 1x1, but the | 469 // where the first image in the file is 1x1, but the |
297 // subsequent images are meaningful. This currently | 470 // subsequent images are meaningful. This currently |
298 // displays the 1x1 image, which is not ideal. Right | 471 // displays the 1x1 image, which is not ideal. Right |
299 // now I am leaving this as an issue that will be | 472 // now I am leaving this as an issue that will be |
300 // addressed when we implement animated gifs. | 473 // addressed when we implement animated gifs. |
301 // | 474 // |
302 // It is also possible (not explicitly disallowed in the | 475 // It is also possible (not explicitly disallowed in the |
303 // specification) that gif files provide multiple | 476 // specification) that gif files provide multiple |
304 // images in a single file that are all meant to be | 477 // images in a single file that are all meant to be |
305 // displayed in the same frame together. I will | 478 // displayed in the same frame together. I will |
306 // currently leave this unimplemented until I find a | 479 // currently leave this unimplemented until I find a |
307 // test case that expects this behavior. | 480 // test case that expects this behavior. |
308 return kSuccess; | 481 return kSuccess; |
309 } | 482 } |
| 483 |
310 // Extensions are used to specify special properties of the image | 484 // Extensions are used to specify special properties of the image |
311 // such as transparency or animation. | 485 // such as transparency or animation. |
312 case EXTENSION_RECORD_TYPE: | 486 case EXTENSION_RECORD_TYPE: |
313 // Read extension data | 487 // Read extension data |
314 if (GIF_ERROR == DGifGetExtension(gif, &extFunction, &extData))
{ | 488 if (GIF_ERROR == DGifGetExtension(fGif, &extFunction, &extData))
{ |
315 return gif_error("Could not get extension.\n", kIncompleteIn
put); | 489 return gif_error("Could not get extension.\n", kIncompleteIn
put); |
316 } | 490 } |
317 | 491 |
318 // Create an extension block with our data | 492 // Create an extension block with our data |
319 while (nullptr != extData) { | 493 while (nullptr != extData) { |
320 // Add a single block | 494 // Add a single block |
321 if (GIF_ERROR == GifAddExtensionBlock(&saveExt.ExtensionBloc
kCount, | 495 if (GIF_ERROR == GifAddExtensionBlock(&saveExt.ExtensionBloc
kCount, |
322 &saveExt.ExtensionBloc
ks, | 496 &saveExt.ExtensionBloc
ks, |
323 extFunction, extData[0
], &extData[1])) | 497 extFunction, extData[0
], &extData[1])) |
324 { | 498 { |
325 return gif_error("Could not add extension block.\n", kIn
completeInput); | 499 return gif_error("Could not add extension block.\n", kIn
completeInput); |
326 } | 500 } |
327 // Move to the next block | 501 // Move to the next block |
328 if (GIF_ERROR == DGifGetExtensionNext(gif, &extData)) { | 502 if (GIF_ERROR == DGifGetExtensionNext(fGif, &extData)) { |
329 return gif_error("Could not get next extension.\n", kInc
ompleteInput); | 503 return gif_error("Could not get next extension.\n", kInc
ompleteInput); |
330 } | 504 } |
331 } | 505 } |
332 break; | 506 break; |
333 | 507 |
334 // Signals the end of the gif file | 508 // Signals the end of the gif file |
335 case TERMINATE_RECORD_TYPE: | 509 case TERMINATE_RECORD_TYPE: |
336 break; | 510 break; |
337 | 511 |
338 default: | 512 default: |
339 // DGifGetRecordType returns an error if the record type does | 513 // giflib returns an error code if the record type is not known. |
340 // not match one of the above cases. This should not be | 514 // We should catch this error immediately. |
341 // reached. | |
342 SkASSERT(false); | 515 SkASSERT(false); |
343 break; | 516 break; |
344 } | 517 } |
345 } while (TERMINATE_RECORD_TYPE != recordType); | 518 } while (TERMINATE_RECORD_TYPE != recordType); |
346 | 519 |
347 return gif_error("Could not find any images to decode in gif file.\n", kInva
lidInput); | 520 return gif_error("Could not find any images to decode in gif file.\n", kInva
lidInput); |
348 } | 521 } |
349 | |
350 /* | |
351 * A gif may contain many image frames, all of different sizes. | |
352 * This function checks if the frame dimensions are valid and corrects them if | |
353 * necessary. | |
354 */ | |
355 bool SkGifCodec::setFrameDimensions(const GifImageDesc& desc) { | |
356 // Fail on non-positive dimensions | |
357 int32_t frameLeft = desc.Left; | |
358 int32_t frameTop = desc.Top; | |
359 int32_t frameWidth = desc.Width; | |
360 int32_t frameHeight = desc.Height; | |
361 int32_t height = this->getInfo().height(); | |
362 int32_t width = this->getInfo().width(); | |
363 if (frameWidth <= 0 || frameHeight <= 0) { | |
364 return false; | |
365 } | |
366 | |
367 // Treat the following cases as warnings and try to fix | |
368 if (frameWidth > width) { | |
369 gif_warning("Image frame too wide, shrinking.\n"); | |
370 frameWidth = width; | |
371 frameLeft = 0; | |
372 } else if (frameLeft + frameWidth > width) { | |
373 gif_warning("Shifting image frame to left to fit.\n"); | |
374 frameLeft = width - frameWidth; | |
375 } else if (frameLeft < 0) { | |
376 gif_warning("Shifting image frame to right to fit\n"); | |
377 frameLeft = 0; | |
378 } | |
379 if (frameHeight > height) { | |
380 gif_warning("Image frame too tall, shrinking.\n"); | |
381 frameHeight = height; | |
382 frameTop = 0; | |
383 } else if (frameTop + frameHeight > height) { | |
384 gif_warning("Shifting image frame up to fit.\n"); | |
385 frameTop = height - frameHeight; | |
386 } else if (frameTop < 0) { | |
387 gif_warning("Shifting image frame down to fit\n"); | |
388 frameTop = 0; | |
389 } | |
390 fFrameDims.setXYWH(frameLeft, frameTop, frameWidth, frameHeight); | |
391 | |
392 // Indicate if the frame dimensions do not match the header dimensions | |
393 if (this->getInfo().dimensions() != fFrameDims.size()) { | |
394 fFrameIsSubset = true; | |
395 } | |
396 | |
397 return true; | |
398 } | |
399 | |
400 void SkGifCodec::initializeColorTable(const SkImageInfo& dstInfo, SkPMColor* inp
utColorPtr, | |
401 int* inputColorCount) { | |
402 // Set up our own color table | |
403 const uint32_t maxColors = 256; | |
404 SkPMColor colorPtr[256]; | |
405 if (NULL != inputColorCount) { | |
406 // We set the number of colors to maxColors in order to ensure | |
407 // safe memory accesses. Otherwise, an invalid pixel could | |
408 // access memory outside of our color table array. | |
409 *inputColorCount = maxColors; | |
410 } | |
411 | |
412 // Get local color table | |
413 ColorMapObject* colorMap = fGif->Image.ColorMap; | |
414 // If there is no local color table, use the global color table | |
415 if (NULL == colorMap) { | |
416 colorMap = fGif->SColorMap; | |
417 } | |
418 | |
419 uint32_t colorCount = 0; | |
420 if (NULL != colorMap) { | |
421 colorCount = colorMap->ColorCount; | |
422 // giflib guarantees these properties | |
423 SkASSERT(colorCount == (unsigned) (1 << (colorMap->BitsPerPixel))); | |
424 SkASSERT(colorCount <= 256); | |
425 for (uint32_t i = 0; i < colorCount; i++) { | |
426 colorPtr[i] = SkPackARGB32(0xFF, colorMap->Colors[i].Red, | |
427 colorMap->Colors[i].Green, colorMap->Colors[i].Blue); | |
428 } | |
429 } | |
430 | |
431 // Gifs have the option to specify the color at a single index of the color | |
432 // table as transparent. If the transparent index is greater than the | |
433 // colorCount, we know that there is no valid transparent color in the color | |
434 // table. If there is not valid transparent index, we will try to use the | |
435 // backgroundIndex as the fill index. If the backgroundIndex is also not | |
436 // valid, we will let fFillIndex default to 0 (it is set to zero in the | |
437 // constructor). This behavior is not specified but matches | |
438 // SkImageDecoder_libgif. | |
439 uint32_t backgroundIndex = fGif->SBackGroundColor; | |
440 if (fTransIndex < colorCount) { | |
441 colorPtr[fTransIndex] = SK_ColorTRANSPARENT; | |
442 fFillIndex = fTransIndex; | |
443 } else if (backgroundIndex < colorCount) { | |
444 fFillIndex = backgroundIndex; | |
445 } | |
446 | |
447 // Fill in the color table for indices greater than color count. | |
448 // This allows for predictable, safe behavior. | |
449 for (uint32_t i = colorCount; i < maxColors; i++) { | |
450 colorPtr[i] = colorPtr[fFillIndex]; | |
451 } | |
452 | |
453 fColorTable.reset(new SkColorTable(colorPtr, maxColors)); | |
454 copy_color_table(dstInfo, this->fColorTable, inputColorPtr, inputColorCount)
; | |
455 } | |
456 | |
457 SkCodec::Result SkGifCodec::prepareToDecode(const SkImageInfo& dstInfo, SkPMColo
r* inputColorPtr, | |
458 int* inputColorCount, const Options& opts) { | |
459 // Rewind if necessary | |
460 if (!this->rewindIfNeeded()) { | |
461 return kCouldNotRewind; | |
462 } | |
463 | |
464 // Check for valid input parameters | |
465 if (opts.fSubset) { | |
466 // Subsets are not supported. | |
467 return kUnimplemented; | |
468 } | |
469 if (!conversion_possible(dstInfo, this->getInfo())) { | |
470 return gif_error("Cannot convert input type to output type.\n", | |
471 kInvalidConversion); | |
472 } | |
473 | |
474 | |
475 // We have asserted that the image count is at least one in ReadHeader(). | |
476 SavedImage* image = &fGif->SavedImages[fGif->ImageCount - 1]; | |
477 const GifImageDesc& desc = image->ImageDesc; | |
478 | |
479 // Check that the frame dimensions are valid and set them | |
480 if(!this->setFrameDimensions(desc)) { | |
481 return gif_error("Invalid dimensions for image frame.\n", kInvalidInput)
; | |
482 } | |
483 | |
484 // Initialize color table and copy to the client if necessary | |
485 this->initializeColorTable(dstInfo, inputColorPtr, inputColorCount); | |
486 return kSuccess; | |
487 } | |
488 | |
489 SkCodec::Result SkGifCodec::initializeSwizzler(const SkImageInfo& dstInfo, | |
490 ZeroInitialized zeroInit) { | |
491 const SkPMColor* colorPtr = get_color_ptr(fColorTable.get()); | |
492 fSwizzler.reset(SkSwizzler::CreateSwizzler(SkSwizzler::kIndex, | |
493 colorPtr, dstInfo, zeroInit, this->getInfo())); | |
494 if (nullptr != fSwizzler.get()) { | |
495 return kSuccess; | |
496 } | |
497 return kUnimplemented; | |
498 } | |
499 | |
500 SkCodec::Result SkGifCodec::readRow() { | |
501 if (GIF_ERROR == DGifGetLine(fGif, fSrcBuffer.get(), fFrameDims.width())) { | |
502 return kIncompleteInput; | |
503 } | |
504 return kSuccess; | |
505 } | |
506 | |
507 /* | |
508 * Initiates the gif decode | |
509 */ | |
510 SkCodec::Result SkGifCodec::onGetPixels(const SkImageInfo& dstInfo, | |
511 void* dst, size_t dstRowBytes, | |
512 const Options& opts, | |
513 SkPMColor* inputColorPtr, | |
514 int* inputColorCount) { | |
515 Result result = this->prepareToDecode(dstInfo, inputColorPtr, inputColorCoun
t, opts); | |
516 if (kSuccess != result) { | |
517 return result; | |
518 } | |
519 | |
520 if (dstInfo.dimensions() != this->getInfo().dimensions()) { | |
521 return gif_error("Scaling not supported.\n", kInvalidScale); | |
522 } | |
523 | |
524 // Initialize the swizzler | |
525 if (fFrameIsSubset) { | |
526 const SkImageInfo subsetDstInfo = dstInfo.makeWH(fFrameDims.width(), fFr
ameDims.height()); | |
527 if (kSuccess != this->initializeSwizzler(subsetDstInfo, opts.fZeroInitia
lized)) { | |
528 return gif_error("Could not initialize swizzler.\n", kUnimplemented)
; | |
529 } | |
530 | |
531 // Fill the background | |
532 const SkPMColor* colorPtr = get_color_ptr(fColorTable.get()); | |
533 SkSwizzler::Fill(dst, dstInfo, dstRowBytes, this->getInfo().height(), | |
534 fFillIndex, colorPtr, opts.fZeroInitialized); | |
535 | |
536 // Modify the dst pointer | |
537 const int32_t dstBytesPerPixel = SkColorTypeBytesPerPixel(dstInfo.colorT
ype()); | |
538 dst = SkTAddOffset<void*>(dst, dstRowBytes * fFrameDims.top() + | |
539 dstBytesPerPixel * fFrameDims.left()); | |
540 } else { | |
541 if (kSuccess != this->initializeSwizzler(dstInfo, opts.fZeroInitialized)
) { | |
542 return gif_error("Could not initialize swizzler.\n", kUnimplemented)
; | |
543 } | |
544 } | |
545 | |
546 // Check the interlace flag and iterate over rows of the input | |
547 uint32_t width = fFrameDims.width(); | |
548 uint32_t height = fFrameDims.height(); | |
549 if (fGif->Image.Interlace) { | |
550 // In interlace mode, the rows of input are rearranged in | |
551 // the output image. We a helper function to help us | |
552 // rearrange the decoded rows. | |
553 for (uint32_t y = 0; y < height; y++) { | |
554 if (kSuccess != this->readRow()) { | |
555 // Recover from error by filling remainder of image | |
556 memset(fSrcBuffer.get(), fFillIndex, width); | |
557 for (; y < height; y++) { | |
558 void* dstRow = SkTAddOffset<void>(dst, | |
559 dstRowBytes * get_output_row_interlaced(y, height)); | |
560 fSwizzler->swizzle(dstRow, fSrcBuffer.get()); | |
561 } | |
562 return gif_error("Could not decode line.\n", kIncompleteInput); | |
563 } | |
564 void* dstRow = SkTAddOffset<void>(dst, | |
565 dstRowBytes * get_output_row_interlaced(y, height)); | |
566 fSwizzler->swizzle(dstRow, fSrcBuffer.get()); | |
567 } | |
568 } else { | |
569 // Standard mode | |
570 void* dstRow = dst; | |
571 for (uint32_t y = 0; y < height; y++) { | |
572 if (kSuccess != this->readRow()) { | |
573 const SkPMColor* colorPtr = get_color_ptr(fColorTable.get()); | |
574 SkSwizzler::Fill(dstRow, dstInfo, dstRowBytes, | |
575 height - y, fFillIndex, colorPtr, opts.fZeroInitialized)
; | |
576 return gif_error("Could not decode line\n", kIncompleteInput); | |
577 } | |
578 fSwizzler->swizzle(dstRow, fSrcBuffer.get()); | |
579 dstRow = SkTAddOffset<void>(dstRow, dstRowBytes); | |
580 } | |
581 } | |
582 return kSuccess; | |
583 } | |
584 | |
585 // TODO (msarett): skbug.com/3582 | |
586 // Should we implement reallyHasAlpha? Or should we read extens
ion blocks in the | |
587 // header? Or should we do both? | |
588 | |
589 class SkGifScanlineDecoder : public SkScanlineDecoder { | |
590 public: | |
591 SkGifScanlineDecoder(const SkImageInfo& srcInfo, SkGifCodec* codec) | |
592 : INHERITED(srcInfo) | |
593 , fCodec(codec) | |
594 {} | |
595 | |
596 SkEncodedFormat onGetEncodedFormat() const override { | |
597 return kGIF_SkEncodedFormat; | |
598 } | |
599 | |
600 SkCodec::Result onStart(const SkImageInfo& dstInfo, const SkCodec::Options&
opts, | |
601 SkPMColor inputColorPtr[], int* inputColorCount) ove
rride { | |
602 SkCodec::Result result = fCodec->prepareToDecode(dstInfo, inputColorPtr,
inputColorCount, | |
603 this->options()); | |
604 if (SkCodec::kSuccess != result) { | |
605 return result; | |
606 } | |
607 | |
608 // Check to see if scaling was requested. | |
609 if (dstInfo.dimensions() != this->getInfo().dimensions()) { | |
610 if (!SkScaledCodec::DimensionsSupportedForSampling(this->getInfo(),
dstInfo)) { | |
611 return gif_error("Scaling not supported.\n", SkCodec::kInvalidSc
ale); | |
612 } | |
613 } | |
614 | |
615 // Initialize the swizzler | |
616 if (fCodec->fFrameIsSubset) { | |
617 int sampleX; | |
618 SkScaledCodec::ComputeSampleSize(dstInfo, fCodec->getInfo(), &sample
X, NULL); | |
619 const SkImageInfo subsetDstInfo = dstInfo.makeWH( | |
620 get_scaled_dimension(fCodec->fFrameDims.width(), sampleX), | |
621 fCodec->fFrameDims.height()); | |
622 if (SkCodec::kSuccess != fCodec->initializeSwizzler(subsetDstInfo, | |
623 opts.fZeroInitialized)) { | |
624 return gif_error("Could not initialize swizzler.\n", SkCodec::kU
nimplemented); | |
625 } | |
626 } else { | |
627 if (SkCodec::kSuccess != fCodec->initializeSwizzler(dstInfo, opts.fZ
eroInitialized)) { | |
628 return gif_error("Could not initialize swizzler.\n", SkCodec::kU
nimplemented); | |
629 } | |
630 } | |
631 | |
632 return SkCodec::kSuccess; | |
633 } | |
634 | |
635 SkCodec::Result onGetScanlines(void* dst, int count, size_t rowBytes) overri
de { | |
636 if (fCodec->fFrameIsSubset) { | |
637 // Fill the requested rows | |
638 const SkPMColor* colorPtr = get_color_ptr(fCodec->fColorTable.get())
; | |
639 SkSwizzler::Fill(dst, this->dstInfo(), rowBytes, count, fCodec->fFil
lIndex, | |
640 colorPtr, this->options().fZeroInitialized); | |
641 | |
642 // Do nothing for rows before the image frame | |
643 int rowsBeforeFrame = fCodec->fFrameDims.top() - INHERITED::getY(); | |
644 if (rowsBeforeFrame > 0) { | |
645 count = SkTMin(0, count - rowsBeforeFrame); | |
646 dst = SkTAddOffset<void>(dst, rowBytes * rowsBeforeFrame); | |
647 } | |
648 | |
649 // Do nothing for rows after the image frame | |
650 int rowsAfterFrame = INHERITED::getY() + count - fCodec->fFrameDims.
bottom(); | |
651 if (rowsAfterFrame > 0) { | |
652 count = SkTMin(0, count - rowsAfterFrame); | |
653 } | |
654 | |
655 // Adjust dst pointer for left offset | |
656 dst = SkTAddOffset<void>(dst, SkColorTypeBytesPerPixel( | |
657 this->dstInfo().colorType()) * fCodec->fFrameDims.left()); | |
658 } | |
659 | |
660 for (int i = 0; i < count; i++) { | |
661 if (SkCodec::kSuccess != fCodec->readRow()) { | |
662 const SkPMColor* colorPtr = get_color_ptr(fCodec->fColorTable.ge
t()); | |
663 SkSwizzler::Fill(dst, this->dstInfo(), rowBytes, | |
664 count - i, fCodec->fFillIndex, colorPtr, | |
665 this->options().fZeroInitialized); | |
666 return gif_error("Could not decode line\n", SkCodec::kIncomplete
Input); | |
667 } | |
668 fCodec->fSwizzler->swizzle(dst, fCodec->fSrcBuffer.get()); | |
669 dst = SkTAddOffset<void>(dst, rowBytes); | |
670 } | |
671 return SkCodec::kSuccess; | |
672 } | |
673 | |
674 SkScanlineOrder onGetScanlineOrder() const override { | |
675 if (fCodec->fGif->Image.Interlace) { | |
676 return kOutOfOrder_SkScanlineOrder; | |
677 } else { | |
678 return kTopDown_SkScanlineOrder; | |
679 } | |
680 } | |
681 | |
682 int onGetY() const override { | |
683 if (fCodec->fGif->Image.Interlace) { | |
684 return get_output_row_interlaced(INHERITED::onGetY(), this->dstInfo(
).height()); | |
685 } else { | |
686 return INHERITED::onGetY(); | |
687 } | |
688 } | |
689 | |
690 private: | |
691 SkAutoTDelete<SkGifCodec> fCodec; | |
692 | |
693 typedef SkScanlineDecoder INHERITED; | |
694 }; | |
695 | |
696 SkScanlineDecoder* SkGifCodec::NewSDFromStream(SkStream* stream) { | |
697 SkAutoTDelete<SkGifCodec> codec (static_cast<SkGifCodec*>(SkGifCodec::NewFro
mStream(stream))); | |
698 if (!codec) { | |
699 return NULL; | |
700 } | |
701 | |
702 const SkImageInfo& srcInfo = codec->getInfo(); | |
703 | |
704 return SkNEW_ARGS(SkGifScanlineDecoder, (srcInfo, codec.detach())); | |
705 } | |
OLD | NEW |