rockbox/apps/plugins/imageviewer/gif/gif_decoder.c
Marcin Bukat 62cb84a57c imageviewer: fix animated gifs handling
If disposal method is set to BACKGROUND one would expect that
canvas should be restored to global background color. That is
what gif standard suggests. Most (all?) decoders however treat
this as reseting canvas to transparency or fixed, decoder
specific background color. Virtually all gifs are prepared with
this in mind so to not break them we can't follow standard here.

Change-Id: I90ca712bba89d4190771eb5320eabda353d3e2bb
2013-04-18 09:42:48 +02:00

473 lines
14 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
*
* Copyright (c) 2012 Marcin Bukat
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include <lib/pluginlib_bmp.h>
#include "bmp.h"
#if LCD_DEPTH < 8
#include <lib/grey.h>
#endif
#include "gif_lib.h"
#include "gif_decoder.h"
#ifndef resize_bitmap
#if defined(HAVE_LCD_COLOR)
#define resize_bitmap smooth_resize_bitmap
#else
#define resize_bitmap grey_resize_bitmap
#endif
#endif
#if defined(HAVE_LCD_COLOR)
typedef struct uint8_rgb pixel_t;
#define NATIVE_SZ (GifFile->SWidth*GifFile->SHeight*FB_DATA_SZ)
#define PIXEL_TRANSPARENT 0x00
#else
typedef unsigned char pixel_t;
#define NATIVE_SZ (GifFile->SWidth*GifFile->SHeight)
#define PIXEL_TRANSPARENT 0xff
#endif
#define PIXELS_SZ (GifFile->SWidth*GifFile->SHeight*sizeof(pixel_t))
static GifFileType *GifFile;
static void gif2pixels(GifPixelType *Line, pixel_t *out,
int Row, int Col, int Width)
{
int x;
#ifndef HAVE_LCD_COLOR
struct uint8_rgb rgb;
#endif
GifColorType *ColorMapEntry;
/* Color map to use */
ColorMapObject *ColorMap = (GifFile->Image.ColorMap ?
GifFile->Image.ColorMap :
GifFile->SColorMap);
pixel_t *pixel = out + ((Row * GifFile->SWidth) + Col);
for (x = 0; x < Width; x++, pixel++)
{
ColorMapEntry = &ColorMap->Colors[Line[x]];
if (GifFile->Image.GCB &&
GifFile->Image.GCB->TransparentColor == Line[x])
continue;
#ifdef HAVE_LCD_COLOR
pixel->red = ColorMapEntry->Red;
pixel->green = ColorMapEntry->Green;
pixel->blue = ColorMapEntry->Blue;
#else
rgb.red = ColorMapEntry->Red;
rgb.green = ColorMapEntry->Green;
rgb.blue = ColorMapEntry->Blue;
*pixel = brightness(rgb);
#endif
}
}
static void pixels2native(struct scaler_context *ctx,
pixel_t *pixels_buffer,
int Row)
{
#ifdef HAVE_LCD_COLOR
const struct custom_format *cformat = &format_native;
#else
const struct custom_format *cformat = &format_grey;
#endif
void (*output_row_8)(uint32_t, void*, struct scaler_context*) =
cformat->output_row_8;
output_row_8(Row, (void *)(pixels_buffer + (Row*ctx->bm->width)), ctx);
}
void gif_decoder_init(struct gif_decoder *d, void *mem, size_t size)
{
memset(d, 0, sizeof(struct gif_decoder));
d->mem = mem;
d->mem_size = size;
/* mem allocator init */
init_memory_pool(d->mem_size, d->mem);
}
void gif_open(char *filename, struct gif_decoder *d)
{
if ((GifFile = DGifOpenFileName(filename, &d->error)) == NULL)
return;
d->width = GifFile->SWidth;
d->height = GifFile->SHeight;
d->frames_count = 0;
}
static void set_canvas_background(pixel_t *out, GifFileType *GifFile)
{
/* Reading Gif spec it seems one should always use background color
* in canvas but most real files omit this and sets background color to 0
* (which IS valid index). We can choose to either conform to standard
* (and wrongly display most of gifs with transparency) or stick to
* common practise and treat background color 0 as transparent.
* Moreover when dispose method is BACKGROUND spec suggest
* to reset canvas to global background color specified in gif BUT
* all renderers I know use transparency instead.
*/
memset(out, PIXEL_TRANSPARENT, PIXELS_SZ);
}
/* var names adhere to giflib coding style */
void gif_decode(struct gif_decoder *d,
void (*pf_progress)(int current, int total))
{
int i, j;
int Size;
int Row;
int Col;
int Width;
int Height;
int ExtCode;
GifPixelType *Line;
GifRecordType RecordType;
GifByteType *Extension;
unsigned char *out = NULL;
/* The way Interlaced image should
* be read - offsets and jumps
*/
const char InterlacedOffset[] = { 0, 4, 2, 1 };
const char InterlacedJumps[] = { 8, 8, 4, 2 };
/* used for color conversion */
struct bitmap bm;
struct scaler_context ctx = {
.bm = &bm,
.dither = 0
};
/* initialize struct */
memset(&bm, 0, sizeof(struct bitmap));
Size = GifFile->SWidth * sizeof(GifPixelType); /* Size in bytes one row.*/
Line = (GifPixelType *)malloc(Size);
if (Line == NULL)
{
/* error allocating temp space */
d->error = D_GIF_ERR_NOT_ENOUGH_MEM;
return;
}
/* We use two pixel buffers if dispose method asks
* for restoration of the previous state.
* We only swap the indexes leaving data in place.
*/
int buf_idx = 0;
pixel_t *pixels_buffer[2];
pixels_buffer[0] = (pixel_t *)malloc(PIXELS_SZ);
pixels_buffer[1] = NULL;
if (pixels_buffer[0] == NULL)
{
d->error = D_GIF_ERR_NOT_ENOUGH_MEM;
return;
}
/* Global background color */
set_canvas_background(pixels_buffer[0], GifFile);
bm.width = GifFile->SWidth;
bm.height = GifFile->SHeight;
d->native_img_size = NATIVE_SZ;
if (pf_progress != NULL)
pf_progress(0, 100);
/* Scan the content of the GIF file and load the image(s) in: */
do
{
if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR)
{
d->error = GifFile->Error;
return;
}
switch (RecordType)
{
case IMAGE_DESC_RECORD_TYPE:
if (DGifGetImageDesc(GifFile) == GIF_ERROR)
{
d->error = GifFile->Error;
return;
}
/* Image Position relative to canvas */
Row = GifFile->Image.Top;
Col = GifFile->Image.Left;
Width = GifFile->Image.Width;
Height = GifFile->Image.Height;
/* Check Color map to use */
if (GifFile->Image.ColorMap == NULL &&
GifFile->SColorMap == NULL)
{
d->error = D_GIF_ERR_NO_COLOR_MAP;
return;
}
/* sanity check */
if (GifFile->Image.Left+GifFile->Image.Width>GifFile->SWidth ||
GifFile->Image.Top+GifFile->Image.Height>GifFile->SHeight)
{
d->error = D_GIF_ERR_DATA_TOO_BIG;
return;
}
if (GifFile->Image.GCB &&
GifFile->Image.GCB->DisposalMode == DISPOSE_PREVIOUS)
{
/* We need to take a snapshot before processing the image
* in order to restore canvas to previous state after
* rendering
*/
buf_idx ^= 1;
if (pixels_buffer[buf_idx] == NULL)
pixels_buffer[buf_idx] = (pixel_t *)malloc(PIXELS_SZ);
}
if (GifFile->Image.Interlace)
{
/* Need to perform 4 passes on the image */
for (i = 0; i < 4; i++)
{
for (j = Row + InterlacedOffset[i];
j < Row + Height;
j += InterlacedJumps[i])
{
if (DGifGetLine(GifFile, Line, Width) == GIF_ERROR)
{
d->error = GifFile->Error;
return;
}
gif2pixels(Line, pixels_buffer[buf_idx],
Row + j, Col, Width);
}
pf_progress(25*(i+1), 100);
}
}
else
{
for (i = 0; i < Height; i++)
{
/* load single line into buffer */
if (DGifGetLine(GifFile, Line, Width) == GIF_ERROR)
{
d->error = GifFile->Error;
return;
}
gif2pixels(Line, pixels_buffer[buf_idx],
Row + i, Col, Width);
pf_progress(100*(i+1)/Height, 100);
}
}
/* allocate space for new frame */
out = realloc(out, d->native_img_size*(d->frames_count + 1));
if (out == NULL)
{
d->error = D_GIF_ERR_NOT_ENOUGH_MEM;
return;
}
bm.data = out + d->native_img_size*d->frames_count;
/* animated gif */
if (GifFile->Image.GCB && GifFile->Image.GCB->DelayTime != 0)
{
for (i=0; i < ctx.bm->height; i++)
pixels2native(&ctx, (void *)pixels_buffer[buf_idx], i);
/* restore to the background color */
switch (GifFile->Image.GCB->DisposalMode)
{
case DISPOSE_BACKGROUND:
set_canvas_background(pixels_buffer[buf_idx],
GifFile);
break;
case DISPOSE_PREVIOUS:
buf_idx ^= 1;
break;
default:
/* DISPOSAL_UNSPECIFIED
* DISPOSE_DO_NOT
*/
break;
}
d->frames_count++;
}
break;
case EXTENSION_RECORD_TYPE:
if (DGifGetExtension(GifFile, &ExtCode, &Extension) ==
GIF_ERROR)
{
d->error = GifFile->Error;
return;
}
if (ExtCode == GRAPHICS_EXT_FUNC_CODE)
{
if (GifFile->Image.GCB == NULL)
GifFile->Image.GCB = (GraphicsControlBlock *)
malloc(sizeof(GraphicsControlBlock));
if (DGifExtensionToGCB(Extension[0],
Extension + 1,
GifFile->Image.GCB) == GIF_ERROR)
{
d->error = GifFile->Error;
return;
}
d->delay = GifFile->Image.GCB->DelayTime;
}
/* Skip anything else */
while (Extension != NULL)
{
if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR)
{
d->error = GifFile->Error;
return;
}
}
break;
/* including TERMINATE_RECORD_TYPE */
default:
break;
}
} while (RecordType != TERMINATE_RECORD_TYPE);
/* free all internal allocated data */
if (DGifCloseFile(GifFile) == GIF_ERROR)
{
d->error = GifFile->Error;
return;
}
/* not animated gif */
if (d->frames_count == 0)
{
for (i=0; i < ctx.bm->height; i++)
pixels2native(&ctx, (void *)pixels_buffer[buf_idx], i);
d->frames_count++;
}
free(pixels_buffer[0]);
if (pixels_buffer[1])
free(pixels_buffer[1]);
free(Line);
/* WARNING !!!! */
/* GifFile object is trashed from now on, DONT use it */
/* Move bitmap in native format to the front of the buff */
memmove(d->mem, out, d->frames_count*d->native_img_size);
/* correct aspect ratio */
#if (LCD_PIXEL_ASPECT_HEIGHT != 1 || LCD_PIXEL_ASPECT_WIDTH != 1)
struct bitmap img_src, img_dst; /* scaler vars */
struct dim dim_src, dim_dst; /* recalc_dimensions vars */
size_t c_native_img_size; /* size of the image after correction */
dim_src.width = bm.width;
dim_src.height = bm.height;
dim_dst.width = bm.width;
dim_dst.height = bm.height;
/* defined in apps/recorder/resize.c */
if (!recalc_dimension(&dim_dst, &dim_src))
{
/* calculate 'corrected' image size */
#ifdef HAVE_LCD_COLOR
c_native_img_size = dim_dst.width * dim_dst.height * FB_DATA_SZ;
#else
c_native_img_size = dim_dst.width * dim_dst.height;
#endif
/* check memory constraints
* do the correction only if there is enough
* free memory
*/
if (d->native_img_size*d->frames_count + c_native_img_size <=
d->mem_size)
{
img_dst.width = dim_dst.width;
img_dst.height = dim_dst.height;
img_dst.data = (unsigned char *)d->mem +
d->native_img_size*d->frames_count;
for (i = 0; i < d->frames_count; i++)
{
img_src.width = dim_src.width;
img_src.height = dim_src.height;
img_src.data = (unsigned char *)d->mem + i*d->native_img_size;
/* scale the bitmap to correct physical
* pixel dimentions
*/
resize_bitmap(&img_src, &img_dst);
/* copy back corrected image */
memmove(d->mem + i*c_native_img_size,
img_dst.data,
c_native_img_size);
}
/* update decoder struct */
d->width = img_dst.width;
d->height = img_dst.height;
d->native_img_size = c_native_img_size;
}
}
#endif
}