1/* 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: 34 virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode mode); 35}; 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 121 if (cmap && ((unsigned)cmap->ColorCount > 256 || 122 cmap->ColorCount != (1 << cmap->BitsPerPixel))) { 123 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 156bool SkGIFImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* bm, Mode mode) { 157 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; 172 int transpIndex = -1; // -1 means we don't have it (yet) 173 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 228 transpIndex = find_transpIndex(temp_save, colorCount); 229 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 { 259 int fill; 260 if (transpIndex >= 0) { 261 fill = transpIndex; 262 } else { 263 fill = gif->SBackGroundColor; 264 } 265 // check for valid fill index/color 266 if (static_cast<unsigned>(fill) >= 267 static_cast<unsigned>(colorCount)) { 268 fill = 0; 269 } 270 memset(scanline, fill, bm->getSize()); 271 // 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 334#include "SkTRegistry.h" 335 336static SkImageDecoder* Factory(SkStream* stream) { 337 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 348static SkTRegistry<SkImageDecoder*, SkStream*> gReg(Factory); 349