/* libs/graphics/images/SkImageDecoder_libgif.cpp
**
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
#include "SkMovie.h"
#include "SkColor.h"
#include "SkColorPriv.h"
#include "SkStream.h"
#include "SkTemplates.h"
#include "gif_lib.h"
class SkGIFMovie : public SkMovie {
public:
SkGIFMovie(SkStream* stream);
virtual ~SkGIFMovie();
protected:
virtual bool onGetInfo(Info*);
virtual bool onSetTime(SkMSec);
virtual bool onGetBitmap(SkBitmap*);
private:
GifFileType* fGIF;
SavedImage* fCurrSavedImage;
};
static int Decode(GifFileType* fileType, GifByteType* out, int size) {
SkStream* stream = (SkStream*) fileType->UserData;
return (int) stream->read(out, size);
}
SkGIFMovie::SkGIFMovie(SkStream* stream)
{
fGIF = DGifOpen( stream, Decode );
if (NULL == fGIF)
return;
if (DGifSlurp(fGIF) != GIF_OK)
{
DGifCloseFile(fGIF);
fGIF = NULL;
}
fCurrSavedImage = NULL;
}
SkGIFMovie::~SkGIFMovie()
{
if (fGIF)
DGifCloseFile(fGIF);
}
static SkMSec savedimage_duration(const SavedImage* image)
{
for (int j = 0; j < image->ExtensionBlockCount; j++)
{
if (image->ExtensionBlocks[j].Function == GRAPHICS_EXT_FUNC_CODE)
{
int size = image->ExtensionBlocks[j].ByteCount;
SkASSERT(size >= 4);
const uint8_t* b = (const uint8_t*)image->ExtensionBlocks[j].Bytes;
return ((b[2] << 8) | b[1]) * 10;
}
}
return 0;
}
bool SkGIFMovie::onGetInfo(Info* info)
{
if (NULL == fGIF)
return false;
SkMSec dur = 0;
for (int i = 0; i < fGIF->ImageCount; i++)
dur += savedimage_duration(&fGIF->SavedImages[i]);
info->fDuration = dur;
info->fWidth = fGIF->SWidth;
info->fHeight = fGIF->SHeight;
info->fIsOpaque = false; // how to compute?
return true;
}
bool SkGIFMovie::onSetTime(SkMSec time)
{
if (NULL == fGIF)
return false;
SkMSec dur = 0;
for (int i = 0; i < fGIF->ImageCount; i++)
{
dur += savedimage_duration(&fGIF->SavedImages[i]);
if (dur >= time)
{
SavedImage* prev = fCurrSavedImage;
fCurrSavedImage = &fGIF->SavedImages[i];
return prev != fCurrSavedImage;
}
}
fCurrSavedImage = &fGIF->SavedImages[fGIF->ImageCount - 1];
return true;
}
bool SkGIFMovie::onGetBitmap(SkBitmap* bm)
{
GifFileType* gif = fGIF;
if (NULL == gif)
return false;
// should we check for the Image cmap or the global (SColorMap) first?
ColorMapObject* cmap = gif->SColorMap;
if (cmap == NULL)
cmap = gif->Image.ColorMap;
if (cmap == NULL || gif->ImageCount < 1 || cmap->ColorCount != (1 << cmap->BitsPerPixel))
{
SkASSERT(!"bad colortable setup");
return false;
}
const int width = gif->SWidth;
const int height = gif->SHeight;
if (width <= 0 || height <= 0) {
return false;
}
SavedImage* gif_image = fCurrSavedImage;
SkBitmap::Config config = SkBitmap::kIndex8_Config;
SkColorTable* colorTable = SkNEW_ARGS(SkColorTable, (cmap->ColorCount));
SkAutoUnref aur(colorTable);
bm->setConfig(config, width, height, 0);
if (!bm->allocPixels(colorTable)) {
return false;
}
int transparent = -1;
for (int i = 0; i < gif_image->ExtensionBlockCount; ++i) {
ExtensionBlock* eb = gif_image->ExtensionBlocks + i;
if (eb->Function == 0xF9 &&
eb->ByteCount == 4) {
bool has_transparency = ((eb->Bytes[0] & 1) == 1);
if (has_transparency) {
transparent = (unsigned char)eb->Bytes[3];
}
}
}
SkPMColor* colorPtr = colorTable->lockColors();
if (transparent >= 0)
memset(colorPtr, 0, cmap->ColorCount * 4);
else
colorTable->setFlags(colorTable->getFlags() | SkColorTable::kColorsAreOpaque_Flag);
for (int index = 0; index < cmap->ColorCount; index++)
{
if (transparent != index)
colorPtr[index] = SkPackARGB32(0xFF, cmap->Colors[index].Red,
cmap->Colors[index].Green, cmap->Colors[index].Blue);
}
colorTable->unlockColors(true);
unsigned char* in = (unsigned char*)gif_image->RasterBits;
unsigned char* out = bm->getAddr8(0, 0);
if (gif->Image.Interlace) {
// deinterlace
int row;
// group 1 - every 8th row, starting with row 0
for (row = 0; row < height; row += 8) {
memcpy(out + width * row, in, width);
in += width;
}
// group 2 - every 8th row, starting with row 4
for (row = 4; row < height; row += 8) {
memcpy(out + width * row, in, width);
in += width;
}
// group 3 - every 4th row, starting with row 2
for (row = 2; row < height; row += 4) {
memcpy(out + width * row, in, width);
in += width;
}
for (row = 1; row < height; row += 2) {
memcpy(out + width * row, in, width);
in += width;
}
} else {
memcpy(out, in, width * height);
}
return true;
}
///////////////////////////////////////////////////////////////////////////////
#include "SkTRegistry.h"
SkMovie* Factory(SkStream* stream) {
char buf[GIF_STAMP_LEN];
if (stream->read(buf, GIF_STAMP_LEN) == GIF_STAMP_LEN) {
if (memcmp(GIF_STAMP, buf, GIF_STAMP_LEN) == 0 ||
memcmp(GIF87_STAMP, buf, GIF_STAMP_LEN) == 0 ||
memcmp(GIF89_STAMP, buf, GIF_STAMP_LEN) == 0) {
// must rewind here, since our construct wants to re-read the data
stream->rewind();
return SkNEW_ARGS(SkGIFMovie, (stream));
}
}
return NULL;
}
static SkTRegistry<SkMovie*, SkStream*> gReg(Factory);