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

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: Adding gif functionality to SkCodec Created 5 years, 9 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 * Error reporting function
34 * TODO: Add option to turn off printing of error
35 */
36 static void gif_error(const char* msg) {
37 SkDebugf("Gif Error: %s\n", msg);
38 }
39
40 /*
41 * This function cleans up the gif object after the decode completes
42 * It is used in a SkAutoTCallIProc template
43 */
44 int32_t SkGifCodec::close_gif(GifFileType* gif) {
scroggo 2015/03/25 19:44:48 It looks like this returns an int in the header?
msarett 2015/03/26 19:15:57 I'll make it uniform. Is there a difference betwe
scroggo 2015/03/26 20:03:52 int is platform dependent, and may be 2 or 4 bytes
45 #if GIFLIB_MAJOR < 5 || (GIFLIB_MAJOR == 5 && GIFLIB_MINOR == 0)
46 return DGifCloseFile(gif);
47 #else
48 return DGifCloseFile(gif, NULL);
49 #endif
50 }
51
52 /*
53 * This function free extension data that has been saved to assist the image
54 * decoder
55 */
56 void free_extension(SavedImage* image) {
57 if (NULL != image->ExtensionBlocks) {
58 #if GIFLIB_MAJOR < 5
59 FreeExtension(image);
60 #else
61 GifFreeExtensions(&image->ExtensionBlockCount, &image->ExtensionBlocks);
62 #endif
63 }
64 }
65
66 /*
67 * Read function that will be passed to gif_lib
68 */
69 static int read_bytes_callback(GifFileType* fileType, GifByteType* out,
70 int size) {
71 SkStream* stream = (SkStream*) fileType->UserData;
72 return (int) stream->read(out, size);
73 }
74
75 /*
76 * Check if a there is an index of the color table for a transparent pixel
77 */
78 static int find_trans_index(const SavedImage& image, uint32_t colorCount) {
79 int transIndex = -1;
80 for (int32_t i = 0; i < image.ExtensionBlockCount; i++) {
81 const ExtensionBlock* eb = image.ExtensionBlocks + i;
scroggo 2015/03/25 19:44:49 Is this an array? i.e. Can this be const Extens
msarett 2015/03/26 19:15:56 Yes this is an array. Sorry will fix up this help
82 if (GRAPHICS_EXT_FUNC_CODE == eb->Function && 4 == eb->ByteCount) {
scroggo 2015/03/25 19:44:48 Can you add some comments to this function? I'm ha
msarett 2015/03/26 19:15:56 Done.
scroggo 2015/03/26 20:03:53 This is helpful. Thanks!
83 if (eb->Bytes[0] & 1) {
84 transIndex = (uint8_t) eb->Bytes[3];
85 if (transIndex >= (signed) colorCount) {
scroggo 2015/03/25 19:44:49 I'm confused: transIndex is declared as an int. W
msarett 2015/03/26 19:15:56 Done.
86 transIndex = -1;
87 }
88 break;
scroggo 2015/03/25 19:44:48 This might be a matter of preference, but what if
msarett 2015/03/26 19:15:56 Done.
89 }
90 }
91 }
92 return transIndex;
93 }
94
95 /*
96 * Assumes IsGif was called and returned true
97 * Creates a gif decoder
98 * Reads enough of the stream to determine the image format
99 */
100 SkCodec* SkGifCodec::NewFromStream(SkStream* stream) {
101 // Read gif header, logical screen descriptor, and global color table
102 #if GIFLIB_MAJOR < 5
103 GifFileType* gif = DGifOpen(stream, read_bytes_callback);
104 #else
105 GifFileType* gif = DGifOpen(stream, read_bytes_callback, NULL);
106 #endif
107 if (NULL == gif) {
108 gif_error("DGifOpen failed.\n");
109 return NULL;
110 }
111
112 // Get fields from header
113 const int width = gif->SWidth;
114 const int height = gif->SHeight;
115
116 // Return the codec
117 // FIXME: Gifs are naturally stored as kIndex. We should default to this
scroggo 2015/03/25 19:44:49 Go ahead and return kIndex. I do think it's a litt
msarett 2015/03/26 19:15:56 Done.
scroggo 2015/03/26 20:03:52 So I've realized that in order to return kIndex8,
118 // color type, but currently we only support kN32. Many gifs
119 // specify a color table index for transparent pixels, so we guess
120 // that they are stored as kUnpremul. Gifs without this index are
scroggo 2015/03/25 19:44:49 Refresh my memory: the colors besides the transpar
msarett 2015/03/26 19:15:57 Yes, colors that are not the transparent color are
scroggo 2015/03/26 20:03:53 To be fair, ARGB 0 0 0 0 is a valid premul AND unp
msarett 2015/03/26 22:26:36 Agreed. I was just thinking that if it could be c
scroggo 2015/03/27 13:09:36 Good thinking.
121 // opaque, but we do not have a way of knowing this before decoding.
122 const SkImageInfo& imageInfo = SkImageInfo::Make(width, height,
123 kN32_SkColorType, kUnpremul_SkAlphaType);
124 return SkNEW_ARGS(SkGifCodec, (imageInfo, stream, gif));
125 }
126
127 SkGifCodec::SkGifCodec(const SkImageInfo& srcInfo, SkStream* stream,
128 GifFileType* gif)
129 : INHERITED(srcInfo, stream)
130 , fGif(gif)
131 , fAutoClose(gif)
132 {}
133
134 /*
135 * Checks if the conversion between the input image and the requested output
136 * image has been implemented
137 */
138 static bool conversion_possible(const SkImageInfo& dst,
139 const SkImageInfo& src) {
140 // Ensure that the profile type is unchanged
141 if (dst.profileType() != src.profileType()) {
142 return false;
143 }
144
145 // Check for supported color and alpha types
146 switch (dst.colorType()) {
147 case kN32_SkColorType:
148 return src.alphaType() == dst.alphaType() ||
149 (kPremul_SkAlphaType == dst.alphaType() &&
150 kUnpremul_SkAlphaType == src.alphaType());
151 default:
152 return false;
153 }
154 }
155
156 /*
157 * Initiates the gif decode
158 */
159 SkCodec::Result SkGifCodec::onGetPixels(const SkImageInfo& dstInfo,
160 void* dst, size_t dstRowBytes,
161 const Options&, SkPMColor*, int*) {
162 if (!this->rewindIfNeeded()) {
163 return kCouldNotRewind;
164 }
165 if (dstInfo.dimensions() != this->getInfo().dimensions()) {
166 SkDebugf("Error: scaling not supported.\n");
scroggo 2015/03/25 19:44:48 gif_error?
msarett 2015/03/26 19:15:57 Done.
167 return kInvalidScale;
168 }
169 if (!conversion_possible(dstInfo, this->getInfo())) {
170 SkDebugf("Error: cannot convert input type to output type.\n");
scroggo 2015/03/25 19:44:48 gif_error?
msarett 2015/03/26 19:15:56 Done.
171 return kInvalidConversion;
172 }
173
174 // Use this as a container to hold information about any gif extension
175 // blocks. This generally stores transparency and animation instructions.
176 SavedImage saveExt;
scroggo 2015/03/25 19:44:48 It looks like this is only needed if GIFLIB_MAJOR
msarett 2015/03/26 19:15:56 It's actually needed for both.
scroggo 2015/03/26 20:03:52 But it looks like you've deleted it? Oh, you've m
msarett 2015/03/26 22:26:36 I was grouping auto calls together. It's better d
scroggo 2015/03/27 13:09:36 In general, I would say to declare variables as la
177 saveExt.ExtensionBlocks = NULL;
178 saveExt.ExtensionBlockCount = 0;
179 GifByteType* extData;
180 #if GIFLIB_MAJOR >= 5
181 int extFunction;
182 #endif
183
184 // This is the index used to fill unspecified pixels in the image data.
185 uint32_t fillIndex = fGif->SBackGroundColor;
scroggo 2015/03/25 19:44:48 Can you move this variable closer to where it's us
msarett 2015/03/26 19:15:56 Done.
186
187 // We will loop over components of gif images until we find an image. Once
188 // we find an image, we will decode and return it. While many gif files
189 // contain more than one image, we will simply decode the first image.
190 int width = dstInfo.width();
scroggo 2015/03/25 19:44:48 Can these be const?
msarett 2015/03/26 19:15:55 Yes.
191 int height = dstInfo.height();
192 GifRecordType recordType = UNDEFINED_RECORD_TYPE;
193 while (TERMINATE_RECORD_TYPE != recordType) {
194 // Get the current record type
195 if (GIF_ERROR == DGifGetRecordType(fGif, &recordType)) {
196 gif_error("DGifGetRecordType failed.\n");
197 return kInvalidInput;
198 }
199
200 switch (recordType) {
201 case IMAGE_DESC_RECORD_TYPE: {
202 // Read the image descriptor
203 if (GIF_ERROR == DGifGetImageDesc(fGif)) {
204 gif_error("DGifGetImageDesc failed.\n");
205 return kInvalidInput;
206 }
207
208 // If reading the image descriptor is successful, the image
209 // count will be incremented
210 SkASSERT(fGif->ImageCount >= 1);
211 SavedImage* image = &fGif->SavedImages[fGif->ImageCount - 1];
212
213 // Process the descriptor
214 const GifImageDesc& desc = image->ImageDesc;
215 int imageLeft = desc.Left;
216 int imageTop = desc.Top;
217 int innerWidth = desc.Width;
218 int innerHeight = desc.Height;
219 // Fail on non-positive dimensions
220 if (innerWidth <= 0 || innerHeight <= 0) {
221 gif_error("Invalid dimensions for inner image.\n");
222 return kInvalidInput;
223 }
224 // Treat the following cases as warnings and try to fix
225 if (innerWidth > width) {
226 gif_error("Inner image too wide, shrinking.\n");
scroggo 2015/03/25 19:44:48 Should this be a gif_warning?
msarett 2015/03/26 19:15:57 gif_error, does not return it only prints a messag
scroggo 2015/03/26 20:03:52 We could also have a separate one for errors vs wa
227 // TODO: The original gif decoder expands the overall image
228 // dimensions. Here we shrink the inner image
229 // because we can't change the requested dimensions.
230 // What are the implications of this decision?
scroggo 2015/03/25 19:44:48 I think this is the right decision. At this point,
msarett 2015/03/26 19:15:56 Done.
231 innerWidth = width;
232 imageLeft = 0;
233 } else if (imageLeft + innerWidth > width) {
234 gif_error("Shifting inner image to left to fit.\n");
scroggo 2015/03/25 19:44:49 gif_warning?
msarett 2015/03/26 19:15:56 Acknowledged.
235 imageLeft = width - innerWidth;
236 } else if (imageLeft < 0) {
237 gif_error("Shifting image to right to fit\n");
238 imageLeft = 0;
239 }
240 if (innerHeight > height) {
241 gif_error("Inner image too tall, shrinking.\n");
scroggo 2015/03/25 19:44:48 gif_warning?
msarett 2015/03/26 19:15:56 Acknowledged.
242 // TODO: The original gif decoder expands the overall image
243 // dimensions. Here we shrink the inner image
244 // because we can't change the requested dimensions.
245 // What are the implications of this decision?
246 innerHeight = height;
247 imageTop = 0;
248 } else if (imageTop + innerHeight > height) {
249 gif_error("Shifting inner image up to fit.\n");
250 imageTop = height - innerHeight;
251 } else if (imageTop < 0) {
252 gif_error("Shifting image down to fit\n");
253 imageTop = 0;
254 }
255
256 // Set up the color table
257 uint32_t colorCount = 0;
258 // Allocate maximum storage to deal with invalid indices safely
259 const uint32_t maxColors = 256;
260 SkPMColor colorPtr[maxColors];
261 ColorMapObject* colorMap = fGif->Image.ColorMap;
262 // If there is no local color table, use the global color table
263 if (NULL == colorMap) {
264 colorMap = fGif->SColorMap;
265 }
266 if (NULL != colorMap) {
267 colorCount = colorMap->ColorCount;
268 SkASSERT(colorCount ==
269 (unsigned) (1 << (colorMap->BitsPerPixel)));
270 SkASSERT(colorCount <= 256);
271 uint32_t i;
272 for (i = 0; i < colorCount; i++) {
273 colorPtr[i] = SkPackARGB32(0xFF,
274 colorMap->Colors[i].Red,
275 colorMap->Colors[i].Green,
276 colorMap->Colors[i].Blue);
277 }
278 }
279
280 // Gifs have the option to specify the color at a single
281 // index of the color table as transparent.
282 int transIndex = find_trans_index(saveExt, colorCount);
scroggo 2015/03/25 19:44:49 Maybe add a scope here, since transIndex is only u
msarett 2015/03/26 19:15:57 Done. What is the benefit of adding this type of
283 if (transIndex >= 0) {
284 colorPtr[transIndex] = SK_ColorTRANSPARENT;
285 fillIndex = transIndex;
286 } else if (fillIndex >= colorCount) {
287 fillIndex = 0;
scroggo 2015/03/25 19:44:49 How did you choose 0? Can you add a comment explai
msarett 2015/03/26 19:15:56 I don't really have a great reason. This only occ
288 }
289
290 // Fill in the color table for indices greater than color count.
291 // This allows for predictable, safe behavior.
292 for (uint32_t i = colorCount; i < maxColors; i++) {
293 colorPtr[i] = colorPtr[fillIndex];
294 }
295
296 SkAutoTDelete<SkColorTable> colorTable(
297 SkNEW_ARGS(SkColorTable, (colorPtr, colorCount)));
scroggo 2015/03/25 19:44:48 I don't think you need an SkColorTable here. It wo
msarett 2015/03/26 19:15:56 Done.
298
299 // Check if image is only a subset of the image frame
300 SkAutoTDelete<SkSwizzler> swizzler(NULL);
301 if (innerWidth < width || innerHeight < height) {
302
303 // Modify the destination info
304 const SkImageInfo subsetDstInfo =
305 dstInfo.makeWH(innerWidth, innerHeight);
306
307 // Fill the destination with the fill color
308 // FIXME: This may not be the behavior that we want for
309 // animated gifs where we draw on top of the
310 // previous frame.
311 // FIXME: This code is kN32 specific. We should also
312 // support kIndex because this is the most logical
313 // destination color type for gifs. If we want to
314 // support more than these, that may require
315 // additional cases. There is also an option to add
316 // this functionality to the swizzler, but I
317 // currently think the complexity makes more sense
318 // here.
319 SkColorType dstColorType = dstInfo.colorType();
320 switch (dstColorType) {
321 case kN32_SkColorType:
322 sk_memset32((SkPMColor*) dst,
323 colorTable->operator[](fillIndex),
scroggo 2015/03/25 19:44:48 It's possible that the color you're filling with i
msarett 2015/03/26 19:15:56 Done.
324 dstRowBytes * height / sizeof(SkPMColor));
325 break;
326 // case kIndex_SkColorType:
scroggo 2015/03/25 19:44:48 I'm fine with supporting kIndex. Android *might* d
msarett 2015/03/26 19:15:56 That's interesting, I wasn't aware of that. My th
scroggo 2015/03/26 20:03:53 Sure. Go ahead and ignore my other comments about
327 // memset(dst, fillIndex, dstRowBytes * height);
328 // break;
329 default:
330 SkASSERT(false);
331 break;
332 }
333
334 // Modify the dst pointer
335 const int32_t dstBytesPerPixel =
336 SkColorTypeBytesPerPixel(dstColorType);
337 void* subsetDst = SkTAddOffset<void*>(dst,
338 dstRowBytes * imageTop +
339 dstBytesPerPixel * imageLeft);
340
341 // Create the subset swizzler
342 swizzler.reset(SkSwizzler::CreateSwizzler(
343 SkSwizzler::kIndex, colorTable->readColors(),
344 subsetDstInfo, subsetDst, dstRowBytes,
345 SkImageGenerator::kNo_ZeroInitialized));
scroggo 2015/03/25 19:44:49 Why did you use this instead of the value in Optio
msarett 2015/03/26 19:15:56 No idea. This is clearly wrong, thanks.
346 } else {
347 // Create the fully dimensional swizzler
348 swizzler.reset(SkSwizzler::CreateSwizzler(
349 SkSwizzler::kIndex, colorTable->readColors(), dstInfo,
350 dst, dstRowBytes,
351 SkImageGenerator::kNo_ZeroInitialized));
352 }
353
354 // Stores output from dgiflib and input to the swizzler
355 SkAutoTDeleteArray<uint8_t>
356 buffer(SkNEW_ARRAY(uint8_t, innerWidth));
357
358 // Check the interlace flag and iterate over rows of the input
359 if (fGif->Image.Interlace) {
360 // In interlace mode, the rows of input are rearranged in
361 // the output image. We use an iterator to take care of
362 // the rearranging.
363 SkGifInterlaceIter iter(innerHeight);
364 for (int32_t y = 0; y < innerHeight; y++) {
365 if (GIF_ERROR == DGifGetLine(fGif, buffer.get(),
366 innerWidth)) {
367 gif_error("Could not decode line.\n");
scroggo 2015/03/25 19:44:49 Maybe specify what line you're on?
msarett 2015/03/26 19:15:56 Good idea. I know that information is useful beca
368 // Recover from error by filling remainder of image
369 memset(buffer.get(), fillIndex, innerWidth);
370 for (; y < innerHeight; y++) {
scroggo 2015/03/25 19:44:49 This isn't quite right, is it? You have switched t
msarett 2015/03/26 19:15:55 Nice catch.
371 swizzler->next(buffer.get());
372 }
373 return kIncompleteInput;
374 }
375 swizzler->next(buffer.get(), iter.nextY());
376 }
377 } else {
378 // Standard mode
379 for (int32_t y = 0; y < innerHeight; y++) {
380 if (GIF_ERROR == DGifGetLine(fGif, buffer.get(),
381 innerWidth)) {
382 gif_error("Could not decode line.\n");
383 memset(buffer.get(), fillIndex, innerWidth);
384 for (; y < innerHeight; y++) {
385 swizzler->next(buffer.get());
scroggo 2015/03/25 19:44:48 Is the swizzler overkill here? Could we just use m
msarett 2015/03/26 19:15:57 Done.
386 }
387 return kIncompleteInput;
388 }
389 swizzler->next(buffer.get());
390 }
391 }
392
393 // FIXME: Gif files may have multiple images stored in a single
394 // file. This is most commonly used to enable
395 // animations. Since we are leaving animated gifs as a
396 // TODO, we will return kSuccess after decoding the
397 // first image in the file. This is the same behavior
398 // as SkImageDecoder_libgif.
399 //
400 // Most times this works pretty well, but sometimes it
401 // doesn't. For example, I have an animated test image
402 // where the first image in the file is 1x1, but the
403 // subsequent images are meaningful. This currently
404 // displays the 1x1 image, which is not ideal. Right
405 // now I am leaving this as an issue that will be
406 // addressed when we implement animated gifs.
407 //
408 // It is also possible (not explicitly disallowed in the
409 // specification) that gif files provide multiple
410 // images in a single file that are all meant to be
411 // displayed in the same frame together. I will
412 // currently leave this unimplemented until I find a
413 // test case that expects this behavior.
414 return kSuccess;
415 }
416
417 // Extensions are used to specify special properties of the image
418 // such as transparency or animation.
419 case EXTENSION_RECORD_TYPE:
420 // Read extension data
421 #if GIFLIB_MAJOR < 5
422 if (GIF_ERROR ==
423 DGifGetExtension(fGif, &saveExt.Function, &extData)) {
424 #else
425 if (GIF_ERROR ==
426 DGifGetExtension(fGif, &extFunction, &extData)) {
427 #endif
428 gif_error("Could not get extension.\n");
429 return kInvalidInput;
430 }
431
432 // Create an extension block with our data
433 while (NULL != extData) {
434 // Add a single block
435 #if GIFLIB_MAJOR < 5
436 if (GIF_ERROR == AddExtensionBlock(&saveExt, extData[0],
437 &extData[1])) {
438 #else
439 if (GIF_ERROR ==
440 GifAddExtensionBlock(&saveExt.ExtensionBlockCount,
441 &saveExt.ExtensionBlocks, extFunction, extData[0],
442 &extData[1])) {
443 #endif
444 gif_error("Could not add extension block.\n");
445 return kInvalidInput;
446 }
447 // Move to the next block
448 if (GIF_ERROR == DGifGetExtensionNext(fGif, &extData)) {
449 gif_error("Could not get next extension.\n");
450 return kInvalidInput;
451 }
452 #if GIFLIB_MAJOR < 5
453 saveExt.Function = 0;
454 #endif
455 }
456 break;
457
458 // Signals the end of the gif file
459 case TERMINATE_RECORD_TYPE:
scroggo 2015/03/25 19:44:49 Handle skbug.com/3534?
msarett 2015/03/26 19:15:56 I added a check for 0x0 images in NewFromStream.
460 break;
461
462 default:
463 break;
464 }
465 }
466
467 // Clean up memory
468 free_extension(&saveExt);
scroggo 2015/03/25 19:44:48 Can you put this in an SkAutoCallVProc?
msarett 2015/03/26 19:15:56 Yes definitely. The way it is set up now seems to
scroggo 2015/03/26 20:03:53 Oh, you mean before updating? And now it's fixed?
469
470 return kSuccess;
scroggo 2015/03/25 19:56:58 This function is over 300 lines. Can it be broken
msarett 2015/03/26 19:15:56 This is unfortunate. I can't find a good way to b
scroggo 2015/03/26 20:03:53 Only break it up if there's a clear way to do it.
471 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698