blob: d2470ccf1dff54a8c2f6b7ae0b47c79e7a19692e [file] [log] [blame]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001/* libs/graphics/images/SkImageDecoder_libgif.cpp
2**
3** Copyright 2006, The Android Open Source Project
4**
5** Licensed under the Apache License, Version 2.0 (the "License");
6** you may not use this file except in compliance with the License.
7** You may obtain a copy of the License at
8**
9** http://www.apache.org/licenses/LICENSE-2.0
10**
11** Unless required by applicable law or agreed to in writing, software
12** distributed under the License is distributed on an "AS IS" BASIS,
13** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14** See the License for the specific language governing permissions and
15** limitations under the License.
16*/
17
18#include "SkImageDecoder.h"
19#include "SkColor.h"
20#include "SkColorPriv.h"
21#include "SkStream.h"
22#include "SkTemplates.h"
23#include "SkPackBits.h"
24
25#include "gif_lib.h"
26
27class SkGIFImageDecoder : public SkImageDecoder {
28public:
29 virtual Format getFormat() const {
30 return kGIF_Format;
31 }
32
33protected:
reed@android.com3f1f06a2010-03-03 21:04:12 +000034 virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +000035};
36
37static const uint8_t gStartingIterlaceYValue[] = {
38 0, 4, 2, 1
39};
40static const uint8_t gDeltaIterlaceYValue[] = {
41 8, 8, 4, 2
42};
43
44/* Implement the GIF interlace algorithm in an iterator.
45 1) grab every 8th line beginning at 0
46 2) grab every 8th line beginning at 4
47 3) grab every 4th line beginning at 2
48 4) grab every 2nd line beginning at 1
49*/
50class GifInterlaceIter {
51public:
52 GifInterlaceIter(int height) : fHeight(height) {
53 fStartYPtr = gStartingIterlaceYValue;
54 fDeltaYPtr = gDeltaIterlaceYValue;
55
56 fCurrY = *fStartYPtr++;
57 fDeltaY = *fDeltaYPtr++;
58 }
59
60 int currY() const {
61 SkASSERT(fStartYPtr);
62 SkASSERT(fDeltaYPtr);
63 return fCurrY;
64 }
65
66 void next() {
67 SkASSERT(fStartYPtr);
68 SkASSERT(fDeltaYPtr);
69
70 int y = fCurrY + fDeltaY;
71 // We went from an if statement to a while loop so that we iterate
72 // through fStartYPtr until a valid row is found. This is so that images
73 // that are smaller than 5x5 will not trash memory.
74 while (y >= fHeight) {
75 if (gStartingIterlaceYValue +
76 SK_ARRAY_COUNT(gStartingIterlaceYValue) == fStartYPtr) {
77 // we done
78 SkDEBUGCODE(fStartYPtr = NULL;)
79 SkDEBUGCODE(fDeltaYPtr = NULL;)
80 y = 0;
81 } else {
82 y = *fStartYPtr++;
83 fDeltaY = *fDeltaYPtr++;
84 }
85 }
86 fCurrY = y;
87 }
88
89private:
90 const int fHeight;
91 int fCurrY;
92 int fDeltaY;
93 const uint8_t* fStartYPtr;
94 const uint8_t* fDeltaYPtr;
95};
96
97///////////////////////////////////////////////////////////////////////////////
98
99//#define GIF_STAMP "GIF" /* First chars in file - GIF stamp. */
100//#define GIF_STAMP_LEN (sizeof(GIF_STAMP) - 1)
101
102static int DecodeCallBackProc(GifFileType* fileType, GifByteType* out,
103 int size) {
104 SkStream* stream = (SkStream*) fileType->UserData;
105 return (int) stream->read(out, size);
106}
107
108void CheckFreeExtension(SavedImage* Image) {
109 if (Image->ExtensionBlocks) {
110 FreeExtension(Image);
111 }
112}
113
114// return NULL on failure
115static const ColorMapObject* find_colormap(const GifFileType* gif) {
116 const ColorMapObject* cmap = gif->Image.ColorMap;
117 if (NULL == cmap) {
118 cmap = gif->SColorMap;
119 }
120 // some sanity checks
reed@android.combc7d2fb2010-02-05 15:43:07 +0000121 if (cmap && ((unsigned)cmap->ColorCount > 256 ||
122 cmap->ColorCount != (1 << cmap->BitsPerPixel))) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000123 cmap = NULL;
124 }
125 return cmap;
126}
127
128// return -1 if not found (i.e. we're completely opaque)
129static int find_transpIndex(const SavedImage& image, int colorCount) {
130 int transpIndex = -1;
131 for (int i = 0; i < image.ExtensionBlockCount; ++i) {
132 const ExtensionBlock* eb = image.ExtensionBlocks + i;
133 if (eb->Function == 0xF9 && eb->ByteCount == 4) {
134 if (eb->Bytes[0] & 1) {
135 transpIndex = (unsigned char)eb->Bytes[3];
136 // check for valid transpIndex
137 if (transpIndex >= colorCount) {
138 transpIndex = -1;
139 }
140 break;
141 }
142 }
143 }
144 return transpIndex;
145}
146
147static bool error_return(GifFileType* gif, const SkBitmap& bm,
148 const char msg[]) {
149#if 0
150 SkDebugf("libgif error <%s> bitmap [%d %d] pixels %p colortable %p\n",
151 msg, bm.width(), bm.height(), bm.getPixels(), bm.getColorTable());
152#endif
153 return false;
154}
155
reed@android.com3f1f06a2010-03-03 21:04:12 +0000156bool SkGIFImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* bm, Mode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000157 GifFileType* gif = DGifOpen(sk_stream, DecodeCallBackProc);
158 if (NULL == gif) {
159 return error_return(gif, *bm, "DGifOpen");
160 }
161
162 SkAutoTCallIProc<GifFileType, DGifCloseFile> acp(gif);
163
164 SavedImage temp_save;
165 temp_save.ExtensionBlocks=NULL;
166 temp_save.ExtensionBlockCount=0;
167 SkAutoTCallVProc<SavedImage, CheckFreeExtension> acp2(&temp_save);
168
169 int width, height;
170 GifRecordType recType;
171 GifByteType *extData;
reed@android.com9781ca52009-04-14 14:28:22 +0000172 int transpIndex = -1; // -1 means we don't have it (yet)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000173
174 do {
175 if (DGifGetRecordType(gif, &recType) == GIF_ERROR) {
176 return error_return(gif, *bm, "DGifGetRecordType");
177 }
178
179 switch (recType) {
180 case IMAGE_DESC_RECORD_TYPE: {
181 if (DGifGetImageDesc(gif) == GIF_ERROR) {
182 return error_return(gif, *bm, "IMAGE_DESC_RECORD_TYPE");
183 }
184
185 if (gif->ImageCount < 1) { // sanity check
186 return error_return(gif, *bm, "ImageCount < 1");
187 }
188
189 width = gif->SWidth;
190 height = gif->SHeight;
191 if (width <= 0 || height <= 0 ||
192 !this->chooseFromOneChoice(SkBitmap::kIndex8_Config,
193 width, height)) {
194 return error_return(gif, *bm, "chooseFromOneChoice");
195 }
196
197 bm->setConfig(SkBitmap::kIndex8_Config, width, height);
198 if (SkImageDecoder::kDecodeBounds_Mode == mode)
199 return true;
200
201 SavedImage* image = &gif->SavedImages[gif->ImageCount-1];
202 const GifImageDesc& desc = image->ImageDesc;
203
204 // check for valid descriptor
205 if ( (desc.Top | desc.Left) < 0 ||
206 desc.Left + desc.Width > width ||
207 desc.Top + desc.Height > height) {
208 return error_return(gif, *bm, "TopLeft");
209 }
210
211 // now we decode the colortable
212 int colorCount = 0;
213 {
214 const ColorMapObject* cmap = find_colormap(gif);
215 if (NULL == cmap) {
216 return error_return(gif, *bm, "null cmap");
217 }
218
219 colorCount = cmap->ColorCount;
220 SkColorTable* ctable = SkNEW_ARGS(SkColorTable, (colorCount));
221 SkPMColor* colorPtr = ctable->lockColors();
222 for (int index = 0; index < colorCount; index++)
223 colorPtr[index] = SkPackARGB32(0xFF,
224 cmap->Colors[index].Red,
225 cmap->Colors[index].Green,
226 cmap->Colors[index].Blue);
227
reed@android.com9781ca52009-04-14 14:28:22 +0000228 transpIndex = find_transpIndex(temp_save, colorCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000229 if (transpIndex < 0)
230 ctable->setFlags(ctable->getFlags() | SkColorTable::kColorsAreOpaque_Flag);
231 else
232 colorPtr[transpIndex] = 0; // ram in a transparent SkPMColor
233 ctable->unlockColors(true);
234
235 SkAutoUnref aurts(ctable);
236 if (!this->allocPixelRef(bm, ctable)) {
237 return error_return(gif, *bm, "allocPixelRef");
238 }
239 }
240
241 SkAutoLockPixels alp(*bm);
242
243 // time to decode the scanlines
244 //
245 uint8_t* scanline = bm->getAddr8(0, 0);
246 const int rowBytes = bm->rowBytes();
247 const int innerWidth = desc.Width;
248 const int innerHeight = desc.Height;
249
250 // abort if either inner dimension is <= 0
251 if (innerWidth <= 0 || innerHeight <= 0) {
252 return error_return(gif, *bm, "non-pos inner width/height");
253 }
254
255 // are we only a subset of the total bounds?
256 if ((desc.Top | desc.Left) > 0 ||
257 innerWidth < width || innerHeight < height)
258 {
reed@android.com9781ca52009-04-14 14:28:22 +0000259 int fill;
260 if (transpIndex >= 0) {
261 fill = transpIndex;
262 } else {
263 fill = gif->SBackGroundColor;
264 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000265 // check for valid fill index/color
reed@android.com9781ca52009-04-14 14:28:22 +0000266 if (static_cast<unsigned>(fill) >=
267 static_cast<unsigned>(colorCount)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000268 fill = 0;
269 }
reed@android.com9781ca52009-04-14 14:28:22 +0000270 memset(scanline, fill, bm->getSize());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000271 // bump our starting address
272 scanline += desc.Top * rowBytes + desc.Left;
273 }
274
275 // now decode each scanline
276 if (gif->Image.Interlace)
277 {
278 GifInterlaceIter iter(innerHeight);
279 for (int y = 0; y < innerHeight; y++)
280 {
281 uint8_t* row = scanline + iter.currY() * rowBytes;
282 if (DGifGetLine(gif, row, innerWidth) == GIF_ERROR) {
283 return error_return(gif, *bm, "interlace DGifGetLine");
284 }
285 iter.next();
286 }
287 }
288 else
289 {
290 // easy, non-interlace case
291 for (int y = 0; y < innerHeight; y++) {
292 if (DGifGetLine(gif, scanline, innerWidth) == GIF_ERROR) {
293 return error_return(gif, *bm, "DGifGetLine");
294 }
295 scanline += rowBytes;
296 }
297 }
298 goto DONE;
299 } break;
300
301 case EXTENSION_RECORD_TYPE:
302 if (DGifGetExtension(gif, &temp_save.Function,
303 &extData) == GIF_ERROR) {
304 return error_return(gif, *bm, "DGifGetExtension");
305 }
306
307 while (extData != NULL) {
308 /* Create an extension block with our data */
309 if (AddExtensionBlock(&temp_save, extData[0],
310 &extData[1]) == GIF_ERROR) {
311 return error_return(gif, *bm, "AddExtensionBlock");
312 }
313 if (DGifGetExtensionNext(gif, &extData) == GIF_ERROR) {
314 return error_return(gif, *bm, "DGifGetExtensionNext");
315 }
316 temp_save.Function = 0;
317 }
318 break;
319
320 case TERMINATE_RECORD_TYPE:
321 break;
322
323 default: /* Should be trapped by DGifGetRecordType */
324 break;
325 }
326 } while (recType != TERMINATE_RECORD_TYPE);
327
328DONE:
329 return true;
330}
331
332///////////////////////////////////////////////////////////////////////////////
333
reed@android.com00bf85a2009-01-22 13:04:56 +0000334#include "SkTRegistry.h"
335
336static SkImageDecoder* Factory(SkStream* stream) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000337 char buf[GIF_STAMP_LEN];
338 if (stream->read(buf, GIF_STAMP_LEN) == GIF_STAMP_LEN) {
339 if (memcmp(GIF_STAMP, buf, GIF_STAMP_LEN) == 0 ||
340 memcmp(GIF87_STAMP, buf, GIF_STAMP_LEN) == 0 ||
341 memcmp(GIF89_STAMP, buf, GIF_STAMP_LEN) == 0) {
342 return SkNEW(SkGIFImageDecoder);
343 }
344 }
345 return NULL;
346}
347
reed@android.com00bf85a2009-01-22 13:04:56 +0000348static SkTRegistry<SkImageDecoder*, SkStream*> gReg(Factory);