aboutsummaryrefslogtreecommitdiffstats
path: root/src/gdisp/image_gif.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/gdisp/image_gif.c')
-rw-r--r--src/gdisp/image_gif.c1170
1 files changed, 1156 insertions, 14 deletions
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"
-
-/* 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;
-
-/* 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);
+/**
+ * Helper Routines Needed
+ */
+void *gdispImageAlloc(gdispImage *img, size_t sz);
+void gdispImageFree(gdispImage *img, void *ptr, size_t sz);
+
+/**
+ * How big an array to allocate for blitting (in pixels)
+ * Bigger is faster but uses more RAM.
+ */
+#define BLIT_BUFFER_SIZE 32
+
+/*
+ * 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<<MAX_CODE_BITS)-1) // Maximum legal code value
+#define CODE_FLUSH (CODE_MAX+1) // Illegal code to signal flush
+#define CODE_FIRST (CODE_MAX+2) // Illegal code to signal first
+#define CODE_NONE (CODE_MAX+3) // Illegal code to signal empty
+
+// Convert bits to masks for that number of bits
+static const uint16_t BitMask[] = {
+ 0x0000, 0x0001, 0x0003, 0x0007,
+ 0x000f, 0x001f, 0x003f, 0x007f,
+ 0x00ff, 0x01ff, 0x03ff, 0x07ff,
+ 0x0fff
+ };
+
+// Structure for decoding a single frame
+typedef struct imgdecode {
+ uint8_t blocksz; // The size of the block currently being processed
+ uint8_t maxpixel; // The maximum allowed pixel value
+ uint8_t bitsperpixel;
+ uint8_t bitspercode;
+ uint8_t shiftbits;
+ uint16_t maxcodesz;
+ uint16_t stackcnt; // The number of items on the stack
+ uint16_t code_clear;
+ uint16_t code_eof;
+ uint16_t code_max;
+ uint16_t code_last;
+ uint32_t shiftdata;
+ color_t * palette;
+ uint8_t buf[BLIT_BUFFER_SIZE]; // Buffer for decoded pixels
+ uint16_t prefix[1<<MAX_CODE_BITS]; // The LZW table
+ uint8_t suffix[1<<MAX_CODE_BITS]; // So we can trace the codes
+ uint8_t stack[1<<MAX_CODE_BITS]; // Decoded pixels might be stacked here
+} imgdecode;
+
+// The data on a single frame
+typedef struct imgframe {
+ coord_t x, y; // position relative to full image
+ coord_t width, height; // size of frame
+ uint16_t delay; // delay after processing
+ uint8_t flags; // Local flags
+ #define GIFL_TRANSPARENT 0x01 // There exists a transparent color
+ #define GIFL_DISPOSECLEAR 0x02 // Dispose this frame by clearing
+ #define GIFL_DISPOSEREST 0x04 // Dispose this frame by restoring
+ #define GIFL_INTERLACE 0x08 // Current frame is interlaced
+ uint8_t paltrans; // Transparency
+ uint16_t palsize; // Local palette size
+ size_t posstart; // The file position of the start of the image
+ size_t pospal; // The file position of the palette
+ size_t posimg; // The file position of the image bits
+ size_t posend; // The file position of the end of the frame
+} imgframe;
+
+// The data for a cache
+typedef struct imgcache {
+ imgframe frame;
+ color_t * palette; // Local palette
+ uint8_t * imagebits; // Image bits - only saved when caching
+ struct imgcache * next; // Next cached frame
+} imgcache;
+
+// The data for a dispose area
+typedef struct imgdispose {
+ uint8_t flags; // Frame flags
+ uint8_t paltrans; // Transparency
+ coord_t x, y; // position relative to full image
+ coord_t width, height; // size of dispose area
+} imgdispose;
+
+typedef struct gdispImagePrivate {
+ uint8_t flags; // Flags (global)
+ #define GIF_LOOP 0x01 // Loop back to first frame
+ #define GIF_LOOPFOREVER 0x02 // Looping is forever
+ uint8_t bgcolor; // Background Color (global)
+ uint16_t loops; // Remaining frame loops (if animated)
+ uint16_t palsize; // Global palette size (global)
+ pixel_t *palette; // Global palette (global)
+ size_t frame0pos; // The position of the first frame
+ imgcache * cache; // The list of cached frames
+ imgcache * curcache; // The cache of the current frame (if created)
+ imgdecode * decode; // The decode data for the decode in progress
+ imgframe frame;
+ imgdispose dispose;
+ pixel_t buf[BLIT_BUFFER_SIZE]; // Buffer for reading and blitting
+ } gdispImagePrivate;
+
+/**
+ * Get ready for decoding a frame.
+ *
+ * Pre: Frame info has been read.
+ */
+static gdispImageError startDecode(gdispImage *img) {
+ gdispImagePrivate * priv;
+ imgdecode * decode;
+ uint16_t cnt;
+
+ priv = img->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 */
/** @} */