michael@0: /* michael@0: * Copyright 2006 The Android Open Source Project michael@0: * michael@0: * Use of this source code is governed by a BSD-style license that can be michael@0: * found in the LICENSE file. michael@0: */ michael@0: michael@0: #include "SkColor.h" michael@0: #include "SkColorPriv.h" michael@0: #include "SkColorTable.h" michael@0: #include "SkImageDecoder.h" michael@0: #include "SkRTConf.h" michael@0: #include "SkScaledBitmapSampler.h" michael@0: #include "SkStream.h" michael@0: #include "SkTemplates.h" michael@0: #include "SkUtils.h" michael@0: michael@0: #include "gif_lib.h" michael@0: michael@0: class SkGIFImageDecoder : public SkImageDecoder { michael@0: public: michael@0: virtual Format getFormat() const SK_OVERRIDE { michael@0: return kGIF_Format; michael@0: } michael@0: michael@0: protected: michael@0: virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode mode) SK_OVERRIDE; michael@0: michael@0: private: michael@0: typedef SkImageDecoder INHERITED; michael@0: }; michael@0: michael@0: static const uint8_t gStartingIterlaceYValue[] = { michael@0: 0, 4, 2, 1 michael@0: }; michael@0: static const uint8_t gDeltaIterlaceYValue[] = { michael@0: 8, 8, 4, 2 michael@0: }; michael@0: michael@0: SK_CONF_DECLARE(bool, c_suppressGIFImageDecoderWarnings, michael@0: "images.gif.suppressDecoderWarnings", true, michael@0: "Suppress GIF warnings and errors when calling image decode " michael@0: "functions."); michael@0: michael@0: michael@0: /* Implement the GIF interlace algorithm in an iterator. michael@0: 1) grab every 8th line beginning at 0 michael@0: 2) grab every 8th line beginning at 4 michael@0: 3) grab every 4th line beginning at 2 michael@0: 4) grab every 2nd line beginning at 1 michael@0: */ michael@0: class GifInterlaceIter { michael@0: public: michael@0: GifInterlaceIter(int height) : fHeight(height) { michael@0: fStartYPtr = gStartingIterlaceYValue; michael@0: fDeltaYPtr = gDeltaIterlaceYValue; michael@0: michael@0: fCurrY = *fStartYPtr++; michael@0: fDeltaY = *fDeltaYPtr++; michael@0: } michael@0: michael@0: int currY() const { michael@0: SkASSERT(fStartYPtr); michael@0: SkASSERT(fDeltaYPtr); michael@0: return fCurrY; michael@0: } michael@0: michael@0: void next() { michael@0: SkASSERT(fStartYPtr); michael@0: SkASSERT(fDeltaYPtr); michael@0: michael@0: int y = fCurrY + fDeltaY; michael@0: // We went from an if statement to a while loop so that we iterate michael@0: // through fStartYPtr until a valid row is found. This is so that images michael@0: // that are smaller than 5x5 will not trash memory. michael@0: while (y >= fHeight) { michael@0: if (gStartingIterlaceYValue + michael@0: SK_ARRAY_COUNT(gStartingIterlaceYValue) == fStartYPtr) { michael@0: // we done michael@0: SkDEBUGCODE(fStartYPtr = NULL;) michael@0: SkDEBUGCODE(fDeltaYPtr = NULL;) michael@0: y = 0; michael@0: } else { michael@0: y = *fStartYPtr++; michael@0: fDeltaY = *fDeltaYPtr++; michael@0: } michael@0: } michael@0: fCurrY = y; michael@0: } michael@0: michael@0: private: michael@0: const int fHeight; michael@0: int fCurrY; michael@0: int fDeltaY; michael@0: const uint8_t* fStartYPtr; michael@0: const uint8_t* fDeltaYPtr; michael@0: }; michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: static int DecodeCallBackProc(GifFileType* fileType, GifByteType* out, michael@0: int size) { michael@0: SkStream* stream = (SkStream*) fileType->UserData; michael@0: return (int) stream->read(out, size); michael@0: } michael@0: michael@0: void CheckFreeExtension(SavedImage* Image) { michael@0: if (Image->ExtensionBlocks) { michael@0: #if GIFLIB_MAJOR < 5 michael@0: FreeExtension(Image); michael@0: #else michael@0: GifFreeExtensions(&Image->ExtensionBlockCount, &Image->ExtensionBlocks); michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: // return NULL on failure michael@0: static const ColorMapObject* find_colormap(const GifFileType* gif) { michael@0: const ColorMapObject* cmap = gif->Image.ColorMap; michael@0: if (NULL == cmap) { michael@0: cmap = gif->SColorMap; michael@0: } michael@0: michael@0: if (NULL == cmap) { michael@0: // no colormap found michael@0: return NULL; michael@0: } michael@0: // some sanity checks michael@0: if (cmap && ((unsigned)cmap->ColorCount > 256 || michael@0: cmap->ColorCount != (1 << cmap->BitsPerPixel))) { michael@0: cmap = NULL; michael@0: } michael@0: return cmap; michael@0: } michael@0: michael@0: // return -1 if not found (i.e. we're completely opaque) michael@0: static int find_transpIndex(const SavedImage& image, int colorCount) { michael@0: int transpIndex = -1; michael@0: for (int i = 0; i < image.ExtensionBlockCount; ++i) { michael@0: const ExtensionBlock* eb = image.ExtensionBlocks + i; michael@0: if (eb->Function == 0xF9 && eb->ByteCount == 4) { michael@0: if (eb->Bytes[0] & 1) { michael@0: transpIndex = (unsigned char)eb->Bytes[3]; michael@0: // check for valid transpIndex michael@0: if (transpIndex >= colorCount) { michael@0: transpIndex = -1; michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: return transpIndex; michael@0: } michael@0: michael@0: static bool error_return(const SkBitmap& bm, const char msg[]) { michael@0: if (!c_suppressGIFImageDecoderWarnings) { michael@0: SkDebugf("libgif error [%s] bitmap [%d %d] pixels %p colortable %p\n", michael@0: msg, bm.width(), bm.height(), bm.getPixels(), michael@0: bm.getColorTable()); michael@0: } michael@0: return false; michael@0: } michael@0: static void gif_warning(const SkBitmap& bm, const char msg[]) { michael@0: if (!c_suppressGIFImageDecoderWarnings) { michael@0: SkDebugf("libgif warning [%s] bitmap [%d %d] pixels %p colortable %p\n", michael@0: msg, bm.width(), bm.height(), bm.getPixels(), michael@0: bm.getColorTable()); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Skip rows in the source gif image. michael@0: * @param gif Source image. michael@0: * @param dst Scratch output needed by gif library call. Must be >= width bytes. michael@0: * @param width Bytes per row in the source image. michael@0: * @param rowsToSkip Number of rows to skip. michael@0: * @return True on success, false on GIF_ERROR. michael@0: */ michael@0: static bool skip_src_rows(GifFileType* gif, uint8_t* dst, int width, int rowsToSkip) { michael@0: for (int i = 0; i < rowsToSkip; i++) { michael@0: if (DGifGetLine(gif, dst, width) == GIF_ERROR) { michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * GIFs with fewer then 256 color entries will sometimes index out of michael@0: * bounds of the color table (this is malformed, but libgif does not michael@0: * check sicne it is rare). This function checks for this error and michael@0: * fixes it. This makes the output image consistantly deterministic. michael@0: */ michael@0: static void sanitize_indexed_bitmap(SkBitmap* bm) { michael@0: if ((SkBitmap::kIndex8_Config == bm->config()) && !(bm->empty())) { michael@0: SkAutoLockPixels alp(*bm); michael@0: if (NULL != bm->getPixels()) { michael@0: SkColorTable* ct = bm->getColorTable(); // Index8 must have it. michael@0: SkASSERT(ct != NULL); michael@0: uint32_t count = ct->count(); michael@0: SkASSERT(count > 0); michael@0: SkASSERT(count <= 0x100); michael@0: if (count != 0x100) { // Full colortables can't go wrong. michael@0: // Count is a power of 2; asserted elsewhere. michael@0: uint8_t byteMask = (~(count - 1)); michael@0: bool warning = false; michael@0: uint8_t* addr = static_cast(bm->getPixels()); michael@0: int height = bm->height(); michael@0: int width = bm->width(); michael@0: size_t rowBytes = bm->rowBytes(); michael@0: while (--height >= 0) { michael@0: uint8_t* ptr = addr; michael@0: int x = width; michael@0: while (--x >= 0) { michael@0: if (0 != ((*ptr) & byteMask)) { michael@0: warning = true; michael@0: *ptr = 0; michael@0: } michael@0: ++ptr; michael@0: } michael@0: addr += rowBytes; michael@0: } michael@0: if (warning) { michael@0: gif_warning(*bm, "Index out of bounds."); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool SkGIFImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* bm, Mode mode) { michael@0: #if GIFLIB_MAJOR < 5 michael@0: GifFileType* gif = DGifOpen(sk_stream, DecodeCallBackProc); michael@0: #else michael@0: GifFileType* gif = DGifOpen(sk_stream, DecodeCallBackProc, NULL); michael@0: #endif michael@0: if (NULL == gif) { michael@0: return error_return(*bm, "DGifOpen"); michael@0: } michael@0: michael@0: SkAutoTCallIProc acp(gif); michael@0: michael@0: SavedImage temp_save; michael@0: temp_save.ExtensionBlocks=NULL; michael@0: temp_save.ExtensionBlockCount=0; michael@0: SkAutoTCallVProc acp2(&temp_save); michael@0: michael@0: int width, height; michael@0: GifRecordType recType; michael@0: GifByteType *extData; michael@0: #if GIFLIB_MAJOR >= 5 michael@0: int extFunction; michael@0: #endif michael@0: int transpIndex = -1; // -1 means we don't have it (yet) michael@0: int fillIndex = gif->SBackGroundColor; michael@0: michael@0: do { michael@0: if (DGifGetRecordType(gif, &recType) == GIF_ERROR) { michael@0: return error_return(*bm, "DGifGetRecordType"); michael@0: } michael@0: michael@0: switch (recType) { michael@0: case IMAGE_DESC_RECORD_TYPE: { michael@0: if (DGifGetImageDesc(gif) == GIF_ERROR) { michael@0: return error_return(*bm, "IMAGE_DESC_RECORD_TYPE"); michael@0: } michael@0: michael@0: if (gif->ImageCount < 1) { // sanity check michael@0: return error_return(*bm, "ImageCount < 1"); michael@0: } michael@0: michael@0: width = gif->SWidth; michael@0: height = gif->SHeight; michael@0: michael@0: SavedImage* image = &gif->SavedImages[gif->ImageCount-1]; michael@0: const GifImageDesc& desc = image->ImageDesc; michael@0: michael@0: int imageLeft = desc.Left; michael@0: int imageTop = desc.Top; michael@0: const int innerWidth = desc.Width; michael@0: const int innerHeight = desc.Height; michael@0: if (innerWidth <= 0 || innerHeight <= 0) { michael@0: return error_return(*bm, "invalid dimensions"); michael@0: } michael@0: michael@0: // check for valid descriptor michael@0: if (innerWidth > width) { michael@0: gif_warning(*bm, "image too wide, expanding output to size"); michael@0: width = innerWidth; michael@0: imageLeft = 0; michael@0: } else if (imageLeft + innerWidth > width) { michael@0: gif_warning(*bm, "shifting image left to fit"); michael@0: imageLeft = width - innerWidth; michael@0: } else if (imageLeft < 0) { michael@0: gif_warning(*bm, "shifting image right to fit"); michael@0: imageLeft = 0; michael@0: } michael@0: michael@0: michael@0: if (innerHeight > height) { michael@0: gif_warning(*bm, "image too tall, expanding output to size"); michael@0: height = innerHeight; michael@0: imageTop = 0; michael@0: } else if (imageTop + innerHeight > height) { michael@0: gif_warning(*bm, "shifting image up to fit"); michael@0: imageTop = height - innerHeight; michael@0: } else if (imageTop < 0) { michael@0: gif_warning(*bm, "shifting image down to fit"); michael@0: imageTop = 0; michael@0: } michael@0: michael@0: // FIXME: We could give the caller a choice of images or configs. michael@0: if (!this->chooseFromOneChoice(SkBitmap::kIndex8_Config, width, height)) { michael@0: return error_return(*bm, "chooseFromOneChoice"); michael@0: } michael@0: michael@0: SkScaledBitmapSampler sampler(width, height, this->getSampleSize()); michael@0: michael@0: bm->setConfig(SkBitmap::kIndex8_Config, sampler.scaledWidth(), michael@0: sampler.scaledHeight()); michael@0: michael@0: if (SkImageDecoder::kDecodeBounds_Mode == mode) { michael@0: return true; michael@0: } michael@0: michael@0: michael@0: // now we decode the colortable michael@0: int colorCount = 0; michael@0: { michael@0: // Declare colorPtr here for scope. michael@0: SkPMColor colorPtr[256]; // storage for worst-case michael@0: const ColorMapObject* cmap = find_colormap(gif); michael@0: SkAlphaType alphaType = kOpaque_SkAlphaType; michael@0: if (cmap != NULL) { michael@0: SkASSERT(cmap->ColorCount == (1 << (cmap->BitsPerPixel))); michael@0: colorCount = cmap->ColorCount; michael@0: if (colorCount > 256) { michael@0: colorCount = 256; // our kIndex8 can't support more michael@0: } michael@0: for (int index = 0; index < colorCount; index++) { michael@0: colorPtr[index] = SkPackARGB32(0xFF, michael@0: cmap->Colors[index].Red, michael@0: cmap->Colors[index].Green, michael@0: cmap->Colors[index].Blue); michael@0: } michael@0: } else { michael@0: // find_colormap() returned NULL. Some (rare, broken) michael@0: // GIFs don't have a color table, so we force one. michael@0: gif_warning(*bm, "missing colormap"); michael@0: colorCount = 256; michael@0: sk_memset32(colorPtr, SK_ColorWHITE, colorCount); michael@0: } michael@0: transpIndex = find_transpIndex(temp_save, colorCount); michael@0: if (transpIndex >= 0) { michael@0: colorPtr[transpIndex] = SK_ColorTRANSPARENT; // ram in a transparent SkPMColor michael@0: alphaType = kPremul_SkAlphaType; michael@0: fillIndex = transpIndex; michael@0: } else if (fillIndex >= colorCount) { michael@0: // gif->SBackGroundColor should be less than colorCount. michael@0: fillIndex = 0; // If not, fix it. michael@0: } michael@0: michael@0: SkAutoTUnref ctable(SkNEW_ARGS(SkColorTable, michael@0: (colorPtr, colorCount, michael@0: alphaType))); michael@0: if (!this->allocPixelRef(bm, ctable)) { michael@0: return error_return(*bm, "allocPixelRef"); michael@0: } michael@0: } michael@0: michael@0: // abort if either inner dimension is <= 0 michael@0: if (innerWidth <= 0 || innerHeight <= 0) { michael@0: return error_return(*bm, "non-pos inner width/height"); michael@0: } michael@0: michael@0: SkAutoLockPixels alp(*bm); michael@0: michael@0: SkAutoMalloc storage(innerWidth); michael@0: uint8_t* scanline = (uint8_t*) storage.get(); michael@0: michael@0: // GIF has an option to store the scanlines of an image, plus a larger background, michael@0: // filled by a fill color. In this case, we will use a subset of the larger bitmap michael@0: // for sampling. michael@0: SkBitmap subset; michael@0: SkBitmap* workingBitmap; michael@0: // are we only a subset of the total bounds? michael@0: if ((imageTop | imageLeft) > 0 || michael@0: innerWidth < width || innerHeight < height) { michael@0: // Fill the background. michael@0: memset(bm->getPixels(), fillIndex, bm->getSize()); michael@0: michael@0: // Create a subset of the bitmap. michael@0: SkIRect subsetRect(SkIRect::MakeXYWH(imageLeft / sampler.srcDX(), michael@0: imageTop / sampler.srcDY(), michael@0: innerWidth / sampler.srcDX(), michael@0: innerHeight / sampler.srcDY())); michael@0: if (!bm->extractSubset(&subset, subsetRect)) { michael@0: return error_return(*bm, "Extract failed."); michael@0: } michael@0: // Update the sampler. We'll now be only sampling into the subset. michael@0: sampler = SkScaledBitmapSampler(innerWidth, innerHeight, this->getSampleSize()); michael@0: workingBitmap = ⊂ michael@0: } else { michael@0: workingBitmap = bm; michael@0: } michael@0: michael@0: // bm is already locked, but if we had to take a subset, it must be locked also, michael@0: // so that getPixels() will point to its pixels. michael@0: SkAutoLockPixels alpWorking(*workingBitmap); michael@0: michael@0: if (!sampler.begin(workingBitmap, SkScaledBitmapSampler::kIndex, *this)) { michael@0: return error_return(*bm, "Sampler failed to begin."); michael@0: } michael@0: michael@0: // now decode each scanline michael@0: if (gif->Image.Interlace) { michael@0: // Iterate over the height of the source data. The sampler will michael@0: // take care of skipping unneeded rows. michael@0: GifInterlaceIter iter(innerHeight); michael@0: for (int y = 0; y < innerHeight; y++) { michael@0: if (DGifGetLine(gif, scanline, innerWidth) == GIF_ERROR) { michael@0: gif_warning(*bm, "interlace DGifGetLine"); michael@0: memset(scanline, fillIndex, innerWidth); michael@0: for (; y < innerHeight; y++) { michael@0: sampler.sampleInterlaced(scanline, iter.currY()); michael@0: iter.next(); michael@0: } michael@0: return true; michael@0: } michael@0: sampler.sampleInterlaced(scanline, iter.currY()); michael@0: iter.next(); michael@0: } michael@0: } else { michael@0: // easy, non-interlace case michael@0: const int outHeight = workingBitmap->height(); michael@0: skip_src_rows(gif, scanline, innerWidth, sampler.srcY0()); michael@0: for (int y = 0; y < outHeight; y++) { michael@0: if (DGifGetLine(gif, scanline, innerWidth) == GIF_ERROR) { michael@0: gif_warning(*bm, "DGifGetLine"); michael@0: memset(scanline, fillIndex, innerWidth); michael@0: for (; y < outHeight; y++) { michael@0: sampler.next(scanline); michael@0: } michael@0: return true; michael@0: } michael@0: // scanline now contains the raw data. Sample it. michael@0: sampler.next(scanline); michael@0: if (y < outHeight - 1) { michael@0: skip_src_rows(gif, scanline, innerWidth, sampler.srcDY() - 1); michael@0: } michael@0: } michael@0: // skip the rest of the rows (if any) michael@0: int read = (outHeight - 1) * sampler.srcDY() + sampler.srcY0() + 1; michael@0: SkASSERT(read <= innerHeight); michael@0: skip_src_rows(gif, scanline, innerWidth, innerHeight - read); michael@0: } michael@0: sanitize_indexed_bitmap(bm); michael@0: return true; michael@0: } break; michael@0: michael@0: case EXTENSION_RECORD_TYPE: michael@0: #if GIFLIB_MAJOR < 5 michael@0: if (DGifGetExtension(gif, &temp_save.Function, michael@0: &extData) == GIF_ERROR) { michael@0: #else michael@0: if (DGifGetExtension(gif, &extFunction, &extData) == GIF_ERROR) { michael@0: #endif michael@0: return error_return(*bm, "DGifGetExtension"); michael@0: } michael@0: michael@0: while (extData != NULL) { michael@0: /* Create an extension block with our data */ michael@0: #if GIFLIB_MAJOR < 5 michael@0: if (AddExtensionBlock(&temp_save, extData[0], michael@0: &extData[1]) == GIF_ERROR) { michael@0: #else michael@0: if (GifAddExtensionBlock(&gif->ExtensionBlockCount, michael@0: &gif->ExtensionBlocks, michael@0: extFunction, michael@0: extData[0], michael@0: &extData[1]) == GIF_ERROR) { michael@0: #endif michael@0: return error_return(*bm, "AddExtensionBlock"); michael@0: } michael@0: if (DGifGetExtensionNext(gif, &extData) == GIF_ERROR) { michael@0: return error_return(*bm, "DGifGetExtensionNext"); michael@0: } michael@0: #if GIFLIB_MAJOR < 5 michael@0: temp_save.Function = 0; michael@0: #endif michael@0: } michael@0: break; michael@0: michael@0: case TERMINATE_RECORD_TYPE: michael@0: break; michael@0: michael@0: default: /* Should be trapped by DGifGetRecordType */ michael@0: break; michael@0: } michael@0: } while (recType != TERMINATE_RECORD_TYPE); michael@0: michael@0: sanitize_indexed_bitmap(bm); michael@0: return true; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: DEFINE_DECODER_CREATOR(GIFImageDecoder); michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: static bool is_gif(SkStreamRewindable* stream) { michael@0: char buf[GIF_STAMP_LEN]; michael@0: if (stream->read(buf, GIF_STAMP_LEN) == GIF_STAMP_LEN) { michael@0: if (memcmp(GIF_STAMP, buf, GIF_STAMP_LEN) == 0 || michael@0: memcmp(GIF87_STAMP, buf, GIF_STAMP_LEN) == 0 || michael@0: memcmp(GIF89_STAMP, buf, GIF_STAMP_LEN) == 0) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: static SkImageDecoder* sk_libgif_dfactory(SkStreamRewindable* stream) { michael@0: if (is_gif(stream)) { michael@0: return SkNEW(SkGIFImageDecoder); michael@0: } michael@0: return NULL; michael@0: } michael@0: michael@0: static SkImageDecoder_DecodeReg gReg(sk_libgif_dfactory); michael@0: michael@0: static SkImageDecoder::Format get_format_gif(SkStreamRewindable* stream) { michael@0: if (is_gif(stream)) { michael@0: return SkImageDecoder::kGIF_Format; michael@0: } michael@0: return SkImageDecoder::kUnknown_Format; michael@0: } michael@0: michael@0: static SkImageDecoder_FormatReg gFormatReg(get_format_gif);