mirror of
https://github.com/Rockbox/rockbox.git
synced 2025-11-19 10:02:45 -05:00
imageviewer: gif viewer based on giflib-5.0.2
This adds ability to view gif images in rockbox. Works both on color and gray/monochrome targets (greylib). Aspect correction is supported as well. Limitations: - animated gifs are restricted to 32 frames - animated gifs loop always (loopcount is ignored) - plain text extension is not supported - animated gifs with interframe delay = 0 are treated as still images (web browsers usually treat delay 0 as 100ms to prevent exhaustive CPU load by such images) Change-Id: I61501f801ddcd403410e38d83e6bddc9883e7ede
This commit is contained in:
parent
b35f82c91f
commit
0ceaff2b65
23 changed files with 3006 additions and 15 deletions
491
apps/plugins/imageviewer/gif/gif_decoder.c
Normal file
491
apps/plugins/imageviewer/gif/gif_decoder.c
Normal file
|
|
@ -0,0 +1,491 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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(GifPixelType *Line, pixel_t *out,
|
||||
GifFileType *GifFile)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* 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.
|
||||
* I preffer the second.
|
||||
*/
|
||||
if (GifFile->SColorMap && GifFile->SBackGroundColor != 0)
|
||||
{
|
||||
memset(Line, GifFile->SBackGroundColor, GifFile->SWidth);
|
||||
|
||||
for(i=0; i<GifFile->SHeight; i++)
|
||||
gif2pixels(Line, out, i, 0, GifFile->SWidth);
|
||||
}
|
||||
else
|
||||
{
|
||||
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(Line, 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(Line, pixels_buffer[buf_idx],
|
||||
GifFile);
|
||||
break;
|
||||
|
||||
case DISPOSE_PREVIOUS:
|
||||
buf_idx ^= 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
/* DISPOSAL_UNSPECIFIED
|
||||
* DISPOSE_DO_NOT
|
||||
*/
|
||||
break;
|
||||
}
|
||||
|
||||
d->frames_count++;
|
||||
|
||||
if (d->frames_count > GIF_MAX_FRAMES)
|
||||
{
|
||||
d->error = D_GIF_ERR_NOT_ENOUGH_MEM;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue