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