blob: 113804462a88f54dfefec48905328b79c568c741 [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 "SkMovie.h"
19#include "SkColor.h"
20#include "SkColorPriv.h"
21#include "SkStream.h"
22#include "SkTemplates.h"
23
24#include "gif_lib.h"
25
26class SkGIFMovie : public SkMovie {
27public:
28 SkGIFMovie(SkStream* stream);
29 virtual ~SkGIFMovie();
30
31protected:
32 virtual bool onGetInfo(Info*);
33 virtual bool onSetTime(SkMSec);
34 virtual bool onGetBitmap(SkBitmap*);
35
36private:
37 GifFileType* fGIF;
38 SavedImage* fCurrSavedImage;
39};
40
reed@android.com8a1c16f2008-12-17 15:59:43 +000041static int Decode(GifFileType* fileType, GifByteType* out, int size) {
42 SkStream* stream = (SkStream*) fileType->UserData;
43 return (int) stream->read(out, size);
44}
45
46SkGIFMovie::SkGIFMovie(SkStream* stream)
47{
48 fGIF = DGifOpen( stream, Decode );
49 if (NULL == fGIF)
50 return;
51
52 if (DGifSlurp(fGIF) != GIF_OK)
53 {
54 DGifCloseFile(fGIF);
55 fGIF = NULL;
56 }
57 fCurrSavedImage = NULL;
58}
59
60SkGIFMovie::~SkGIFMovie()
61{
62 if (fGIF)
63 DGifCloseFile(fGIF);
64}
65
66static SkMSec savedimage_duration(const SavedImage* image)
67{
68 for (int j = 0; j < image->ExtensionBlockCount; j++)
69 {
70 if (image->ExtensionBlocks[j].Function == GRAPHICS_EXT_FUNC_CODE)
71 {
72 int size = image->ExtensionBlocks[j].ByteCount;
73 SkASSERT(size >= 4);
74 const uint8_t* b = (const uint8_t*)image->ExtensionBlocks[j].Bytes;
75 return ((b[2] << 8) | b[1]) * 10;
76 }
77 }
78 return 0;
79}
80
81bool SkGIFMovie::onGetInfo(Info* info)
82{
83 if (NULL == fGIF)
84 return false;
85
86 SkMSec dur = 0;
87 for (int i = 0; i < fGIF->ImageCount; i++)
88 dur += savedimage_duration(&fGIF->SavedImages[i]);
89
90 info->fDuration = dur;
91 info->fWidth = fGIF->SWidth;
92 info->fHeight = fGIF->SHeight;
93 info->fIsOpaque = false; // how to compute?
94 return true;
95}
96
97bool SkGIFMovie::onSetTime(SkMSec time)
98{
99 if (NULL == fGIF)
100 return false;
101
102 SkMSec dur = 0;
103 for (int i = 0; i < fGIF->ImageCount; i++)
104 {
105 dur += savedimage_duration(&fGIF->SavedImages[i]);
106 if (dur >= time)
107 {
108 SavedImage* prev = fCurrSavedImage;
109 fCurrSavedImage = &fGIF->SavedImages[i];
110 return prev != fCurrSavedImage;
111 }
112 }
113 fCurrSavedImage = &fGIF->SavedImages[fGIF->ImageCount - 1];
114 return true;
115}
116
117bool SkGIFMovie::onGetBitmap(SkBitmap* bm)
118{
119 GifFileType* gif = fGIF;
120 if (NULL == gif)
121 return false;
122
123 // should we check for the Image cmap or the global (SColorMap) first?
124 ColorMapObject* cmap = gif->SColorMap;
125 if (cmap == NULL)
126 cmap = gif->Image.ColorMap;
127
128 if (cmap == NULL || gif->ImageCount < 1 || cmap->ColorCount != (1 << cmap->BitsPerPixel))
129 {
130 SkASSERT(!"bad colortable setup");
131 return false;
132 }
133
134 const int width = gif->SWidth;
135 const int height = gif->SHeight;
136 if (width <= 0 || height <= 0) {
137 return false;
138 }
139
140 SavedImage* gif_image = fCurrSavedImage;
141 SkBitmap::Config config = SkBitmap::kIndex8_Config;
142
143 SkColorTable* colorTable = SkNEW_ARGS(SkColorTable, (cmap->ColorCount));
144 SkAutoUnref aur(colorTable);
145
146 bm->setConfig(config, width, height, 0);
147 if (!bm->allocPixels(colorTable)) {
148 return false;
149 }
150
151 int transparent = -1;
152 for (int i = 0; i < gif_image->ExtensionBlockCount; ++i) {
153 ExtensionBlock* eb = gif_image->ExtensionBlocks + i;
154 if (eb->Function == 0xF9 &&
155 eb->ByteCount == 4) {
156 bool has_transparency = ((eb->Bytes[0] & 1) == 1);
157 if (has_transparency) {
158 transparent = (unsigned char)eb->Bytes[3];
159 }
160 }
161 }
162
163 SkPMColor* colorPtr = colorTable->lockColors();
164
165 if (transparent >= 0)
166 memset(colorPtr, 0, cmap->ColorCount * 4);
167 else
168 colorTable->setFlags(colorTable->getFlags() | SkColorTable::kColorsAreOpaque_Flag);
169
170 for (int index = 0; index < cmap->ColorCount; index++)
171 {
172 if (transparent != index)
173 colorPtr[index] = SkPackARGB32(0xFF, cmap->Colors[index].Red,
174 cmap->Colors[index].Green, cmap->Colors[index].Blue);
175 }
176 colorTable->unlockColors(true);
177
178 unsigned char* in = (unsigned char*)gif_image->RasterBits;
179 unsigned char* out = bm->getAddr8(0, 0);
180 if (gif->Image.Interlace) {
181
182 // deinterlace
183 int row;
184 // group 1 - every 8th row, starting with row 0
185 for (row = 0; row < height; row += 8) {
186 memcpy(out + width * row, in, width);
187 in += width;
188 }
189
190 // group 2 - every 8th row, starting with row 4
191 for (row = 4; row < height; row += 8) {
192 memcpy(out + width * row, in, width);
193 in += width;
194 }
195
196 // group 3 - every 4th row, starting with row 2
197 for (row = 2; row < height; row += 4) {
198 memcpy(out + width * row, in, width);
199 in += width;
200 }
201
202 for (row = 1; row < height; row += 2) {
203 memcpy(out + width * row, in, width);
204 in += width;
205 }
206
207 } else {
208 memcpy(out, in, width * height);
209 }
210 return true;
211}
reed@android.com8540a792009-06-18 17:53:12 +0000212
213///////////////////////////////////////////////////////////////////////////////
214
215#include "SkTRegistry.h"
216
217SkMovie* Factory(SkStream* stream) {
218 char buf[GIF_STAMP_LEN];
219 if (stream->read(buf, GIF_STAMP_LEN) == GIF_STAMP_LEN) {
220 if (memcmp(GIF_STAMP, buf, GIF_STAMP_LEN) == 0 ||
reed@android.com5df93ea2009-06-18 18:52:22 +0000221 memcmp(GIF87_STAMP, buf, GIF_STAMP_LEN) == 0 ||
222 memcmp(GIF89_STAMP, buf, GIF_STAMP_LEN) == 0) {
reed@android.coma16cb972009-06-18 20:26:58 +0000223 // must rewind here, since our construct wants to re-read the data
224 stream->rewind();
reed@android.com8540a792009-06-18 17:53:12 +0000225 return SkNEW_ARGS(SkGIFMovie, (stream));
226 }
227 }
228 return NULL;
229}
230
231static SkTRegistry<SkMovie*, SkStream*> gReg(Factory);