From a02c124940b6c5d61ffcbfb2c7e4d493775c3192 Mon Sep 17 00:00:00 2001 From: Andrew Hannam Date: Sat, 20 Apr 2013 21:19:26 +1000 Subject: [PATCH] GIF image handling GIF image handling Updates to Image structure to make memory accounting optional Add set image background color to handle animated transparency. --- .../gdisp/gdisp_images_animated/gfxconf.h | 57 + .../gdisp/gdisp_images_animated/main.c | 101 ++ .../gdisp/gdisp_images_animated/testanim.gif | Bin 0 -> 8938 bytes .../gdisp/gdisp_images_animated/testanim.h | 567 ++++++++ gfxconf.example.h | 1 + include/gdisp/image.h | 20 +- include/gdisp/options.h | 7 + src/gdisp/image.c | 33 + src/gdisp/image_bmp.c | 21 +- src/gdisp/image_gif.c | 1166 ++++++++++++++++- 10 files changed, 1951 insertions(+), 22 deletions(-) create mode 100644 demos/modules/gdisp/gdisp_images_animated/gfxconf.h create mode 100644 demos/modules/gdisp/gdisp_images_animated/main.c create mode 100644 demos/modules/gdisp/gdisp_images_animated/testanim.gif create mode 100644 demos/modules/gdisp/gdisp_images_animated/testanim.h diff --git a/demos/modules/gdisp/gdisp_images_animated/gfxconf.h b/demos/modules/gdisp/gdisp_images_animated/gfxconf.h new file mode 100644 index 00000000..cd368e95 --- /dev/null +++ b/demos/modules/gdisp/gdisp_images_animated/gfxconf.h @@ -0,0 +1,57 @@ +/** + * This file has a different license to the rest of the GFX system. + * You can copy, modify and distribute this file as you see fit. + * You do not need to publish your source modifications to this file. + * The only thing you are not permitted to do is to relicense it + * under a different license. + */ + +#ifndef _GFXCONF_H +#define _GFXCONF_H + +/* GFX sub-systems to turn on */ +#define GFX_USE_GDISP TRUE +#define GFX_USE_GWIN FALSE +#define GFX_USE_GEVENT FALSE +#define GFX_USE_GTIMER FALSE +#define GFX_USE_GINPUT FALSE + +/* Features for the GDISP sub-system. */ +#define GDISP_NEED_VALIDATION TRUE +#define GDISP_NEED_CLIP TRUE +#define GDISP_NEED_TEXT FALSE +#define GDISP_NEED_CIRCLE FALSE +#define GDISP_NEED_ELLIPSE FALSE +#define GDISP_NEED_ARC FALSE +#define GDISP_NEED_CONVEX_POLYGON FALSE +#define GDISP_NEED_SCROLL FALSE +#define GDISP_NEED_PIXELREAD FALSE +#define GDISP_NEED_CONTROL FALSE +#define GDISP_NEED_IMAGE TRUE +#define GDISP_NEED_MULTITHREAD FALSE +#define GDISP_NEED_ASYNC FALSE +#define GDISP_NEED_MSGAPI FALSE + +/* Builtin Fonts */ +#define GDISP_INCLUDE_FONT_SMALL FALSE +#define GDISP_INCLUDE_FONT_LARGER FALSE +#define GDISP_INCLUDE_FONT_UI1 FALSE +#define GDISP_INCLUDE_FONT_UI2 FALSE +#define GDISP_INCLUDE_FONT_LARGENUMBERS FALSE + +/* GDISP image decoders */ +#define GDISP_NEED_IMAGE_NATIVE FALSE +#define GDISP_NEED_IMAGE_GIF TRUE +#define GDISP_NEED_IMAGE_BMP FALSE +#define GDISP_NEED_IMAGE_JPG FALSE +#define GDISP_NEED_IMAGE_PNG FALSE +#define GDISP_NEED_IMAGE_ACCOUNTING FALSE + +/* Features for the GWIN sub-system. */ +#define GWIN_NEED_BUTTON FALSE +#define GWIN_NEED_CONSOLE FALSE + +/* Features for the GINPUT sub-system. */ +#define GINPUT_NEED_MOUSE FALSE + +#endif /* _GFXCONF_H */ diff --git a/demos/modules/gdisp/gdisp_images_animated/main.c b/demos/modules/gdisp/gdisp_images_animated/main.c new file mode 100644 index 00000000..d5c3d712 --- /dev/null +++ b/demos/modules/gdisp/gdisp_images_animated/main.c @@ -0,0 +1,101 @@ +/* + ChibiOS/GFX - Copyright (C) 2012, 2013 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX 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 3 of the License, or + (at your option) any later version. + + ChibiOS/GFX is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "ch.h" +#include "hal.h" +#include "gfx.h" + +#define USE_IMAGE_CACHE FALSE // Only if you want to get performance at the expense of RAM +#define MY_BG_COLOR RGB2COLOR(220, 220, 255) // Pale blue so we can see the transparent parts + +#ifdef WIN32 + #define USE_MEMORY_FILE FALSE // Can be true or false for Win32 +#else + #define USE_MEMORY_FILE TRUE // Non-Win32 - use the compiled in image +#endif + +#define SHOW_ERROR(color) gdispFillArea(swidth-10, 0, 10, sheight, color) + +#if USE_MEMORY_FILE + #include "testanim.h" +#endif + +static gdispImage myImage; + +/** + * This demo display the animated gif (either directly from a file or from a + * file encoded in flash. + * To show the various stages of encoding it displays a colored bar down the + * right hand side of the display to indicate what is happenning. + * Green - Image has completed and is displayed correctly + * Red - A really bad image fault. We couldn't open the image + * Yellow - Waiting to decode the next animation frame + * Orange - Decoding a frame has produced an error. + */ +int main(void) { + coord_t swidth, sheight; + systime_t delay; + + halInit(); // Initialize the Hardware + chSysInit(); // Initialize the OS + gdispInit(); // Initialize the display + + gdispClear(MY_BG_COLOR); + + // Get the display dimensions + swidth = gdispGetWidth(); + sheight = gdispGetHeight(); + + // Set up IO for our image +#if USE_MEMORY_FILE + gdispImageSetMemoryReader(&myImage, testanim); +#else + gdispImageSetSimulFileReader(&myImage, "testanim.gif"); +#endif + + if (gdispImageOpen(&myImage) == GDISP_IMAGE_ERR_OK) { + gdispImageSetBgColor(&myImage, MY_BG_COLOR); + while(1) { + #if USE_IMAGE_CACHE + gdispImageCache(&myImage); + #endif + if (gdispImageDraw(&myImage, 5, 5, myImage.width, myImage.height, 0, 0) != GDISP_IMAGE_ERR_OK) { + SHOW_ERROR(Orange); + break; + } + delay = gdispImageNext(&myImage); + if (delay == TIME_INFINITE) { + SHOW_ERROR(Green); + break; + } + SHOW_ERROR(Yellow); + if (delay != TIME_IMMEDIATE) + chThdSleepMilliseconds(delay); + } + gdispImageClose(&myImage); + } else + SHOW_ERROR(Red); + + while(1) { + chThdSleepMilliseconds(1000); + } + + return 0; +} diff --git a/demos/modules/gdisp/gdisp_images_animated/testanim.gif b/demos/modules/gdisp/gdisp_images_animated/testanim.gif new file mode 100644 index 0000000000000000000000000000000000000000..6639acb2561746ce95eaa4d805189c6a6838d024 GIT binary patch literal 8938 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 = White; for(img->fns = ImageHandlers; img->fns < ImageHandlers+sizeof(ImageHandlers)/sizeof(ImageHandlers[0]); img->fns++) { err = img->fns->open(img); if (err != GDISP_IMAGE_ERR_BADFORMAT) { @@ -190,6 +191,10 @@ void gdispImageClose(gdispImage *img) { img->io.fns->close(&img->io); } +void gdispImageSetBgColor(gdispImage *img, color_t bgcolor) { + img->bgcolor = bgcolor; +} + gdispImageError gdispImageCache(gdispImage *img) { if (!img->fns) return GDISP_IMAGE_ERR_BADFORMAT; return img->fns->cache(img); @@ -205,6 +210,34 @@ systime_t gdispImageNext(gdispImage *img) { return img->fns->next(img); } +// Helper Routines +void *gdispImageAlloc(gdispImage *img, size_t sz) { + #if GDISP_NEED_IMAGE_ACCOUNTING + void *ptr; + + ptr = chHeapAlloc(NULL, sz); + if (ptr) { + img->memused += sz; + if (img->memused > img->maxmemused) + img->maxmemused = img->memused; + } + return ptr; + #else + (void) img; + return chHeapAlloc(NULL, sz); + #endif +} + +void gdispImageFree(gdispImage *img, void *ptr, size_t sz) { + #if GDISP_NEED_IMAGE_ACCOUNTING + chHeapFree(ptr); + img->memused -= sz; + #else + (void) img; + (void) sz; + chHeapFree(ptr); + #endif +} #endif /* GFX_USE_GDISP && GDISP_NEED_IMAGE */ /** @} */ diff --git a/src/gdisp/image_bmp.c b/src/gdisp/image_bmp.c index 4ddfc7f8..bab9b4e9 100644 --- a/src/gdisp/image_bmp.c +++ b/src/gdisp/image_bmp.c @@ -53,6 +53,12 @@ #define GDISP_NEED_IMAGE_BMP_32 TRUE #endif +/** + * Helper Routines Needed + */ +void *gdispImageAlloc(gdispImage *img, size_t sz); +void gdispImageFree(gdispImage *img, void *ptr, size_t sz); + /** * How big a pixel array to allocate for blitting (in pixels) * Bigger is faster but uses more RAM. @@ -148,9 +154,8 @@ gdispImageError gdispImageOpen_BMP(gdispImage *img) { img->flags = 0; /* Allocate our private area */ - if (!(img->priv = (gdispImagePrivate *)chHeapAlloc(NULL, sizeof(gdispImagePrivate)))) + if (!(img->priv = (gdispImagePrivate *)gdispImageAlloc(img, sizeof(gdispImagePrivate)))) return GDISP_IMAGE_ERR_NOMEMORY; - img->membytes = sizeof(gdispImagePrivate); /* Initialise the essential bits in the private area */ priv = img->priv; @@ -336,9 +341,8 @@ gdispImageError gdispImageOpen_BMP(gdispImage *img) { if (priv->bmpflags & BMP_PALETTE) { img->io.fns->seek(&img->io, offsetColorTable); - if (!(priv->palette = (color_t *)chHeapAlloc(NULL, priv->palsize*sizeof(color_t)))) + if (!(priv->palette = (color_t *)gdispImageAlloc(img, priv->palsize*sizeof(color_t)))) return GDISP_IMAGE_ERR_NOMEMORY; - img->membytes += priv->palsize * sizeof(color_t); if (priv->bmpflags & BMP_V2) { for(aword = 0; aword < priv->palsize; aword++) { if (img->io.fns->read(&img->io, &priv->buf, 3) != 3) goto baddatacleanup; @@ -430,14 +434,13 @@ void gdispImageClose_BMP(gdispImage *img) { if (img->priv) { #if GDISP_NEED_IMAGE_BMP_1 || GDISP_NEED_IMAGE_BMP_4 || GDISP_NEED_IMAGE_BMP_4_RLE || GDISP_NEED_IMAGE_BMP_8 || GDISP_NEED_IMAGE_BMP_8_RLE if (img->priv->palette) - chHeapFree((void *)img->priv->palette); + gdispImageFree(img, (void *)img->priv->palette, img->priv->palsize*sizeof(color_t)); #endif if (img->priv->frame0cache) - chHeapFree((void *)img->priv->frame0cache); - chHeapFree((void *)img->priv); + gdispImageFree(img, (void *)img->priv->frame0cache, img->width*img->height*sizeof(pixel_t)); + gdispImageFree(img, (void *)img->priv, sizeof(gdispImagePrivate)); img->priv = 0; } - img->membytes = 0; img->io.fns->close(&img->io); } @@ -794,7 +797,7 @@ gdispImageError gdispImageCache_BMP(gdispImage *img) { /* We need to allocate the cache */ len = img->width * img->height * sizeof(pixel_t); - priv->frame0cache = (pixel_t *)chHeapAlloc(NULL, len); + priv->frame0cache = (pixel_t *)gdispImageAlloc(img, len); if (!priv->frame0cache) return GDISP_IMAGE_ERR_NOMEMORY; img->membytes += len; diff --git a/src/gdisp/image_gif.c b/src/gdisp/image_gif.c index b7a940ad..2672333f 100644 --- a/src/gdisp/image_gif.c +++ b/src/gdisp/image_gif.c @@ -28,20 +28,1162 @@ #if GFX_USE_GDISP && GDISP_NEED_IMAGE && GDISP_NEED_IMAGE_GIF -#error "GIF support not implemented yet" +/** + * Helper Routines Needed + */ +void *gdispImageAlloc(gdispImage *img, size_t sz); +void gdispImageFree(gdispImage *img, void *ptr, size_t sz); -/* A pallete structure */ -typedef struct gdispImagePallete { - uint8_t flags; - #define GDISP_IMAGE_FLG_INT_TRANSPARENT 0x01 - uint8_t idxtrans; /* The transparent idx */ - uint8_t maxidx; /* The maximum index (0..255) */ - uint8_t repidx; /* The index to use if the image data > maxidx */ - color_t pal[256]; /* The pallete entries - not all may actually be allocated */ -} gdispImagePallete; +/** + * How big an array to allocate for blitting (in pixels) + * Bigger is faster but uses more RAM. + */ +#define BLIT_BUFFER_SIZE 32 -/* Draw a single palletized line (or partial line) */ -static void gdispDrawPalleteLine(const gdispImagePallete *pal, const uint8_t *line, coord_t x, coord_t y, coord_t cx); +/* + * Determining endianness as at compile time is not guaranteed or compiler portable. + * We use the best test we can. If we can't guarantee little endianness we do things the + * hard way. + */ +#define GUARANTEED_LITTLE_ENDIAN (!defined(SAFE_ENDIAN) && !defined(SAFE_ALIGNMENT) && (\ + (defined(__BYTE_ORDER__)&&(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) \ + || defined(__LITTLE_ENDIAN__) \ + || defined(__LITTLE_ENDIAN) \ + || defined(_LITTLE_ENDIAN) \ +/* || (1 == *(unsigned char *)&(const int){1})*/ \ + )) + + +/* This is a runtime test */ +static const uint8_t dwordOrder[4] = { 1, 2, 3, 4 }; + +#define isWordLittleEndian() (*(uint16_t *)&dwordOrder == 0x0201) +#define isDWordLittleEndian() (*(uint32_t *)&dwordOrder == 0x04030201) + +#if GUARANTEED_LITTLE_ENDIAN + /* These are fast routines for guaranteed little endian machines */ + #define CONVERT_FROM_WORD_LE(w) + #define CONVERT_FROM_DWORD_LE(dw) +#else + /* These are slower routines for when little endianness cannot be guaranteed at compile time */ + #define CONVERT_FROM_WORD_LE(w) { if (!isWordLittleEndian()) w = ((((uint16_t)(w))>>8)|(((uint16_t)(w))<<8)); } + #define CONVERT_FROM_DWORD_LE(dw) { if (!isDWordLittleEndian()) dw = (((uint32_t)(((const uint8_t *)(&dw))[0]))|(((uint32_t)(((const uint8_t *)(&dw))[1]))<<8)|(((uint32_t)(((const uint8_t *)(&dw))[2]))<<16)|(((uint32_t)(((const uint8_t *)(&dw))[3]))<<24)); } +#endif + +// We need a special error to indicate the end of file (which may not actually be an error) +#define GDISP_IMAGE_EOF ((gdispImageError)-1) +#define GDISP_IMAGE_LOOP ((gdispImageError)-2) + +#define MAX_CODE_BITS 12 +#define CODE_MAX ((1<priv; + + // We need the decode ram, and possibly a palette + if (!(decode = (imgdecode *)gdispImageAlloc(img, sizeof(imgdecode)+priv->frame.palsize*sizeof(color_t)))) + return GDISP_IMAGE_ERR_NOMEMORY; + + // We currently have not read any image data block + decode->blocksz = 0; + + // Set the palette + if (priv->frame.palsize) { + // Local palette + decode->maxpixel = priv->frame.palsize-1; + decode->palette = (color_t *)(decode+1); + img->io.fns->seek(&img->io, priv->frame.pospal); + for(cnt = 0; cnt < priv->frame.palsize; cnt++) { + if (img->io.fns->read(&img->io, &decode->buf, 3) != 3) + goto baddatacleanup; + decode->palette[cnt] = RGB2COLOR(decode->buf[0], decode->buf[1], decode->buf[2]); + } + } else if (priv->palette) { + // Global palette + decode->maxpixel = priv->palsize-1; + decode->palette = priv->palette; + } else { + // Oops - we must have a palette + goto baddatacleanup; + } + + // Get the initial lzw code size and values + img->io.fns->seek(&img->io, priv->frame.posimg); + if (img->io.fns->read(&img->io, &decode->bitsperpixel, 1) != 1 || decode->bitsperpixel >= MAX_CODE_BITS) + goto baddatacleanup; + decode->code_clear = 1 << decode->bitsperpixel; + decode->code_eof = decode->code_clear + 1; + decode->code_max = decode->code_clear + 2; + decode->code_last = CODE_NONE; + decode->bitspercode = decode->bitsperpixel+1; + decode->maxcodesz = 1 << decode->bitspercode; + decode->shiftbits = 0; + decode->shiftdata = 0; + decode->stackcnt = 0; + for(cnt = 0; cnt <= CODE_MAX; cnt++) + decode->prefix[cnt] = CODE_NONE; + + // All ready to go + priv->decode = decode; + return GDISP_IMAGE_ERR_OK; + +baddatacleanup: + gdispImageFree(img, decode, sizeof(imgdecode)+priv->frame.palsize*sizeof(color_t)); + return GDISP_IMAGE_ERR_BADDATA; +} + +/** + * Stop decoding a frame. + * + * Pre: Frame info has been read. + */ +static void stopDecode(gdispImage *img) { + gdispImagePrivate * priv; + + priv = img->priv; + + // Free the decode data + if (priv->decode) { + gdispImageFree(img, (void *)priv->decode, sizeof(imgdecode)+priv->frame.palsize*sizeof(color_t)); + priv->decode = 0; + } +} + +static uint16_t getPrefix(imgdecode *decode, uint16_t code) { + uint16_t i; + + for(i=0; code > decode->code_clear && i <= CODE_MAX; i++, code = decode->prefix[code]) { + if (code > CODE_MAX) + return CODE_NONE; + } + return code; +} + +/** + * Decode some pixels from a frame. + * + * Pre: We are ready for decoding. + * + * Return: The number of pixels decoded 0 .. BLIT_BUFFER_SIZE-1. 0 means EOF + * + * Note: The resulting pixels are stored in decode->buf + */ +static uint16_t getbytes(gdispImage *img) { + gdispImagePrivate * priv; + imgdecode * decode; + uint16_t cnt; + uint16_t code, prefix; + uint8_t bdata; + + priv = img->priv; + decode = priv->decode; + cnt = 0; + + // At EOF + if (decode->code_last == decode->code_eof) + return 0; + + while(cnt < sizeof(decode->buf)) { + // Use the stack up first + if (decode->stackcnt > 0) { + decode->buf[cnt++] = decode->stack[--decode->stackcnt]; + continue; + } + + // Get another code - a code is made up of decode->bitspercode bits. + while (decode->shiftbits < decode->bitspercode) { + // Get a byte - we may have to start a new data block + if ((!decode->blocksz && (img->io.fns->read(&img->io, &decode->blocksz, 1) != 1 || !decode->blocksz)) + || img->io.fns->read(&img->io, &bdata, 1) != 1) { + // Pretend we got the EOF code - some encoders seem to just end the file + decode->code_last = decode->code_eof; + return cnt; + } + decode->blocksz--; + + decode->shiftdata |= ((unsigned long)bdata) << decode->shiftbits; + decode->shiftbits += 8; + } + code = decode->shiftdata & BitMask[decode->bitspercode]; + decode->shiftdata >>= decode->bitspercode; + decode->shiftbits -= decode->bitspercode; + /** + * If code cannot fit into bitspercode bits we must raise its size. + * Note that codes above CODE_MAX are used for special signaling. + * If we're using MAX_CODE_BITS bits already and we're at the max code, just + * keep using the table as it is, don't increment decode->bitspercode. + */ + if (decode->code_max < CODE_MAX + 2 && ++decode->code_max > decode->maxcodesz && decode->bitspercode < MAX_CODE_BITS) { + decode->maxcodesz <<= 1; + decode->bitspercode++; + } + + // EOF - the appropriate way to stop decoding + if (code == decode->code_eof) { + // Skip to the end of the data blocks + do { + img->io.fns->seek(&img->io, img->io.pos+decode->blocksz); + } while (img->io.fns->read(&img->io, &decode->blocksz, 1) == 1 && decode->blocksz); + + // Mark the end + decode->code_last = decode->code_eof; + break; + } + + if (code == decode->code_clear) { + // Start again + for(prefix = 0; prefix <= CODE_MAX; prefix++) + decode->prefix[prefix] = CODE_NONE; + decode->code_max = decode->code_eof + 1; + decode->bitspercode = decode->bitsperpixel + 1; + decode->maxcodesz = 1 << decode->bitspercode; + decode->code_last = CODE_NONE; + continue; + } + + if (code < decode->code_clear) { + // Simple unencoded pixel - add it + decode->buf[cnt++] = code; + + } else { + /** + * Its a LZW code - trace the linked list until the prefix is a + * valid pixel while pushing the suffix pixels on the stack. + * If done, pop the stack in reverse order adding the pixels + */ + if (decode->prefix[code] != CODE_NONE) + prefix = code; + + /** + * Only allowed if the code equals the partial code. + * In that case code = XXXCode, CrntCode or the + * prefix code is last code and the suffix char is + * exactly the prefix of last code! + */ + else if (code == decode->code_max - 2 && decode->stackcnt < sizeof(decode->stack)) { + prefix = decode->code_last; + decode->suffix[decode->code_max - 2] = decode->stack[decode->stackcnt++] = getPrefix(decode, decode->code_last); + } else + return 0; + + /** + * If the image is OK we should not get a CODE_NONE while tracing. + * To prevent looping with a bad image we use StackPtr as loop counter + * and stop before overflowing Stack[]. + */ + while (decode->stackcnt < sizeof(decode->stack) && prefix > decode->code_clear && prefix <= CODE_MAX) { + decode->stack[decode->stackcnt++] = decode->suffix[prefix]; + prefix = decode->prefix[prefix]; + } + if (decode->stackcnt >= sizeof(decode->stack) || prefix > CODE_MAX) + return 0; + + /* Push the last character on stack: */ + decode->stack[decode->stackcnt++] = prefix; + } + + if (decode->code_last != CODE_NONE && decode->prefix[decode->code_max - 2] == CODE_NONE) { + decode->prefix[decode->code_max - 2] = decode->code_last; + + /* Only allowed if code is exactly the running code: + * In that case code = XXXCode, CrntCode or the + * prefix code is last code and the suffix char is + * exactly the prefix of last code! */ + decode->suffix[decode->code_max - 2] = getPrefix(decode, code == decode->code_max - 2 ? decode->code_last : code); + } + decode->code_last = code; + } + return cnt; +} + +/** + * Read the info on a frame. + * + * Pre: The file position is at the start of the frame. + */ +static gdispImageError initFrame(gdispImage *img) { + gdispImagePrivate * priv; + imgcache * cache; + uint8_t blocktype; + uint8_t blocksz; + + priv = img->priv; + + // Save the dispose info from the existing frame + priv->dispose.flags = priv->frame.flags; + priv->dispose.paltrans = priv->frame.paltrans; + priv->dispose.x = priv->frame.x; + priv->dispose.y = priv->frame.y; + priv->dispose.width = priv->frame.width; + priv->dispose.height = priv->frame.height; + + // Check for a cached version of this image + for(cache=priv->cache; cache && cache->frame.posstart <= img->io.pos; cache=cache->next) { + if (cache->frame.posstart == img->io.pos) { + priv->frame = cache->frame; + priv->curcache = cache; + return GDISP_IMAGE_ERR_OK; + } + } + + // Get ready for a new image + priv->curcache = 0; + priv->frame.posstart = img->io.pos; + priv->frame.flags = 0; + priv->frame.delay = 0; + priv->frame.palsize = 0; + + // Process blocks until we reach the image descriptor + while(1) { + if (img->io.fns->read(&img->io, &blocktype, 1) != 1) + return GDISP_IMAGE_ERR_BADDATA; + + switch(blocktype) { + case 0x2C: //',' - IMAGE_DESC_RECORD_TYPE; + // Read the Image Descriptor + if (img->io.fns->read(&img->io, priv->buf, 9) != 9) + return GDISP_IMAGE_ERR_BADDATA; + priv->frame.x = *(uint16_t *)(((uint8_t *)priv->buf)+0); + CONVERT_FROM_WORD_LE(priv->frame.x); + priv->frame.y = *(uint16_t *)(((uint8_t *)priv->buf)+2); + CONVERT_FROM_WORD_LE(priv->frame.y); + priv->frame.width = *(uint16_t *)(((uint8_t *)priv->buf)+4); + CONVERT_FROM_WORD_LE(priv->frame.width); + priv->frame.height = *(uint16_t *)(((uint8_t *)priv->buf)+6); + CONVERT_FROM_WORD_LE(priv->frame.height); + if (((uint8_t *)priv->buf)[8] & 0x80) // Local color table? + priv->frame.palsize = 2 << (((uint8_t *)priv->buf)[8] & 0x07); + if (((uint8_t *)priv->buf)[8] & 0x40) // Interlaced? + priv->frame.flags |= GIFL_INTERLACE; + + // We are ready to go for the actual palette read and image decode + priv->frame.pospal = img->io.pos; + priv->frame.posimg = priv->frame.pospal+priv->frame.palsize*3; + priv->frame.posend = 0; + + // Mark this as an animated image if more than 1 frame. + if (priv->frame.posstart != priv->frame0pos) + img->flags |= GDISP_IMAGE_FLG_ANIMATED; + return GDISP_IMAGE_ERR_OK; + + case 0x21: //'!' - EXTENSION_RECORD_TYPE; + // Read the extension type + if (img->io.fns->read(&img->io, &blocktype, 1) != 1) + return GDISP_IMAGE_ERR_BADDATA; + + switch(blocktype) { + case 0xF9: // EXTENSION - Graphics Control Block + // Read the GCB + if (img->io.fns->read(&img->io, priv->buf, 6) != 6) + return GDISP_IMAGE_ERR_BADDATA; + // Check we have read a 4 byte data block and a data block terminator (0) + if (((uint8_t *)priv->buf)[0] != 4 || ((uint8_t *)priv->buf)[5] != 0) + return GDISP_IMAGE_ERR_BADDATA; + // Process the flags + switch(((uint8_t *)priv->buf)[1] & 0x1C) { + case 0x00: case 0x04: break; // Dispose = do nothing + case 0x08: priv->frame.flags |= GIFL_DISPOSECLEAR; break; // Dispose = clear + case 0x0C: case 0x10: priv->frame.flags |= GIFL_DISPOSEREST; break; // Dispose = restore. Value 0x10 is a hack for bad encoders + default: return GDISP_IMAGE_ERR_UNSUPPORTED; + } + if (((uint8_t *)priv->buf)[1] & 0x01) { + priv->frame.flags |= GIFL_TRANSPARENT; + img->flags |= GDISP_IMAGE_FLG_TRANSPARENT; // We set this but never clear it + } + if (((uint8_t *)priv->buf)[1] & 0x02) // Wait for user input? + img->flags |= GDISP_IMAGE_FLG_MULTIPAGE; + else + img->flags &= ~GDISP_IMAGE_FLG_MULTIPAGE; + // Process frame delay and the transparent color (if any) + priv->frame.delay = *(uint16_t *)(((uint8_t *)priv->buf)+2); + CONVERT_FROM_WORD_LE(priv->frame.delay); + priv->frame.paltrans = ((uint8_t *)priv->buf)[4]; + break; + + case 0xFF: // EXTENSION - Application + // We only handle this for the special Netscape loop counter for animation + if (priv->flags & GIF_LOOP) + goto skipdatablocks; + // Read the Application header + if (img->io.fns->read(&img->io, priv->buf, 16) != 16) + return GDISP_IMAGE_ERR_BADDATA; + // Check we have read a 11 byte data block + if (((uint8_t *)priv->buf)[0] != 11 && ((uint8_t *)priv->buf)[12] != 3) + return GDISP_IMAGE_ERR_BADDATA; + // Check the vendor + if (((uint8_t *)priv->buf)[1] == 'N' && ((uint8_t *)priv->buf)[2] == 'E' && ((uint8_t *)priv->buf)[3] == 'T' + && ((uint8_t *)priv->buf)[4] == 'S' && ((uint8_t *)priv->buf)[5] == 'C' && ((uint8_t *)priv->buf)[6] == 'A' + && ((uint8_t *)priv->buf)[7] == 'P' && ((uint8_t *)priv->buf)[8] == 'E' && ((uint8_t *)priv->buf)[9] == '2' + && ((uint8_t *)priv->buf)[10] == '.' && ((uint8_t *)priv->buf)[11] == '0') { + if (((uint8_t *)priv->buf)[13] == 1) { + priv->loops = *(uint16_t *)(((uint8_t *)priv->buf)+14); + CONVERT_FROM_WORD_LE(priv->loops); + priv->flags |= GIF_LOOP; + if (!priv->loops) + priv->flags |= GIF_LOOPFOREVER; + } + } + goto skipdatablocks; + + case 0x01: // EXTENSION - Plain Text (Graphics Rendering) + case 0xFE: // EXTENSION - Comment + default: + // 0x00-0x7F (0-127) are the Graphic Rendering blocks + if (blocktype <= 0x7F) + return GDISP_IMAGE_ERR_UNSUPPORTED; + // 0x80-0xF9 (128-249) are the Control blocks + // 0xFA-0xFF (250-255) are the Special Purpose blocks + // We don't understand this extension - just skip it by skipping data blocks + skipdatablocks: + while(1) { + if (img->io.fns->read(&img->io, &blocksz, 1) != 1) + return GDISP_IMAGE_ERR_BADDATA; + if (!blocksz) + break; + img->io.fns->seek(&img->io, img->io.pos + blocksz); + } + break; + } + break; + + case 0x3B: //';' - TERMINATE_RECORD_TYPE; + // Are we an looping animation + if (!(priv->flags & GIF_LOOP)) + return GDISP_IMAGE_EOF; + if (!(priv->flags & GIF_LOOPFOREVER)) { + if (!priv->loops) + return GDISP_IMAGE_EOF; + priv->loops--; + } + + // Seek back to frame0 + img->io.fns->seek(&img->io, priv->frame0pos); + return GDISP_IMAGE_LOOP; + + default: // UNDEFINED_RECORD_TYPE; + return GDISP_IMAGE_ERR_UNSUPPORTED; + } + } +} + +gdispImageError gdispImageOpen_GIF(gdispImage *img) { + gdispImagePrivate *priv; + uint8_t hdr[6]; + uint16_t aword; + + /* Read the file identifier */ + if (img->io.fns->read(&img->io, hdr, 6) != 6) + return GDISP_IMAGE_ERR_BADFORMAT; // It can't be us + + /* Process the GIFFILEHEADER structure */ + + if (hdr[0] != 'G' || hdr[1] != 'I' || hdr[2] != 'F' + || hdr[3] != '8' || (hdr[4] != '7' && hdr[4] != '9') || hdr[5] != 'a') + return GDISP_IMAGE_ERR_BADFORMAT; // It can't be us + + /* We know we are a GIF format image */ + img->flags = 0; + + /* Allocate our private area */ + if (!(img->priv = (gdispImagePrivate *)gdispImageAlloc(img, sizeof(gdispImagePrivate)))) + return GDISP_IMAGE_ERR_NOMEMORY; + + /* Initialise the essential bits in the private area */ + priv = img->priv; + priv->flags = 0; + priv->palsize = 0; + priv->palette = 0; + priv->frame.flags = 0; + + /* Process the Screen Descriptor structure */ + + // Read the screen descriptor + if (img->io.fns->read(&img->io, priv->buf, 7) != 7) + goto baddatacleanup; + // Get the width + img->width = *(uint16_t *)(((uint8_t *)priv->buf)+0); + CONVERT_FROM_WORD_LE(img->width); + // Get the height + img->height = *(uint16_t *)(((uint8_t *)priv->buf)+2); + CONVERT_FROM_WORD_LE(img->height); + if (((uint8_t *)priv->buf)[4] & 0x80) { + // Global color table + priv->palsize = 2 << (((uint8_t *)priv->buf)[4] & 0x07); + // Allocate the global palette + if (!(priv->palette = (color_t *)gdispImageAlloc(img, priv->palsize*sizeof(color_t)))) + goto nomemcleanup; + // Read the global palette + for(aword = 0; aword < priv->palsize; aword++) { + if (img->io.fns->read(&img->io, &priv->buf, 3) != 3) + goto baddatacleanup; + priv->palette[aword] = RGB2COLOR(((uint8_t *)priv->buf)[0], ((uint8_t *)priv->buf)[1], ((uint8_t *)priv->buf)[2]); + } + } + priv->bgcolor = ((uint8_t *)priv->buf)[5]; + + // Save the fram0pos + priv->frame0pos = img->io.pos; + + // Read the first frame descriptor + switch(initFrame(img)) { + case GDISP_IMAGE_ERR_OK: // Everything OK + return GDISP_IMAGE_ERR_OK; + case GDISP_IMAGE_ERR_UNSUPPORTED: // Unsupported + gdispImageClose_GIF(img); // Clean up the private data area + return GDISP_IMAGE_ERR_UNSUPPORTED; + case GDISP_IMAGE_ERR_NOMEMORY: // Out of Memory + nomemcleanup: + gdispImageClose_GIF(img); // Clean up the private data area + return GDISP_IMAGE_ERR_NOMEMORY; + case GDISP_IMAGE_EOF: // We should have a frame but we don't seem to + case GDISP_IMAGE_LOOP: // We should have a frame but we don't seem to + case GDISP_IMAGE_ERR_BADDATA: // Oops - something wrong with the data + default: + baddatacleanup: + gdispImageClose_GIF(img); // Clean up the private data area + return GDISP_IMAGE_ERR_BADDATA; + } +} + +void gdispImageClose_GIF(gdispImage *img) { + gdispImagePrivate * priv; + imgcache * cache; + imgcache * ncache; + + priv = img->priv; + if (priv) { + // Free any stored frames + cache = priv->cache; + while(cache) { + ncache = cache->next; + gdispImageFree(img, (void *)cache, sizeof(imgcache)+cache->frame.width*cache->frame.height+cache->frame.palsize*sizeof(color_t)); + cache = ncache; + } + if (priv->palette) + gdispImageFree(img, (void *)priv->palette, priv->palsize*sizeof(color_t)); + gdispImageFree(img, (void *)img->priv, sizeof(gdispImagePrivate)); + img->priv = 0; + } + img->io.fns->close(&img->io); +} + +gdispImageError gdispImageCache_GIF(gdispImage *img) { + gdispImagePrivate * priv; + imgcache * cache; + imgdecode * decode; + uint8_t * p; + uint8_t * q; + coord_t mx, my; + uint16_t cnt; + + /* If we are already cached - just return OK */ + priv = img->priv; + if (priv->curcache) + return GDISP_IMAGE_ERR_OK; + + /* We need to allocate the frame, the palette and bits for the image */ + if (!(cache = (imgcache *)gdispImageAlloc(img, sizeof(imgcache) + priv->frame.palsize*sizeof(color_t) + priv->frame.width*priv->frame.height))) + return GDISP_IMAGE_ERR_NOMEMORY; + + /* Initialise the cache */ + decode = 0; + cache->frame = priv->frame; + cache->imagebits = (uint8_t *)(cache+1) + cache->frame.palsize*sizeof(color_t); + cache->next = 0; + + /* Start the decode */ + switch(startDecode(img)) { + case GDISP_IMAGE_ERR_OK: break; + case GDISP_IMAGE_ERR_NOMEMORY: goto nomemcleanup; + case GDISP_IMAGE_ERR_BADDATA: + default: goto baddatacleanup; + } + decode = priv->decode; + + // Save the palette + if (cache->frame.palsize) { + cache->palette = (color_t *)(cache+1); + + /* Copy the local palette into the cache */ + for(cnt = 0; cnt < cache->frame.palsize; cnt++) + cache->palette[cnt] = decode->palette[cnt]; + } else + cache->palette = priv->palette; + + // Check for interlacing + cnt = 0; + if (cache->frame.flags & GIFL_INTERLACE) { + // Every 8th row starting at row 0 + for(p=cache->imagebits, my=0; my < cache->frame.height; my+=8, p += cache->frame.width*7) { + for(mx=0; mx < cache->frame.width; mx++) { + if (!cnt) { + if (!(cnt = getbytes(img))) { + // Sometimes the image EOF is a bit early - treat the rest as transparent + if (decode->code_last != decode->code_eof) + goto baddatacleanup; + while(cnt < sizeof(decode->buf)) + decode->buf[cnt++] = (cache->frame.flags & GIFL_TRANSPARENT) ? cache->frame.paltrans : 0; + } + q = decode->buf; + } + *p++ = *q++; + cnt--; + } + } + // Every 8th row starting at row 4 + for(p=cache->imagebits+cache->frame.width*4, my=4; my < cache->frame.height; my+=8, p += cache->frame.width*7) { + for(mx=0; mx < cache->frame.width; mx++) { + if (!cnt) { + if (!(cnt = getbytes(img))) { + // Sometimes the image EOF is a bit early - treat the rest as transparent + if (decode->code_last != decode->code_eof) + goto baddatacleanup; + while(cnt < sizeof(decode->buf)) + decode->buf[cnt++] = (cache->frame.flags & GIFL_TRANSPARENT) ? cache->frame.paltrans : 0; + } + q = decode->buf; + } + *p++ = *q++; + cnt--; + } + } + // Every 4th row starting at row 2 + for(p=cache->imagebits+cache->frame.width*2, my=2; my < cache->frame.height; my+=4, p += cache->frame.width*3) { + for(mx=0; mx < cache->frame.width; mx++) { + if (!cnt) { + if (!(cnt = getbytes(img))) { + // Sometimes the image EOF is a bit early - treat the rest as transparent + if (decode->code_last != decode->code_eof) + goto baddatacleanup; + while(cnt < sizeof(decode->buf)) + decode->buf[cnt++] = (cache->frame.flags & GIFL_TRANSPARENT) ? cache->frame.paltrans : 0; + } + q = decode->buf; + } + *p++ = *q++; + cnt--; + } + } + // Every 2nd row starting at row 1 + for(p=cache->imagebits+cache->frame.width, my=1; my < cache->frame.height; my+=2, p += cache->frame.width) { + for(mx=0; mx < cache->frame.width; mx++) { + if (!cnt) { + if (!(cnt = getbytes(img))) { + // Sometimes the image EOF is a bit early - treat the rest as transparent + if (decode->code_last != decode->code_eof) + goto baddatacleanup; + while(cnt < sizeof(decode->buf)) + decode->buf[cnt++] = (cache->frame.flags & GIFL_TRANSPARENT) ? cache->frame.paltrans : 0; + } + q = decode->buf; + } + *p++ = *q++; + cnt--; + } + } + } else { + // Every row in sequence + p=cache->imagebits; + for(my=0; my < cache->frame.height; my++) { + for(mx=0; mx < cache->frame.width; mx++) { + if (!cnt) { + if (!(cnt = getbytes(img))) { + // Sometimes the image EOF is a bit early - treat the rest as transparent + if (decode->code_last != decode->code_eof) + goto baddatacleanup; + while(cnt < sizeof(decode->buf)) + decode->buf[cnt++] = (cache->frame.flags & GIFL_TRANSPARENT) ? cache->frame.paltrans : 0; + } + q = decode->buf; + } + *p++ = *q++; + cnt--; + } + } + } + // We could be pedantic here but extra bytes won't hurt us + while(getbytes(img)); + priv->frame.posend = cache->frame.posend = img->io.pos; + + // Save everything + priv->curcache = cache; + if (!priv->cache) + priv->cache = cache; + else if (priv->cache->frame.posstart > cache->frame.posstart) { + cache->next = priv->cache; + priv->cache = cache; + } else { + imgcache *pc; + + for(pc = priv->cache; pc; pc = pc->next) { + if (!pc->next || pc->next->frame.posstart > cache->frame.posstart) { + cache->next = pc->next; + pc->next = cache; + break; + } + } + } + stopDecode(img); + return GDISP_IMAGE_ERR_OK; + +nomemcleanup: + stopDecode(img); + gdispImageFree(img, cache, sizeof(imgcache) + priv->frame.palsize*sizeof(color_t) + priv->frame.width*priv->frame.height); + return GDISP_IMAGE_ERR_NOMEMORY; + +baddatacleanup: + stopDecode(img); + gdispImageFree(img, cache, sizeof(imgcache) + priv->frame.palsize*sizeof(color_t) + priv->frame.width*priv->frame.height); + return GDISP_IMAGE_ERR_BADDATA; +} + +gdispImageError gdispImageDraw_GIF(gdispImage *img, coord_t x, coord_t y, coord_t cx, coord_t cy, coord_t sx, coord_t sy) { + gdispImagePrivate * priv; + imgdecode * decode; + uint8_t * q; + coord_t mx, my, fx, fy; + uint16_t cnt, gcnt; + uint8_t col; + + priv = img->priv; + + /* Handle previous frame disposing */ + if (priv->dispose.flags & (GIFL_DISPOSECLEAR|GIFL_DISPOSEREST)) { + // Clip to the disposal area - clip area = mx,my -> fx, fy (sx,sy,cx,cy are unchanged) + mx = priv->dispose.x; + my = priv->dispose.y; + fx = priv->dispose.x+priv->dispose.width; + fy = priv->dispose.y+priv->dispose.height; + if (sx > mx) mx = sx; + if (sy > my) my = sy; + if (sx+cx <= fx) fx = sx+cx; + if (sy+cy <= fy) fy = sy+cy; + if (fx > mx && fy > my) { + // We only support clearing (not restoring). The specification says that we are allowed to do this. + // Calculate the bgcolor + // The spec says to restore the backgound color (priv->bgcolor) but in practice if there is transparency + // image decoders tend to assume that a restore to the transparent color is required instead + if (((priv->dispose.flags & GIFL_TRANSPARENT) /*&& priv->dispose.paltrans == priv->bgcolor*/) || priv->bgcolor >= priv->palsize) + gdispFillArea(x+mx, y+my, fx-mx, fy-my, img->bgcolor); + else + gdispFillArea(x+mx, y+my, fx-mx, fy-my, priv->palette[priv->bgcolor]); + } + } + + /* Clip to just this frame - clip area = sx,sy -> fx, fy */ + fx = priv->frame.x+priv->frame.width; + fy = priv->frame.y+priv->frame.height; + if (sx >= fx || sy >= fy || sx+cx < priv->frame.x || sy+cy < priv->frame.y) return GDISP_IMAGE_ERR_OK; + if (sx < priv->frame.x) { mx = priv->frame.x - sx; x += mx; cx -= mx; sx = priv->frame.x; } + if (sy < priv->frame.y) { my = priv->frame.y - sy; y += my; cy -= my; sy = priv->frame.y; } + if (sx+cx > fx) cx = fx-sx; + if (sy+cy > fy) cy = fy-sy; + + // Make sx, sy relative to this frame so we are not adding priv->frame.x & priv->frame.y each time + sx -= priv->frame.x; sy -= priv->frame.y; + fx = sx + cx; + fy = sy + cy; + + /* Draw from the image cache - if it exists */ + if (priv->curcache) { + imgcache * cache; + + cache = priv->curcache; + q = cache->imagebits+priv->frame.width*sy+sx; + + for(my=sy; my < fy; my++, q += priv->frame.width - cx) { + for(gcnt=0, mx=sx, cnt=0; mx < fx; mx++) { + col = *q++; + if ((priv->frame.flags & GIFL_TRANSPARENT) && col == priv->frame.paltrans) { + // We have a transparent pixel - dump the buffer to the display + switch(gcnt) { + case 0: break; + case 1: gdispDrawPixel(x+mx-gcnt, y+my, priv->buf[0]); break; + default: gdispBlitAreaEx(x+mx-gcnt, y+my, gcnt, 1, 0, 0, gcnt, priv->buf); break; + } + gcnt = 0; + continue; + } + priv->buf[gcnt++] = cache->palette[col]; + if (gcnt >= BLIT_BUFFER_SIZE) { + // We have run out of buffer - dump it to the display + gdispBlitAreaEx(x+mx-gcnt+1, y+my, gcnt, 1, 0, 0, gcnt, priv->buf); + gcnt = 0; + } + } + // We have finished the line - dump the buffer to the display + switch(gcnt) { + case 0: break; + case 1: gdispDrawPixel(x+mx-gcnt, y+my, priv->buf[0]); break; + default: gdispBlitAreaEx(x+mx-gcnt, y+my, gcnt, 1, 0, 0, gcnt, priv->buf); break; + } + } + + return GDISP_IMAGE_ERR_OK; + } + + /* Start the decode */ + switch(startDecode(img)) { + case GDISP_IMAGE_ERR_OK: break; + case GDISP_IMAGE_ERR_NOMEMORY: return GDISP_IMAGE_ERR_NOMEMORY; + case GDISP_IMAGE_ERR_BADDATA: + default: return GDISP_IMAGE_ERR_BADDATA; + } + decode = priv->decode; + + // Check for interlacing + cnt = 0; + if (priv->frame.flags & GIFL_INTERLACE) { + // Every 8th row starting at row 0 + for(my=0; my < priv->frame.height; my+=8) { + for(gcnt=0, mx=0; mx < priv->frame.width; mx++, q++, cnt--) { + if (!cnt) { + if (!(cnt = getbytes(img))) { + // Sometimes the image EOF is a bit early - treat the rest as transparent + if (decode->code_last != decode->code_eof) + goto baddatacleanup; + mx++; + break; + } + q = decode->buf; + } + if (my >= sy && my < fy && mx >= sx && mx < fx) { + col = *q; + if ((priv->frame.flags & GIFL_TRANSPARENT) && col == priv->frame.paltrans) { + // We have a transparent pixel - dump the buffer to the display + switch(gcnt) { + case 0: break; + case 1: gdispDrawPixel(x+mx-gcnt, y+my, priv->buf[0]); break; + default: gdispBlitAreaEx(x+mx-gcnt, y+my, gcnt, 1, 0, 0, gcnt, priv->buf); break; + } + gcnt = 0; + continue; + } + priv->buf[gcnt++] = decode->palette[col]; + if (gcnt >= BLIT_BUFFER_SIZE) { + // We have run out of buffer - dump it to the display + gdispBlitAreaEx(x+mx-gcnt+1, y+my, gcnt, 1, 0, 0, gcnt, priv->buf); + gcnt = 0; + } + } + } + // We have finished the line - dump the buffer to the display + switch(gcnt) { + case 0: break; + case 1: gdispDrawPixel(x+mx-gcnt, y+my, priv->buf[0]); break; + default: gdispBlitAreaEx(x+mx-gcnt, y+my, gcnt, 1, 0, 0, gcnt, priv->buf); break; + } + } + // Every 8th row starting at row 4 + for(my=4; my < priv->frame.height; my+=8) { + for(gcnt=0, mx=0; mx < priv->frame.width; mx++, q++, cnt--) { + if (!cnt) { + if (!(cnt = getbytes(img))) { + // Sometimes the image EOF is a bit early - treat the rest as transparent + if (decode->code_last != decode->code_eof) + goto baddatacleanup; + mx++; + break; + } + q = decode->buf; + } + if (my >= sy && my < fy && mx >= sx && mx < fx) { + col = *q; + if ((priv->frame.flags & GIFL_TRANSPARENT) && col == priv->frame.paltrans) { + // We have a transparent pixel - dump the buffer to the display + switch(gcnt) { + case 0: break; + case 1: gdispDrawPixel(x+mx-gcnt, y+my, priv->buf[0]); break; + default: gdispBlitAreaEx(x+mx-gcnt, y+my, gcnt, 1, 0, 0, gcnt, priv->buf); break; + } + gcnt = 0; + continue; + } + priv->buf[gcnt++] = decode->palette[col]; + if (gcnt >= BLIT_BUFFER_SIZE) { + // We have run out of buffer - dump it to the display + gdispBlitAreaEx(x+mx-gcnt+1, y+my, gcnt, 1, 0, 0, gcnt, priv->buf); + gcnt = 0; + } + } + } + // We have finished the line - dump the buffer to the display + switch(gcnt) { + case 0: break; + case 1: gdispDrawPixel(x+mx-gcnt, y+my, priv->buf[0]); break; + default: gdispBlitAreaEx(x+mx-gcnt, y+my, gcnt, 1, 0, 0, gcnt, priv->buf); break; + } + } + // Every 4th row starting at row 2 + for(my=2; my < priv->frame.height; my+=4) { + for(gcnt=0, mx=0; mx < priv->frame.width; mx++, q++, cnt--) { + if (!cnt) { + if (!(cnt = getbytes(img))) { + // Sometimes the image EOF is a bit early - treat the rest as transparent + if (decode->code_last != decode->code_eof) + goto baddatacleanup; + mx++; + break; + } + q = decode->buf; + } + if (my >= sy && my < fy && mx >= sx && mx < fx) { + col = *q; + if ((priv->frame.flags & GIFL_TRANSPARENT) && col == priv->frame.paltrans) { + // We have a transparent pixel - dump the buffer to the display + switch(gcnt) { + case 0: break; + case 1: gdispDrawPixel(x+mx-gcnt, y+my, priv->buf[0]); break; + default: gdispBlitAreaEx(x+mx-gcnt, y+my, gcnt, 1, 0, 0, gcnt, priv->buf); break; + } + gcnt = 0; + continue; + } + priv->buf[gcnt++] = decode->palette[col]; + if (gcnt >= BLIT_BUFFER_SIZE) { + // We have run out of buffer - dump it to the display + gdispBlitAreaEx(x+mx-gcnt+1, y+my, gcnt, 1, 0, 0, gcnt, priv->buf); + gcnt = 0; + } + } + } + // We have finished the line - dump the buffer to the display + switch(gcnt) { + case 0: break; + case 1: gdispDrawPixel(x+mx-gcnt, y+my, priv->buf[0]); break; + default: gdispBlitAreaEx(x+mx-gcnt, y+my, gcnt, 1, 0, 0, gcnt, priv->buf); break; + } + } + // Every 2nd row starting at row 1 + for(my=1; my < priv->frame.height; my+=2) { + for(gcnt=0, mx=0; mx < priv->frame.width; mx++, q++, cnt--) { + if (!cnt) { + if (!(cnt = getbytes(img))) { + // Sometimes the image EOF is a bit early - treat the rest as transparent + if (decode->code_last != decode->code_eof) + goto baddatacleanup; + mx++; + break; + } + q = decode->buf; + } + if (my >= sy && my < fy && mx >= sx && mx < fx) { + col = *q; + if ((priv->frame.flags & GIFL_TRANSPARENT) && col == priv->frame.paltrans) { + // We have a transparent pixel - dump the buffer to the display + switch(gcnt) { + case 0: break; + case 1: gdispDrawPixel(x+mx-gcnt, y+my, priv->buf[0]); break; + default: gdispBlitAreaEx(x+mx-gcnt, y+my, gcnt, 1, 0, 0, gcnt, priv->buf); break; + } + gcnt = 0; + continue; + } + priv->buf[gcnt++] = decode->palette[col]; + if (gcnt >= BLIT_BUFFER_SIZE) { + // We have run out of buffer - dump it to the display + gdispBlitAreaEx(x+mx-gcnt+1, y+my, gcnt, 1, 0, 0, gcnt, priv->buf); + gcnt = 0; + } + } + } + // We have finished the line - dump the buffer to the display + switch(gcnt) { + case 0: break; + case 1: gdispDrawPixel(x+mx-gcnt, y+my, priv->buf[0]); break; + default: gdispBlitAreaEx(x+mx-gcnt, y+my, gcnt, 1, 0, 0, gcnt, priv->buf); break; + } + } + } else { + // Every row in sequence + for(my=0; my < priv->frame.height; my++) { + for(gcnt=0, mx=0; mx < priv->frame.width; mx++, q++, cnt--) { + if (!cnt) { + if (!(cnt = getbytes(img))) { + // Sometimes the image EOF is a bit early - treat the rest as transparent + if (decode->code_last != decode->code_eof) + goto baddatacleanup; + mx++; + break; + } + q = decode->buf; + } + if (my >= sy && my < fy && mx >= sx && mx < fx) { + col = *q; + if ((priv->frame.flags & GIFL_TRANSPARENT) && col == priv->frame.paltrans) { + // We have a transparent pixel - dump the buffer to the display + switch(gcnt) { + case 0: break; + case 1: gdispDrawPixel(x+mx-gcnt, y+my, priv->buf[0]); break; + default: gdispBlitAreaEx(x+mx-gcnt, y+my, gcnt, 1, 0, 0, gcnt, priv->buf); break; + } + gcnt = 0; + continue; + } + priv->buf[gcnt++] = decode->palette[col]; + if (gcnt >= BLIT_BUFFER_SIZE) { + // We have run out of buffer - dump it to the display + gdispBlitAreaEx(x+mx-gcnt+1, y+my, gcnt, 1, 0, 0, gcnt, priv->buf); + gcnt = 0; + } + } + } + // We have finished the line - dump the buffer to the display + switch(gcnt) { + case 0: break; + case 1: gdispDrawPixel(x+mx-gcnt, y+my, priv->buf[0]); break; + default: gdispBlitAreaEx(x+mx-gcnt, y+my, gcnt, 1, 0, 0, gcnt, priv->buf); break; + } + } + } + // We could be pedantic here but extra bytes won't hurt us + while (getbytes(img)); + priv->frame.posend = img->io.pos; + + stopDecode(img); + return GDISP_IMAGE_ERR_OK; + +baddatacleanup: + stopDecode(img); + return GDISP_IMAGE_ERR_BADDATA; +} + +systime_t gdispImageNext_GIF(gdispImage *img) { + gdispImagePrivate * priv; + systime_t delay; + uint8_t blocksz; + + priv = img->priv; + + // Save the delay and convert to millisecs + delay = (systime_t)priv->frame.delay * 10; + + // We need to get to the end of this frame + if (!priv->frame.posend) { + // We don't know where the end of the frame is yet - find it! + img->io.fns->seek(&img->io, priv->frame.posimg+1); // Skip the code size byte too + while(1) { + if (img->io.fns->read(&img->io, &blocksz, 1) != 1) + return TIME_INFINITE; + if (!blocksz) + break; + img->io.fns->seek(&img->io, img->io.pos + blocksz); + } + priv->frame.posend = img->io.pos; + } + + // Seek to the end of this frame + img->io.fns->seek(&img->io, priv->frame.posend); + + // Read the next frame descriptor + for(blocksz=0; blocksz < 2; blocksz++) { // 2 loops max to prevent cycling forever with a bad file + switch(initFrame(img)) { + case GDISP_IMAGE_ERR_OK: // Everything OK + return delay; + case GDISP_IMAGE_LOOP: // Back to the beginning + break; + case GDISP_IMAGE_EOF: // The real End-Of-File + case GDISP_IMAGE_ERR_BADDATA: // Oops - something wrong with the data + case GDISP_IMAGE_ERR_NOMEMORY: // Out of Memory + case GDISP_IMAGE_ERR_UNSUPPORTED: // Unsupported + default: + return TIME_INFINITE; + } + } + return TIME_INFINITE; +} #endif /* GFX_USE_GDISP && GDISP_NEED_IMAGE && GDISP_NEED_IMAGE_GIF */ /** @} */