Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(143)

Side by Side Diff: src/codec/SkCodec_libgif.cpp

Issue 1022673011: Creating a new wrapper for gif decoder (Closed) Base URL: https://skia.googlesource.com/skia.git@ico-real
Patch Set: Warnings mostly disabled Created 5 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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_libgif.h"
9 #include "SkCodecPriv.h"
10 #include "SkColorPriv.h"
11 #include "SkColorTable.h"
12 #include "SkGifInterlaceIter.h"
13 #include "SkStream.h"
14 #include "SkSwizzler.h"
15 #include "SkUtils.h"
16
17 /*
18 * Checks the start of the stream to see if the image is a gif
19 */
20 bool SkGifCodec::IsGif(SkStream* stream) {
21 char buf[GIF_STAMP_LEN];
22 if (stream->read(buf, GIF_STAMP_LEN) == GIF_STAMP_LEN) {
23 if (memcmp(GIF_STAMP, buf, GIF_STAMP_LEN) == 0 ||
24 memcmp(GIF87_STAMP, buf, GIF_STAMP_LEN) == 0 ||
25 memcmp(GIF89_STAMP, buf, GIF_STAMP_LEN) == 0) {
26 return true;
27 }
28 }
29 return false;
30 }
31
32 /*
33 * Warning reporting function
34 */
35 static void gif_warning(const char* msg) {
36 SkCodecPrintf("Gif Warning: %s\n", msg);
37 }
38
39 /*
40 * Error function
41 */
42 static SkCodec::Result gif_error(const char* msg,
43 SkCodec::Result result = SkCodec::kInvalidInput) {
44 SkCodecPrintf("Gif Error: %s\n", msg);
45 return result;
46 }
47
48
49 /*
50 * Read function that will be passed to gif_lib
51 */
52 static int32_t read_bytes_callback(GifFileType* fileType, GifByteType* out,
53 int32_t size) {
54 SkStream* stream = (SkStream*) fileType->UserData;
55 return (int32_t) stream->read(out, size);
56 }
57
58 /*
59 * Open the gif file
60 */
61 static GifFileType* open_gif(SkStream* stream) {
62 #if GIFLIB_MAJOR < 5
63 return DGifOpen(stream, read_bytes_callback);
64 #else
65 return DGifOpen(stream, read_bytes_callback, NULL);
66 #endif
67 }
68
69 /*
70 * This function cleans up the gif object after the decode completes
71 * It is used in a SkAutoTCallIProc template
72 */
73 int32_t SkGifCodec::CloseGif(GifFileType* gif) {
74 #if GIFLIB_MAJOR < 5 || (GIFLIB_MAJOR == 5 && GIFLIB_MINOR == 0)
75 return DGifCloseFile(gif);
76 #else
77 return DGifCloseFile(gif, NULL);
78 #endif
79 }
80
81 /*
82 * This function free extension data that has been saved to assist the image
83 * decoder
84 */
85 void SkGifCodec::FreeExtension(SavedImage* image) {
86 if (NULL != image->ExtensionBlocks) {
87 #if GIFLIB_MAJOR < 5
88 FreeExtension(image);
89 #else
90 GifFreeExtensions(&image->ExtensionBlockCount, &image->ExtensionBlocks);
91 #endif
92 }
93 }
94
95 /*
96 * Check if a there is an index of the color table for a transparent pixel
97 */
98 static int32_t find_trans_index(const SavedImage& image, uint32_t colorCount) {
99 // If there is a transparent index specified, it will be contained in an
100 // extension block.
101 for (int32_t i = 0; i < image.ExtensionBlockCount; i++) {
102 // Get an extension block
103 const ExtensionBlock& extBlock = image.ExtensionBlocks[i];
104
105 // Specifically, we need to check for a graphics control extension,
106 // which may contain transparency information. Also, note that a valid
107 // graphics control extension is always four bytes. The fourth byte
108 // is the transparent index (if it exists), so we need at least four
109 // bytes.
110 if (GRAPHICS_EXT_FUNC_CODE == extBlock.Function &&
111 extBlock.ByteCount >= 4) {
112
113 // Check the transparent color flag which indicates whether a
114 // transparent index exists. It is the least significant bit of
115 // the first byte of the extension block.
116 if (1 == (extBlock.Bytes[0] & 1)) {
117
118 // Use uint32_t to prevent sign extending
119 uint32_t transIndex = extBlock.Bytes[3];
120 if (transIndex < colorCount) {
121 return transIndex;
122 }
123 }
scroggo 2015/03/31 18:39:56 nit: Could there be multiple graphics control exte
msarett 2015/03/31 20:11:40 There can be multiple GCEs because there can be mu
scroggo 2015/03/31 20:39:11 I wouldn't worry about reversing this. Since you'r
124 }
125 }
126
127 // Flag indicating that a valid index was not found
128 return -1;
129 }
130
131 /*
132 * Assumes IsGif was called and returned true
133 * Creates a gif decoder
134 * Reads enough of the stream to determine the image format
135 */
136 SkCodec* SkGifCodec::NewFromStream(SkStream* stream) {
137 // Read gif header, logical screen descriptor, and global color table
138 SkAutoTCallIProc<GifFileType, CloseGif> gif(open_gif(stream));
139
140 if (NULL == gif) {
141 gif_error("DGifOpen failed.\n");
142 return NULL;
143 }
144
145 // Get fields from header
146 const int32_t width = gif->SWidth;
147 const int32_t height = gif->SHeight;
148 if (width <= 0 || height <= 0) {
149 gif_error("Invalid dimensions.\n");
150 return NULL;
151 }
152
153 // Return the codec
154 // kIndex is the most natural color type for gifs, so we set this as
155 // the default.
156 // Many gifs specify a color table index for transparent pixels. Every
157 // other pixel is guaranteed to be opaque. Despite this, because of the
158 // possiblity of transparent pixels, we cannot assume that the image is
159 // opaque. However, we can at least mark it as kPremul, since pixels will
scroggo 2015/03/31 18:39:56 Maybe a note that it could be treated as unpremul,
msarett 2015/03/31 20:11:41 Agreed
160 // either have a 0xFF alpha component or be completely zeroed.
161 const SkImageInfo& imageInfo = SkImageInfo::Make(width, height,
162 kIndex_8_SkColorType, kPremul_SkAlphaType);
163 return SkNEW_ARGS(SkGifCodec, (imageInfo, stream, gif.detach()));
164 }
165
166 SkGifCodec::SkGifCodec(const SkImageInfo& srcInfo, SkStream* stream,
167 GifFileType* gif)
168 : INHERITED(srcInfo, stream)
169 , fGif(gif)
170 {}
171
172 /*
173 * Checks if the conversion between the input image and the requested output
174 * image has been implemented
175 */
176 static bool conversion_possible(const SkImageInfo& dst,
177 const SkImageInfo& src) {
178 // Ensure that the profile type is unchanged
179 if (dst.profileType() != src.profileType()) {
180 return false;
181 }
182
183 // Check for supported color and alpha types
184 switch (dst.colorType()) {
185 case kN32_SkColorType:
186 return src.alphaType() == dst.alphaType() ||
187 (kPremul_SkAlphaType == dst.alphaType() &&
188 kUnpremul_SkAlphaType == src.alphaType());
scroggo 2015/03/31 18:39:57 The source will always be premul, right? And it co
msarett 2015/03/31 20:11:40 I agree and fixed the return statement. We discus
189 default:
190 return false;
191 }
192 }
193
194 /*
195 * Initiates the gif decode
196 */
197 SkCodec::Result SkGifCodec::onGetPixels(const SkImageInfo& dstInfo,
198 void* dst, size_t dstRowBytes,
199 const Options& opts, SkPMColor*, int*) {
200 // Check for valid input parameters
201 if (!this->rewindIfNeeded()) {
202 return kCouldNotRewind;
203 }
204 if (dstInfo.dimensions() != this->getInfo().dimensions()) {
205 return gif_error("Scaling not supported.\n", kInvalidScale);
206 }
207 if (!conversion_possible(dstInfo, this->getInfo())) {
208 return gif_error("Cannot convert input type to output type.\n",
209 kInvalidConversion);
210 }
211
212 // Use this as a container to hold information about any gif extension
213 // blocks. This generally stores transparency and animation instructions.
214 SavedImage saveExt;
215 SkAutoTCallVProc<SavedImage, FreeExtension> autoFreeExt(&saveExt);
216 saveExt.ExtensionBlocks = NULL;
217 saveExt.ExtensionBlockCount = 0;
218 GifByteType* extData;
219 #if GIFLIB_MAJOR >= 5
220 int32_t extFunction;
221 #endif
222
223 // We will loop over components of gif images until we find an image. Once
224 // we find an image, we will decode and return it. While many gif files
225 // contain more than one image, we will simply decode the first image.
226 const int32_t width = dstInfo.width();
227 const int32_t height = dstInfo.height();
228 GifRecordType recordType = UNDEFINED_RECORD_TYPE;
229 while (TERMINATE_RECORD_TYPE != recordType) {
scroggo 2015/03/31 18:39:56 Is it possible that a file never contains TERMINAT
msarett 2015/03/31 20:11:40 Yes this will always find a way to fail inside or
230 // Get the current record type
231 if (GIF_ERROR == DGifGetRecordType(fGif, &recordType)) {
232 return gif_error("DGifGetRecordType failed.\n", kInvalidInput);
233 }
234
235 switch (recordType) {
236 case IMAGE_DESC_RECORD_TYPE: {
237 // Read the image descriptor
238 if (GIF_ERROR == DGifGetImageDesc(fGif)) {
239 return gif_error("DGifGetImageDesc failed.\n",
240 kInvalidInput);
241 }
242
243 // If reading the image descriptor is successful, the image
244 // count will be incremented
245 SkASSERT(fGif->ImageCount >= 1);
246 SavedImage* image = &fGif->SavedImages[fGif->ImageCount - 1];
scroggo 2015/03/31 18:39:56 It looks like we're using the last image? Why not
msarett 2015/03/31 20:11:40 We are always using and returning the first image.
247
248 // Process the descriptor
249 const GifImageDesc& desc = image->ImageDesc;
250 int32_t imageLeft = desc.Left;
251 int32_t imageTop = desc.Top;
252 int32_t innerWidth = desc.Width;
253 int32_t innerHeight = desc.Height;
254 // Fail on non-positive dimensions
255 if (innerWidth <= 0 || innerHeight <= 0) {
256 return gif_error("Invalid dimensions for inner image.\n",
257 kInvalidInput);
258 }
259 // Treat the following cases as warnings and try to fix
260 if (innerWidth > width) {
261 gif_warning("Inner image too wide, shrinking.\n");
262 innerWidth = width;
263 imageLeft = 0;
264 } else if (imageLeft + innerWidth > width) {
265 gif_warning("Shifting inner image to left to fit.\n");
266 imageLeft = width - innerWidth;
267 } else if (imageLeft < 0) {
268 gif_warning("Shifting image to right to fit\n");
269 imageLeft = 0;
270 }
271 if (innerHeight > height) {
272 gif_warning("Inner image too tall, shrinking.\n");
273 innerHeight = height;
274 imageTop = 0;
275 } else if (imageTop + innerHeight > height) {
276 gif_warning("Shifting inner image up to fit.\n");
277 imageTop = height - innerHeight;
278 } else if (imageTop < 0) {
279 gif_warning("Shifting image down to fit\n");
280 imageTop = 0;
281 }
282
283 // Set up the color table
284 uint32_t colorCount = 0;
285 // Allocate maximum storage to deal with invalid indices safely
286 const uint32_t maxColors = 256;
287 SkPMColor colorTable[maxColors];
288 ColorMapObject* colorMap = fGif->Image.ColorMap;
289 // If there is no local color table, use the global color table
290 if (NULL == colorMap) {
291 colorMap = fGif->SColorMap;
292 }
293 if (NULL != colorMap) {
294 colorCount = colorMap->ColorCount;
scroggo 2015/03/31 18:39:56 Is ColorCount created by giflib (therefore reliabl
msarett 2015/03/31 20:11:40 colorCount is reliable. giflib calculates it and
295 SkASSERT(colorCount ==
296 (unsigned) (1 << (colorMap->BitsPerPixel)));
297 SkASSERT(colorCount <= 256);
298 for (uint32_t i = 0; i < colorCount; i++) {
299 colorTable[i] = SkPackARGB32(0xFF,
300 colorMap->Colors[i].Red,
scroggo 2015/03/31 18:39:56 nit: This should line up with 0xFF or be indented
msarett 2015/03/31 20:11:40 Done.
301 colorMap->Colors[i].Green,
302 colorMap->Colors[i].Blue);
303 }
304 }
305
306 // This is used to fill unspecified pixels in the image data.
307 uint32_t fillIndex = fGif->SBackGroundColor;
308 bool fillBackground = true;
309 ZeroInitialized zeroInit = opts.fZeroInitialized;
310
311 // Gifs have the option to specify the color at a single
312 // index of the color table as transparent.
313 {
314 int32_t transIndex = find_trans_index(saveExt, colorCount);
scroggo 2015/03/31 18:39:56 nit: What if we compared against colorCount here,
msarett 2015/03/31 20:11:41 I think this is much better, and it is now possibl
315
316 // If the background is already zeroed and we have a valid
317 // transparent index, we do not need to fill the background.
318 if (transIndex >= 0) {
319 colorTable[transIndex] = SK_ColorTRANSPARENT;
320 // If there is a transparent index, we also use this as
321 // the fill index.
322 fillIndex = transIndex;
323 fillBackground = (kYes_ZeroInitialized != zeroInit);
324 } else if (fillIndex >= colorCount) {
325 // If the fill index is invalid, we default to 0. This
326 // behavior is unspecified but matches SkImageDecoder.
327 fillIndex = 0;
328 }
329 }
330
331 // Fill in the color table for indices greater than color count.
332 // This allows for predictable, safe behavior.
333 for (uint32_t i = colorCount; i < maxColors; i++) {
334 colorTable[i] = colorTable[fillIndex];
335 }
336
337 // Check if image is only a subset of the image frame
338 SkAutoTDelete<SkSwizzler> swizzler(NULL);
339 if (innerWidth < width || innerHeight < height) {
340
341 // Modify the destination info
342 const SkImageInfo subsetDstInfo =
343 dstInfo.makeWH(innerWidth, innerHeight);
344
345 // Fill the destination with the fill color
346 // FIXME: This may not be the behavior that we want for
347 // animated gifs where we draw on top of the
348 // previous frame.
349 SkColorType dstColorType = dstInfo.colorType();
350 if (fillBackground) {
351 switch (dstColorType) {
352 case kN32_SkColorType:
353 sk_memset32((SkPMColor*) dst,
354 colorTable[fillIndex],
355 ((int) dstRowBytes) * height
356 / sizeof(SkPMColor));
357 break;
358 default:
359 SkASSERT(false);
360 break;
361 }
362 }
363
364 // Modify the dst pointer
365 const int32_t dstBytesPerPixel =
366 SkColorTypeBytesPerPixel(dstColorType);
367 void* subsetDst = SkTAddOffset<void*>(dst,
368 dstRowBytes * imageTop +
369 dstBytesPerPixel * imageLeft);
370
371 // Create the subset swizzler
372 swizzler.reset(SkSwizzler::CreateSwizzler(
373 SkSwizzler::kIndex, colorTable, subsetDstInfo,
374 subsetDst, dstRowBytes, zeroInit));
375 } else {
376 // Create the fully dimensional swizzler
377 swizzler.reset(SkSwizzler::CreateSwizzler(
378 SkSwizzler::kIndex, colorTable, dstInfo, dst,
379 dstRowBytes, zeroInit));
380 }
381
382 // Stores output from dgiflib and input to the swizzler
383 SkAutoTDeleteArray<uint8_t>
384 buffer(SkNEW_ARRAY(uint8_t, innerWidth));
385
386 // Check the interlace flag and iterate over rows of the input
387 if (fGif->Image.Interlace) {
388 // In interlace mode, the rows of input are rearranged in
389 // the output image. We use an iterator to take care of
390 // the rearranging.
391 SkGifInterlaceIter iter(innerHeight);
392 for (int32_t y = 0; y < innerHeight; y++) {
393 if (GIF_ERROR == DGifGetLine(fGif, buffer.get(),
394 innerWidth)) {
395 // Recover from error by filling remainder of image
396 if (fillBackground) {
397 memset(buffer.get(), fillIndex, innerWidth);
398 for (; y < innerHeight; y++) {
399 swizzler->next(buffer.get(), iter.nextY());
400 }
401 }
402 return gif_error(SkStringPrintf(
403 "Could not decode line %d of %d.\n",
404 y, height - 1).c_str(), kIncompleteInput);
405 }
406 swizzler->next(buffer.get(), iter.nextY());
407 }
408 } else {
409 // Standard mode
410 for (int32_t y = 0; y < innerHeight; y++) {
411 if (GIF_ERROR == DGifGetLine(fGif, buffer.get(),
412 innerWidth)) {
413 if (fillBackground) {
414 SkPMColor* dstPtr = (SkPMColor*) SkTAddOffset
415 <void*>(dst, y * dstRowBytes);
416 sk_memset32(dstPtr, colorTable[fillIndex],
417 (height - y) * ((int) dstRowBytes)
418 / sizeof(SkPMColor));
419 }
420 return gif_error(SkStringPrintf(
421 "Could not decode line %d of %d.\n",
422 y, height - 1).c_str(), kIncompleteInput);
423 }
424 swizzler->next(buffer.get());
425 }
426 }
427
428 // FIXME: Gif files may have multiple images stored in a single
429 // file. This is most commonly used to enable
430 // animations. Since we are leaving animated gifs as a
431 // TODO, we will return kSuccess after decoding the
432 // first image in the file. This is the same behavior
433 // as SkImageDecoder_libgif.
434 //
435 // Most times this works pretty well, but sometimes it
436 // doesn't. For example, I have an animated test image
437 // where the first image in the file is 1x1, but the
438 // subsequent images are meaningful. This currently
439 // displays the 1x1 image, which is not ideal. Right
440 // now I am leaving this as an issue that will be
441 // addressed when we implement animated gifs.
442 //
443 // It is also possible (not explicitly disallowed in the
444 // specification) that gif files provide multiple
445 // images in a single file that are all meant to be
446 // displayed in the same frame together. I will
447 // currently leave this unimplemented until I find a
448 // test case that expects this behavior.
449 return kSuccess;
450 }
451
452 // Extensions are used to specify special properties of the image
453 // such as transparency or animation.
454 case EXTENSION_RECORD_TYPE:
scroggo 2015/03/31 18:39:57 Does this part add the extension that provides the
msarett 2015/03/31 20:11:40 Yes.
455 // Read extension data
456 #if GIFLIB_MAJOR < 5
457 if (GIF_ERROR ==
458 DGifGetExtension(fGif, &saveExt.Function, &extData)) {
459 #else
460 if (GIF_ERROR ==
461 DGifGetExtension(fGif, &extFunction, &extData)) {
462 #endif
463 return gif_error("Could not get extension.\n",
464 kIncompleteInput);
465 }
466
467 // Create an extension block with our data
468 while (NULL != extData) {
469 // Add a single block
470 #if GIFLIB_MAJOR < 5
471 if (GIF_ERROR == AddExtensionBlock(&saveExt, extData[0],
472 &extData[1])) {
473 #else
474 if (GIF_ERROR ==
475 GifAddExtensionBlock(&saveExt.ExtensionBlockCount,
476 &saveExt.ExtensionBlocks, extFunction, extData[0],
477 &extData[1])) {
478 #endif
479 return gif_error("Could not add extension block.\n",
480 kIncompleteInput);
481 }
482 // Move to the next block
483 if (GIF_ERROR == DGifGetExtensionNext(fGif, &extData)) {
484 return gif_error("Could not get next extension.\n",
485 kIncompleteInput);
486 }
487 #if GIFLIB_MAJOR < 5
488 saveExt.Function = 0;
489 #endif
490 }
491 break;
492
493 // Signals the end of the gif file
494 case TERMINATE_RECORD_TYPE:
scroggo 2015/03/31 18:39:56 Any reason to single this out from default? The be
msarett 2015/03/31 20:11:40 The default case should never be reached. I will
495 break;
496
497 default:
498 break;
499 }
500 }
501
502 return gif_error("Could not find any images to decode in gif file.\n",
503 kInvalidInput);
504 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698