/*************************************************************************** * __________ __ ___. * 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 #include "bmp.h" #if LCD_DEPTH < 8 #include #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 }