From 309e287b7c5ca4fb505d7756748d01da5c0fc203 Mon Sep 17 00:00:00 2001 From: "Carsten Haitzler (Rasterman)" Date: Wed, 30 Oct 2013 18:16:09 +0900 Subject: [PATCH] evas - gif loader rewrite (clean up code, document it a lot AND fix bugs) after several days of beating head on desk, i gave up trying to find the exact cause of some gifs not rendering right as animated gifs due to the loader. it had something to do with dispose mode handling and which frame it was applied to. i noticed the structure made it also hard to fix, so this re-structures the entire thing with cleaner code, less code AND more comments, with a limited memory cache (512k) for previous frames per image (to avoid eating up huge memory blobs for big/long animations - though at the expense of cpu), and with some notes for future fixes - like fixing the "load 2 copies of the same animated gif" issue... that's another day. this does fix https://phab.enlightenment.org/T443 along with many other things. --- .../evas/loaders/gif/evas_image_load_gif.c | 1446 +++++++---------- 1 file changed, 617 insertions(+), 829 deletions(-) diff --git a/src/modules/evas/loaders/gif/evas_image_load_gif.c b/src/modules/evas/loaders/gif/evas_image_load_gif.c index 6c2c634d6a..3f2ef53f11 100644 --- a/src/modules/evas/loaders/gif/evas_image_load_gif.c +++ b/src/modules/evas/loaders/gif/evas_image_load_gif.c @@ -8,315 +8,210 @@ #include -#ifndef MIN -# define MIN(a, b) (((a) < (b)) ? (a) : (b)) -#endif +typedef struct _Frame_Info Frame_Info; +typedef struct _Loader_Info Loader_Info; -typedef struct _Gif_Frame Gif_Frame; - -typedef enum _Frame_Load_Type -{ - LOAD_FRAME_NONE = 0, - LOAD_FRAME_INFO = 1, - LOAD_FRAME_DATA = 2, - LOAD_FRAME_DATA_INFO = 3 -} Frame_Load_Type; - -struct _Gif_Frame -{ - struct { - /* Image descriptor */ - int x; - int y; - int w; - int h; - int interlace; - } image_des; - - struct { - /* Graphic Control*/ - int disposal; - int transparent; - int delay; - int input; - } frame_info; - int bg_val; -}; - -typedef struct _Evas_Loader_Internal Evas_Loader_Internal; -struct _Evas_Loader_Internal +struct _Loader_Info { Eina_File *f; Evas_Image_Load_Opts *opts; Evas_Image_Animated *animated; }; -static Eina_Bool evas_image_load_specific_frame(Eina_File *f, const Evas_Image_Load_Opts *opts, Evas_Image_Property *prop, Evas_Image_Animated *animated, int frame_index, int *error); - -#define byte2_to_int(a,b) (((b)<<8)|(a)) - -#define FRAME_MAX 1024 - -/* find specific frame in image entry */ -static Eina_Bool -_find_frame(Evas_Image_Animated *animated, int frame_index, Image_Entry_Frame **frame) +struct _Frame_Info { - Eina_List *l; - Image_Entry_Frame *hit_frame = NULL; - - if (!animated->frames) return EINA_FALSE; - - EINA_LIST_FOREACH(animated->frames, l, hit_frame) - { - if (hit_frame->index == frame_index) - { - *frame = hit_frame; - return EINA_TRUE; - } - } - return EINA_FALSE; -} - -static Eina_Bool -_find_close_frame(Evas_Image_Animated *animated, int frame_index, Image_Entry_Frame **frame) -{ - Eina_Bool hit = EINA_FALSE; - int i; - - i = frame_index -1; - - if (!animated->frames) return EINA_FALSE; - - for (; i > 0; i--) - { - hit = _find_frame(animated, i, frame); - if (hit) - return EINA_TRUE; - } - return EINA_FALSE; -} - -static Eina_Bool -_evas_image_skip_frame(GifFileType *gif, int frame) -{ - int remain_frame = 0; - GifRecordType rec; - - if (!gif) return EINA_FALSE; - if (frame == 0) return EINA_TRUE; /* no need to skip */ - if (frame < 0 || frame > FRAME_MAX) return EINA_FALSE; - - remain_frame = frame; - - do - { - if (DGifGetRecordType(gif, &rec) == GIF_ERROR) return EINA_FALSE; - - if (rec == EXTENSION_RECORD_TYPE) - { - int ext_code; - GifByteType *ext; - - ext = NULL; - DGifGetExtension(gif, &ext_code, &ext); - while (ext) - { /*skip extention */ - ext = NULL; - DGifGetExtensionNext(gif, &ext); - } - } - - if (rec == IMAGE_DESC_RECORD_TYPE) - { - int img_code; - GifByteType *img; - - if (DGifGetImageDesc(gif) == GIF_ERROR) return EINA_FALSE; - - remain_frame --; - /* we have to count frame, so use DGifGetCode and skip decoding */ - if (DGifGetCode(gif, &img_code, &img) == GIF_ERROR) return EINA_FALSE; - - while (img) - { - img = NULL; - DGifGetCodeNext(gif, &img); - } - if (remain_frame < 1) return EINA_TRUE; - } - if (rec == TERMINATE_RECORD_TYPE) return EINA_FALSE; /* end of file */ - - } while ((rec != TERMINATE_RECORD_TYPE) && (remain_frame > 0)); - return EINA_FALSE; -} - -static Eina_Bool -_evas_image_load_frame_graphic_info(Image_Entry_Frame *frame, GifByteType *ext) -{ - Gif_Frame *gif_frame = NULL; - if ((!frame) || (!ext)) return EINA_FALSE; - - gif_frame = (Gif_Frame *) frame->info; - - /* transparent */ - if ((ext[1] & 0x1) != 0) - gif_frame->frame_info.transparent = ext[4]; - else - gif_frame->frame_info.transparent = -1; - - gif_frame->frame_info.input = (ext[1] >>1) & 0x1; - gif_frame->frame_info.disposal = (ext[1] >>2) & 0x7; - gif_frame->frame_info.delay = byte2_to_int(ext[2], ext[3]); - return EINA_TRUE; -} - -static Eina_Bool -_evas_image_load_frame_image_des_info(GifFileType *gif, Image_Entry_Frame *frame) -{ - Gif_Frame *gif_frame = NULL; - if ((!gif) || (!frame)) return EINA_FALSE; - - gif_frame = (Gif_Frame *) frame->info; - gif_frame->image_des.x = gif->Image.Left; - gif_frame->image_des.y = gif->Image.Top; - gif_frame->image_des.w = gif->Image.Width; - gif_frame->image_des.h = gif->Image.Height; - gif_frame->image_des.interlace = gif->Image.Interlace; - return EINA_TRUE; -} + int x, y, w, h; + unsigned short delay; // delay time in 1/100ths of a sec + short transparent : 10; // -1 == not, anything else == index + short dispose : 6; // 0, 1, 2, 3 (others invalid) + short interlace : 1; // interlaced or not +}; +#ifndef MIN +# define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif +#define LOADERR(x) \ +do { \ + *error = (x); \ + goto on_error; \ +} while (0) #define PIX(_x, _y) rows[yin + _y][xin + _x] #define CMAP(_v) cmap->Colors[_v] +#define PIXLK(_p) ARGB_JOIN(0xff, CMAP(_p).Red, CMAP(_p).Green, CMAP(_p).Blue) -static void -_expand_gif_rows(GifRowType *rows, ColorMapObject *cmap, DATA32 *ptr, - int rowpix, int xin, int yin, int w, int h, - int x, int y, int alpha, Eina_Bool overwrite) +// utility funcs... + +// brute force find frame index - gifs are normally saml so ok for now +static Image_Entry_Frame * +_find_frame(Evas_Image_Animated *animated, int index) { - int xx, yy, pix; + Eina_List *l; + Image_Entry_Frame *frame; + + if (!animated->frames) return NULL; + EINA_LIST_FOREACH(animated->frames, l, frame) + { + if (frame->index == index) return frame; + } + return NULL; +} + +// fill in am image with a specific rgba color value +static void +_fill_image(DATA32 *data, int rowpix, DATA32 val, int x, int y, int w, int h) +{ + int xx, yy; DATA32 *p; - if (alpha >= 0) + for (yy = 0; yy < h; yy++) { - if (overwrite) + p = data + ((y + yy) * rowpix) + x; + for (xx = 0; xx < w; xx++) { - for (yy = 0; yy < h; yy++) - { - p = ptr + ((y + yy) * rowpix) + x; - for (xx = 0; xx < w; xx++) - { - pix = PIX(xx, yy); - if (pix != alpha) - *p = ARGB_JOIN(0xff, CMAP(pix).Red, - CMAP(pix).Green, CMAP(pix).Blue); - else *p = 0; - p++; - } - } - } - else - { - for (yy = 0; yy < h; yy++) - { - p = ptr + ((y + yy) * rowpix) + x; - for (xx = 0; xx < w; xx++) - { - pix = PIX(xx, yy); - if (pix != alpha) - *p = ARGB_JOIN(0xff, CMAP(pix).Red, - CMAP(pix).Green, CMAP(pix).Blue); - p++; - } - } + *p = val; + p++; } } +} + +// fix coords and work out an x and y inset in orig data if out of image bounds +static void +_clip_coords(int imw, int imh, int *xin, int *yin, + int x0, int y0, int w0, int h0, + int *x, int *y, int *w, int *h) +{ + if (x0 < 0) + { + w0 += x0; + *xin = -x0; + x0 = 0; + } + if ((x0 + w0) > imw) w0 = imw - x0; + if (y0 < 0) + { + h0 += y0; + *yin = -y0; + y0 = 0; + } + if ((y0 + h0) > imh) h0 = imh - y0; + *x = x0; + *y = y0; + *w = w0; + *h = h0; +} + +// file a rgba data pixle blob with a frame color (bg or trans) depending... +static void +_fill_frame(DATA32 *data, int rowpix, GifFileType *gif, Frame_Info *finfo, + int x, int y, int w, int h) +{ + // solid color fill for pre frame region + if (finfo->transparent < 0) + { + ColorMapObject *cmap; + int bg; + + // work out color to use from cmap + if (gif->Image.ColorMap) cmap = gif->Image.ColorMap; + else cmap = gif->SColorMap; + bg = gif->SBackGroundColor; + // and do the fill + _fill_image + (data, rowpix, + ARGB_JOIN(0xff, CMAP(bg).Red, CMAP(bg).Green, CMAP(bg).Blue), + x, y, w, h); + } + // fill in region with 0 (transparent) else - { - for (yy = 0; yy < h; yy++) - { - p = ptr + ((y + yy) * rowpix) + x; - for (xx = 0; xx < w; xx++) - { - pix = PIX(xx, yy); - *p = ARGB_JOIN(0xff, CMAP(pix).Red, - CMAP(pix).Green, CMAP(pix).Blue); - p++; - } - } - } + _fill_image(data, rowpix, 0, x, y, w, h); } +// store common fields from gif file info into frame info static void -_copy_pixels(DATA32 *ptr_src, DATA32 *ptr, int rowpix, - int x, int y, int w, int h) +_store_frame_info(GifFileType *gif, Frame_Info *finfo) { - DATA32 *p1, *p2, *pe; - int yy; - - if ((w <= 0) || (h <= 0)) return; - for (yy = 0; yy < h; yy++) - { - p1 = ptr_src + ((y + yy) * rowpix) + x; - p2 = ptr + ((y + yy) * rowpix) + x; - for (pe = p2 + w; p2 < pe;) *p2++ = *p1++; - } + finfo->x = gif->Image.Left; + finfo->y = gif->Image.Top; + finfo->w = gif->Image.Width; + finfo->h = gif->Image.Height; + finfo->interlace = gif->Image.Interlace; } +// check if image fills "screen space" and if so, if it is transparent +// at all then the image could be transparent - OR if image doesnt fill, +// then it could be trasnparent (full coverage of screen). some gifs will +// be recognized as solid here for faster rendering, but not all. static void -_fill_pixels(DATA32 val, DATA32 *ptr, int rowpix, - int x, int y, int w, int h) +_check_transparency(Eina_Bool *full, Frame_Info *finfo, int w, int h) { - DATA32 *p2, *pe; - int yy; - - if ((w <= 0) || (h <= 0)) return; - for (yy = 0; yy < h; yy++) + if ((finfo->x == 0) && (finfo->y == 0) && + (finfo->w == w) && (finfo->h == h)) { - p2 = ptr + ((y + yy) * rowpix) + x; - for (pe = p2 + w; p2 < pe;) *p2++ = val; + if (finfo->transparent >= 0) *full = EINA_FALSE; } + else *full = EINA_FALSE; } +// allocate frame and frame info and append to list and store fields +static Frame_Info * +_new_frame(Evas_Image_Animated *animated, + int transparent, int dispose, int delay, + int index) +{ + Image_Entry_Frame *frame; + Frame_Info *finfo; + + // allocate frame and frame info data (MUSt be separate) + frame = calloc(1, sizeof(Image_Entry_Frame)); + if (!frame) return NULL; + finfo = calloc(1, sizeof(Frame_Info)); + if (!finfo) + { + free(frame); + return NULL; + } + // record transparent index to be used or -1 if none + // for this SPECIFIC frame + finfo->transparent = transparent; + // record dispose mode (3 bits) + finfo->dispose = dispose; + // record delay (2 bytes so max 65546 /100 sec) + finfo->delay = delay; + // record the index number we are at + frame->index = index; + // that frame is stored AT image/screen size + frame->info = finfo; + animated->frames = eina_list_append(animated->frames, frame); + return finfo; +} + +// decode a gif image into rows then expand to 32bit into the destination +// data pointer static Eina_Bool -_evas_image_load_frame_image_data(Eina_File *f, - const Evas_Image_Load_Opts *opts EINA_UNUSED, - Evas_Image_Property *prop, - Evas_Image_Animated *animated, - GifFileType *gif, Image_Entry_Frame *frame, int *error) +_decode_image(GifFileType *gif, DATA32 *data, int rowpix, int xin, int yin, + int transparent, int x, int y, int w, int h, Eina_Bool fill) { - ColorMapObject *cmap; - GifRowType *rows; - DATA32 *ptr, pad = 0xff000000; - Gif_Frame *gif_frame = NULL; int intoffset[] = { 0, 4, 2, 1 }; int intjump[] = { 8, 8, 4, 2 }; - int x, y, w, h, i, bg, alpha, cache_w, cache_h, cur_h, cur_w, yy; - int disposal = 0, xin = 0, yin = 0; - - if ((!gif) || (!frame)) return EINA_FALSE; - - gif_frame = (Gif_Frame *) frame->info; - w = gif->Image.Width; - h = gif->Image.Height; - x = gif->Image.Left; - y = gif->Image.Top; - cur_h = h; - cur_w = w; - cache_w = prop->w; - cache_h = prop->h; + int i, xx, yy, pix; + GifRowType *rows; + Eina_Bool ret = EINA_FALSE; + ColorMapObject *cmap; + DATA32 *p; + // build a blob of memory to have pointers to rows of pixels + // AND store the decoded gif pixels (1 byte per pixel) as welll rows = malloc((h * sizeof(GifRowType *)) + (w * h * sizeof(GifPixelType))); - if (!rows) - { - *error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED; - return EINA_FALSE; - } + if (!rows) goto on_error; + + // fill in the pointers at the start for (yy = 0; yy < h; yy++) { rows[yy] = ((unsigned char *)rows) + (h * sizeof(GifRowType *)) + (yy * w * sizeof(GifPixelType)); } - + + // if give is interlaced, walk interlace pattern and decode into rows if (gif->Image.Interlace) { for (i = 0; i < 4; i++) @@ -324,719 +219,612 @@ _evas_image_load_frame_image_data(Eina_File *f, for (yy = intoffset[i]; yy < h; yy += intjump[i]) { if (DGifGetLine(gif, rows[yy], w) != GIF_OK) - { - *error = EVAS_LOAD_ERROR_CORRUPT_FILE; - goto error; - } + goto on_error; } } } + // normal top to bottom - decode into rows else { for (yy = 0; yy < h; yy++) { if (DGifGetLine(gif, rows[yy], w) != GIF_OK) - { - *error = EVAS_LOAD_ERROR_CORRUPT_FILE; - goto error; - } + goto on_error; } } - alpha = gif_frame->frame_info.transparent; - if ((prop->alpha) || (alpha >= 0)) pad = 0x00000000; - frame->data = malloc(cache_w * cache_h * sizeof(DATA32)); - if (!frame->data) - { - *error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED; - goto error; - } - bg = gif->SBackGroundColor; - cmap = (gif->Image.ColorMap ? gif->Image.ColorMap : gif->SColorMap); + // work out what colormap to use + if (gif->Image.ColorMap) cmap = gif->Image.ColorMap; + else cmap = gif->SColorMap; - if (!cmap) + // if we need to deal with transparent pixels at all... + if (transparent >= 0) { - DGifCloseFile(gif); - if (frame->data) free(frame->data); - *error = EVAS_LOAD_ERROR_CORRUPT_FILE; - goto error; - } - - if (cur_h > cache_h) cur_h = cache_h; - if (cur_w > cache_w) cur_w = cache_w; - - // clip the image region to be within current image size and note the inset - if (x < 0) - { - w += x; xin = -x; x = 0; - } - if ((x + w) > cache_w) w = cache_w - x; - if (y < 0) - { - h += y; yin = -y; y = 0; - } - if ((y + h) > cache_h) h = cache_h - y; - - // data ptr to write to - ptr = frame->data; - if (frame->index > 1) - { - /* get previous frame only frame index is bigger than 1 */ - DATA32 *ptr_src; - Image_Entry_Frame *new_frame = NULL; - int cur_frame = frame->index; - int start_frame = 1; - - if (_find_close_frame(animated, cur_frame, &new_frame)) - start_frame = new_frame->index + 1; - - if ((start_frame < 1) || (start_frame > cur_frame)) + // if we are told to FILL (overwrite with transparency kept) + if (fill) { - *error = EVAS_LOAD_ERROR_CORRUPT_FILE; - goto error; - } - /* load previous frame of cur_frame */ - for (i = start_frame; i < cur_frame; i++) - { - // FIXME : that one -v - if (!evas_image_load_specific_frame(f, opts, prop, animated, i, error)) + for (yy = 0; yy < h; yy++) { - *error = EVAS_LOAD_ERROR_CORRUPT_FILE; - goto error; + p = data + ((y + yy) * rowpix) + x; + for (xx = 0; xx < w; xx++) + { + pix = PIX(xx, yy); + if (pix != transparent) *p = PIXLK(pix); + else *p = 0; + p++; + } } } - if (!_find_frame(animated, cur_frame - 1, &new_frame)) - { - *error = EVAS_LOAD_ERROR_CORRUPT_FILE; - goto error; - } + // paste on top with transparent pixels untouched else { - Gif_Frame *gif_frame2 = NULL; - - ptr_src = new_frame->data; - if (new_frame->info) + for (yy = 0; yy < h; yy++) { - gif_frame2 = (Gif_Frame *)(new_frame->info); - disposal = gif_frame2->frame_info.disposal; - gif_frame->bg_val = gif_frame2->bg_val; - switch (disposal) + p = data + ((y + yy) * rowpix) + x; + for (xx = 0; xx < w; xx++) { - case 0: // no nothing - case 2: // restore bg ... browsers don't respect bg, so neither shall we. - // fill in the area OUTSIDE the image with 0/black - _fill_pixels(pad, ptr, cache_w, 0, 0, cache_w, y); - _fill_pixels(pad, ptr, cache_w, 0, y + h, cache_w, cache_h - (y + h)); - _fill_pixels(pad, ptr, cache_w, 0, y, x, h); - _fill_pixels(pad, ptr, cache_w, x + w, y, cache_w - (x + w), h); - _expand_gif_rows(rows, cmap, ptr, cache_w, xin, yin, - w, h, x, y, alpha, EINA_TRUE); - break; - case 1: // leave as-is - case 3: // previous image - _copy_pixels(ptr_src, ptr, cache_w, 0, 0, - cache_w, cache_h); - _expand_gif_rows(rows, cmap, ptr, cache_w, xin, yin, - w, h, x, y, alpha, EINA_FALSE); - break; - default: - break; + pix = PIX(xx, yy); + if (pix != transparent) *p = PIXLK(pix); + p++; } } } } - else /* first frame decoding */ + else { - /* get the background value */ - gif_frame->bg_val = RGB_JOIN(cmap->Colors[bg].Red, - cmap->Colors[bg].Green, - cmap->Colors[bg].Blue); - // fill in the area OUTSIDE the image with 0/black - _fill_pixels(pad, ptr, cache_w, 0, 0, cache_w, y); - _fill_pixels(pad, ptr, cache_w, 0, y + h, cache_w, cache_h - (y + h)); - _fill_pixels(pad, ptr, cache_w, 0, y, x, h); - _fill_pixels(pad, ptr, cache_w, x + w, y, cache_w - (x + w), h); - _expand_gif_rows(rows, cmap, ptr, cache_w, xin, yin, - w, h, x, y, alpha, EINA_TRUE); - } - - if (rows) free(rows); - frame->loaded = EINA_TRUE; - return EINA_TRUE; -error: - if (rows) free(rows); - return EINA_FALSE; -} - -static Eina_Bool -_evas_image_load_frame(Eina_File *f, const Evas_Image_Load_Opts *opts, - Evas_Image_Property *prop, Evas_Image_Animated *animated, - GifFileType *gif, Image_Entry_Frame *frame, Frame_Load_Type type, int *error) -{ - GifRecordType rec; - int gra_res = 0, img_res = 0; - Eina_Bool res = EINA_FALSE; - - if ((!gif) || (!frame)) return EINA_FALSE; - if (type > LOAD_FRAME_DATA_INFO) return EINA_FALSE; - - do - { - if (DGifGetRecordType(gif, &rec) == GIF_ERROR) return EINA_FALSE; - if (rec == IMAGE_DESC_RECORD_TYPE) + // walk pixels without worring about transparency at all + for (yy = 0; yy < h; yy++) { - img_res++; - break; - } - else if (rec == EXTENSION_RECORD_TYPE) - { - int ext_code; - GifByteType *ext; - - ext = NULL; - DGifGetExtension(gif, &ext_code, &ext); - while (ext) + p = data + ((y + yy) * rowpix) + x; + for (xx = 0; xx < w; xx++) { - if (ext_code == 0xf9) /* Graphic Control Extension */ - { - gra_res++; - /* fill frame info */ - if ((type == LOAD_FRAME_INFO) || (type == LOAD_FRAME_DATA_INFO)) - _evas_image_load_frame_graphic_info(frame,ext); - } - ext = NULL; - DGifGetExtensionNext(gif, &ext); + pix = PIX(xx, yy); + *p = PIXLK(pix); + p++; } } - } while ((rec != TERMINATE_RECORD_TYPE) && (img_res == 0)); - if (img_res != 1) return EINA_FALSE; - if (DGifGetImageDesc(gif) == GIF_ERROR) return EINA_FALSE; - if ((type == LOAD_FRAME_INFO) || (type == LOAD_FRAME_DATA_INFO)) - _evas_image_load_frame_image_des_info(gif, frame); - - if ((type == LOAD_FRAME_DATA) || (type == LOAD_FRAME_DATA_INFO)) - { - res = _evas_image_load_frame_image_data(f, opts, prop, animated, - gif, frame, error); - if (!res) return EINA_FALSE; } - return EINA_TRUE; + ret = EINA_TRUE; + +on_error: + free(rows); + return ret; } - -/* set frame data to cache entry's data */ -static Eina_Bool -evas_image_load_file_data_gif_internal(Evas_Image_Property *prop, - Image_Entry_Frame *frame, - void *pixels, - int *error) +// flush out older rgba frame images to save memory but skip current frame +// and previous frame (prev needed for dispose mode 3) +static void +_flush_older_frames(Evas_Image_Animated *animated, + int w, int h, + Image_Entry_Frame *thisframe, + Image_Entry_Frame *prevframe) { - /* only copy real frame part */ - memcpy(pixels, frame->data, prop->w * prop->h * sizeof (DATA32)); - prop->premul = EINA_TRUE; + Eina_List *l; + Image_Entry_Frame *frame; + // target is the amount of memory we want to be under for stored frames + int total = 0, target = 512 * 1024; - *error = EVAS_LOAD_ERROR_NONE; - return EINA_TRUE; + // total up the amount of memory used by stored frames for this image + EINA_LIST_FOREACH(animated->frames, l, frame) + { + if (frame->data) total++; + } + total *= (w * h * sizeof(DATA32)); + // if we use less than target (512k) for frames - dont flush + if (total < target) return; + // clean oldest frames first and go until below target or until we loop + // around back to this frame (curent) + EINA_LIST_FOREACH(animated->frames, l, frame) + { + if (frame == thisframe) break; + } + if (!l) return; + // start on next frame after thisframe + l = l->next; + // handle wrap to start + if (!l) l = animated->frames; + // now walk until we hit thisframe again... then stop walk. + while (l) + { + frame = l->data; + if (frame == thisframe) break; + if (frame->data) + { + if ((frame != thisframe) && (frame != prevframe)) + { + free(frame->data); + frame->data = NULL; + // subtract memory used and if below target - stop flush + total -= (w * h * sizeof(DATA32)); + if (total < target) break; + } + } + // go to next - handle wrap to start + l = l->next; + if (!l) l = animated->frames; + } } + -typedef struct _Evas_GIF_Info Evas_GIF_Info; -struct _Evas_GIF_Info +// file access info/funcs +typedef struct _File_Info File_Info; +struct _File_Info { unsigned char *map; - int length; - int position; + int pos, len; // yes - gif uses ints for file sizes. }; static int -_evas_image_load_file_read(GifFileType* gft, GifByteType *buf,int length) +_file_read(GifFileType *gft, GifByteType *buf, int len) { - Evas_GIF_Info *egi = gft->UserData; + File_Info *fi = gft->UserData; - if (egi->position == egi->length) return 0; - if (egi->position + length == egi->length) length = egi->length - egi->position; - memcpy(buf, egi->map + egi->position, length); - egi->position += length; - - return length; -} -static void * -evas_image_load_file_open_gif(Eina_File *f, Eina_Stringshare *key EINA_UNUSED, - Evas_Image_Load_Opts *opts, - Evas_Image_Animated *animated, - int *error) -{ - Evas_Loader_Internal *loader; - - loader = calloc(1, sizeof (Evas_Loader_Internal)); - if (!loader) - { - *error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED; - return NULL; - } - - loader->f = f; - loader->opts = opts; - loader->animated = animated; - - return loader; -} - -static void -evas_image_load_file_close_gif(void *loader_data) -{ - free(loader_data); + if (fi->pos >= fi->len) return 0; // if at or past end - no + if ((fi->pos + len) >= fi->len) len = fi->len - fi->pos; + memcpy(buf, fi->map + fi->pos, len); + fi->pos += len; + return len; } static Eina_Bool -evas_image_load_file_head_gif(void *loader_data, - Evas_Image_Property *prop, - int *error) +evas_image_load_file_head_gif2(void *loader_data, + Evas_Image_Property *prop, + int *error) { - Evas_Loader_Internal *loader = loader_data; -// Evas_Image_Load_Opts *load_opts; - Evas_Image_Animated *animated; - Eina_File *f; - - Evas_GIF_Info egi; - GifRecordType rec; - GifFileType *gif = NULL; - int loop_count = -1; - int a; - Eina_Bool r = EINA_FALSE; - //it is possible which gif file have error midle of frames, - //in that case we should play gif file until meet error frame. - int image_count = 0; - - f = loader->f; -// load_opts = loader->opts; - animated = loader->animated; + Loader_Info *loader = loader_data; + Evas_Image_Animated *animated = loader->animated; + Eina_File *f = loader->f; + Eina_Bool ret = EINA_FALSE; + File_Info fi; + GifRecordType rec; + GifFileType *gif = NULL; + // it is possible which gif file have error midle of frames, + // in that case we should play gif file until meet error frame. + int imgnum = 0; + int loop_count = -1; + Frame_Info *finfo = NULL; + Eina_Bool full = EINA_TRUE; + // init prop struct with some default null values prop->w = 0; prop->h = 0; - a = 0; - egi.map = eina_file_map_all(f, EINA_FILE_RANDOM); - if (!egi.map) - { - *error = EVAS_LOAD_ERROR_CORRUPT_FILE; - goto on_error; - } - egi.length = eina_file_size_get(f); - egi.position = 0; + // map the file and store/track info + fi.map = eina_file_map_all(f, EINA_FILE_RANDOM); + if (!fi.map) LOADERR(EVAS_LOAD_ERROR_CORRUPT_FILE); + fi.len = eina_file_size_get(f); + fi.pos = 0; + // actually ask libgif to open the file #if GIFLIB_MAJOR >= 5 - gif = DGifOpen(&egi, _evas_image_load_file_read, NULL); + gif = DGifOpen(&fi, _file_read, NULL); #else - gif = DGifOpen(&egi, _evas_image_load_file_read); + gif = DGifOpen(&fi, _file_read); #endif - if (!gif) - { - *error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT; - goto on_error; - } - - /* check logical screen size */ + if (!gif) LOADERR(EVAS_LOAD_ERROR_UNKNOWN_FORMAT); + // get the gif "screen size" (the actual image size) prop->w = gif->SWidth; prop->h = gif->SHeight; - /* support scale down feture in gif*/ -// disable scale down for gif until gif is handled right -// if (load_opts->scale_down_by > 1) -// { -// prop->w /= load_opts->scale_down_by; -// prop->h /= load_opts->scale_down_by; -// } - + // if size is invalid - abort here if ((prop->w < 1) || (prop->h < 1) || (prop->w > IMG_MAX_SIZE) || (prop->h > IMG_MAX_SIZE) || IMG_TOO_BIG(prop->w, prop->h)) { if (IMG_TOO_BIG(prop->w, prop->h)) - *error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED; - else - *error = EVAS_LOAD_ERROR_GENERIC; + LOADERR(EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED); + LOADERR(EVAS_LOAD_ERROR_GENERIC); goto on_error; } - + // walk through gif records in file to figure out info do { if (DGifGetRecordType(gif, &rec) == GIF_ERROR) { - if (image_count > 1) break; //we should show normal frames. - /* PrintGifError(); */ - *error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT; - goto on_error; + // if we have a gif that ends part way through a sequence + // (or animation) consider it valid and just break - no error + if (imgnum > 1) break; + LOADERR(EVAS_LOAD_ERROR_UNKNOWN_FORMAT); } - - /* image descript info */ + // get image description section if (rec == IMAGE_DESC_RECORD_TYPE) { int img_code; GifByteType *img; + // get image desc if (DGifGetImageDesc(gif) == GIF_ERROR) - { - /* PrintGifError(); */ - *error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT; - goto on_error; - } - /* we have to count frame, so use DGifGetCode and skip decoding */ + LOADERR(EVAS_LOAD_ERROR_UNKNOWN_FORMAT); + // skip decoding and just walk image to next if (DGifGetCode(gif, &img_code, &img) == GIF_ERROR) - { - /* PrintGifError(); */ - *error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT; - goto on_error; - } + LOADERR(EVAS_LOAD_ERROR_UNKNOWN_FORMAT); + // skip till next... while (img) { img = NULL; DGifGetCodeNext(gif, &img); } - image_count++; + // store geometry in the last frame info data + if (finfo) + { + _store_frame_info(gif, finfo); + _check_transparency(&full, finfo, prop->w, prop->h); + } + // or if we dont have a finfo entry - create one even for stills + else + { + // allocate and save frame with field data + finfo = _new_frame(animated, -1, 0, 0, imgnum + 1); + if (!finfo) + LOADERR(EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED); + // store geometry info from gif image + _store_frame_info(gif, finfo); + // check for transparency/alpha + _check_transparency(&full, finfo, prop->w, prop->h); + } + imgnum++; } + // we have an extension code block - for animated gifs for sure else if (rec == EXTENSION_RECORD_TYPE) { - int ext_code; - GifByteType *ext; + int ext_code; + GifByteType *ext; ext = NULL; + // get the first extension entry DGifGetExtension(gif, &ext_code, &ext); while (ext) { - if (ext_code == 0xf9) /* Graphic Control Extension */ + // graphic control extension - for animated gif data + // and transparent index + flag + if (ext_code == 0xf9) { - if ((ext[1] & 1) && (prop->alpha == 0)) - prop->alpha = (int)ext[4]; + // create frame and store it in image + finfo = _new_frame + (animated, + (ext[1] & 1) ? ext[4] : -1, // transparency index + (ext[1] >> 2) & 0x7, // dispose mode + ((int)ext[3] << 8) | (int)ext[2], // delay + imgnum + 1); + if (!finfo) + LOADERR(EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED); } + // netscape extension indicating loop count... else if (ext_code == 0xff) /* application extension */ { - if (!strncmp ((char*)(&ext[1]), "NETSCAPE2.0", 11) || - !strncmp ((char*)(&ext[1]), "ANIMEXTS1.0", 11)) + if (!strncmp((char *)(&ext[1]), "NETSCAPE2.0", 11) || + !strncmp((char *)(&ext[1]), "ANIMEXTS1.0", 11)) { - ext=NULL; + ext = NULL; DGifGetExtensionNext(gif, &ext); - if (ext[1] == 0x01) { - loop_count = ext[2] + (ext[3] << 8); + loop_count = ((int)ext[3] << 8) | (int)ext[2]; if (loop_count > 0) loop_count++; } } - } - + } + // and continue onto the next extension entry ext = NULL; DGifGetExtensionNext(gif, &ext); } } - } while (rec != TERMINATE_RECORD_TYPE); + } + while (rec != TERMINATE_RECORD_TYPE); - if (a >= 0) prop->alpha = 1; - - if ((gif->ImageCount > 1) || (image_count > 1)) + // if the gif main says we have more than one image or our image counting + // says so, then this image is animated - indicate this + if ((gif->ImageCount > 1) || (imgnum > 1)) { animated->animated = 1; animated->loop_count = loop_count; animated->loop_hint = EVAS_IMAGE_ANIMATED_HINT_LOOP; - animated->frame_count = MIN(gif->ImageCount, image_count); - animated->frames = NULL; + animated->frame_count = MIN(gif->ImageCount, imgnum); } + if (!full) prop->alpha = 1; + animated->cur_frame = 1; + // no errors in header scan etc. so set err and return value *error = EVAS_LOAD_ERROR_NONE; - r = EINA_TRUE; + ret = EINA_TRUE; - on_error: +on_error: // jump here on any errors to clean up if (gif) DGifCloseFile(gif); - if (egi.map) eina_file_map_free(f, egi.map); - return r; + if (fi.map) eina_file_map_free(f, fi.map); + return ret; } static Eina_Bool -evas_image_load_specific_frame(Eina_File *f, - const Evas_Image_Load_Opts *opts, +evas_image_load_file_data_gif2(void *loader_data, Evas_Image_Property *prop, - Evas_Image_Animated *animated, int frame_index, + void *pixels, int *error) { - GifFileType *gif = NULL; - Image_Entry_Frame *frame = NULL; - Gif_Frame *gif_frame = NULL; - Evas_GIF_Info egi; - Eina_Bool r = EINA_FALSE; - - egi.map = eina_file_map_all(f, EINA_FILE_RANDOM); - if (!egi.map) - { - *error = EVAS_LOAD_ERROR_CORRUPT_FILE; - goto on_error; - } - egi.length = eina_file_size_get(f); - egi.position = 0; - -#if GIFLIB_MAJOR >= 5 - gif = DGifOpen(&egi, _evas_image_load_file_read, NULL); -#else - gif = DGifOpen(&egi, _evas_image_load_file_read); -#endif - if (!gif) - { - *error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT; - goto on_error; - } - if (!_evas_image_skip_frame(gif, frame_index-1)) - { - *error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT; - goto on_error; - } - - frame = malloc(sizeof (Image_Entry_Frame)); - if (!frame) - { - *error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED; - goto on_error; - } - - gif_frame = calloc(1, sizeof (Gif_Frame)); - if (!gif_frame) - { - *error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED; - free(frame); - goto on_error; - } - frame->info = gif_frame; - frame->index = frame_index; - if (!_evas_image_load_frame(f, opts, prop, animated, gif, frame, LOAD_FRAME_DATA_INFO, error)) - { - *error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT; - free(gif_frame); - free(frame); - goto on_error; - } - - animated->frames = eina_list_append(animated->frames, frame); - r = EINA_TRUE; - - on_error: - if (gif) DGifCloseFile(gif); - if (egi.map) eina_file_map_free(f, egi.map); - return r; -} - -static Eina_Bool -evas_image_load_file_data_gif(void *loader_data, - Evas_Image_Property *prop, - void *pixels, int *error) -{ - Evas_Loader_Internal *loader = loader_data; - Evas_Image_Load_Opts *opts; - Evas_Image_Animated *animated; - Eina_File *f; - - Image_Entry_Frame *frame = NULL; - int cur_frame_index; - Eina_Bool hit; - - opts = loader->opts; - animated = loader->animated; - f = loader->f; - - if(!animated->animated) - cur_frame_index = 1; - else - cur_frame_index = animated->cur_frame; + Loader_Info *loader = loader_data; + Evas_Image_Animated *animated = loader->animated; + Eina_File *f = loader->f; + Eina_Bool ret = EINA_FALSE; + File_Info fi; + GifRecordType rec; + GifFileType *gif = NULL; + Image_Entry_Frame *frame; + int index = 0, imgnum = 0; + Frame_Info *finfo; + // XXX: this is so wrong - storing current frame IN the image + // so we have to load multiple times to animate. what if the + // same image is shared/loaded in 2 ore more places AND animated + // there? + + // use index stored in image (XXX: yuk!) + index = animated->cur_frame; + // if index is invalid for animated image - error out if ((animated->animated) && - ((cur_frame_index < 0) || (cur_frame_index > FRAME_MAX) || (cur_frame_index > animated->frame_count))) + ((index <= 0) || (index > animated->frame_count))) + LOADERR(EVAS_LOAD_ERROR_GENERIC); + // find the given frame index + frame = _find_frame(animated, index); + if (frame) { - *error = EVAS_LOAD_ERROR_GENERIC; - return EINA_FALSE; - } - - /* first time frame is set to be 0. so default is 1 */ - if (cur_frame_index == 0) cur_frame_index++; - - /* Check current frame exists in hash table */ - hit = _find_frame(animated, cur_frame_index, &frame); - - /* if current frame exist in has table, check load flag */ - if (hit) - { - if (frame->loaded) - { - evas_image_load_file_data_gif_internal(prop, frame, pixels, error); - } - else + if ((frame->loaded) && (frame->data)) { - Evas_GIF_Info egi; - GifFileType *gif = NULL; - Eina_Bool r = EINA_FALSE; - - egi.map = eina_file_map_all(f, EINA_FILE_RANDOM); - if (!egi.map) - { - *error = EVAS_LOAD_ERROR_CORRUPT_FILE; - goto on_error; - } - egi.length = eina_file_size_get(f); - egi.position = 0; - -#if GIFLIB_MAJOR >= 5 - gif = DGifOpen(&egi, _evas_image_load_file_read, NULL); -#else - gif = DGifOpen(&egi, _evas_image_load_file_read); -#endif - if (!gif) - { - *error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT; - goto on_error; - } - _evas_image_skip_frame(gif, cur_frame_index - 1); - if (!_evas_image_load_frame(f, opts, prop, animated, gif, frame, LOAD_FRAME_DATA, error)) - { - *error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT; - goto on_error; - } - if (!evas_image_load_file_data_gif_internal(prop, frame, pixels, error)) - { - *error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT; - goto on_error; - } - *error = EVAS_LOAD_ERROR_NONE; - r = EINA_TRUE; - - on_error: - if (gif) DGifCloseFile(gif); - if (egi.map) eina_file_map_free(f, egi.map); - return r; + // frame is already there and decoded - jump to end + goto on_ok; } } - /* current frame does is not exist */ else - { - if (!evas_image_load_specific_frame(f, opts, prop, animated, cur_frame_index, error)) - return EINA_FALSE; - hit = EINA_FALSE; - frame = NULL; - hit = _find_frame(animated, cur_frame_index, &frame); - if (!hit) return EINA_FALSE; - if (!evas_image_load_file_data_gif_internal(prop, frame, pixels, error)) - { - *error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED; - return EINA_FALSE; - } - return EINA_TRUE; - } - return EINA_FALSE; -} + LOADERR(EVAS_LOAD_ERROR_CORRUPT_FILE); -static double -evas_image_load_frame_duration_gif(void *loader_data, - int start_frame, int frame_num) -{ - Evas_Loader_Internal *loader = loader_data; - Evas_Image_Animated *animated; - Eina_File *f; + // map the file and store/track info + fi.map = eina_file_map_all(f, EINA_FILE_RANDOM); + if (!fi.map) LOADERR(EVAS_LOAD_ERROR_CORRUPT_FILE); + fi.len = eina_file_size_get(f); + fi.pos = 0; - Evas_GIF_Info egi; - GifFileType *gif = NULL; - GifRecordType rec; - int current_frame = 1; - int remain_frames = frame_num; - double duration = -1; - int frame_count = 0; - - animated = loader->animated; - f = loader->f; - frame_count = animated->frame_count; - - if (!animated->animated) return -1; - if ((start_frame + frame_num) > frame_count) return -1; - if (frame_num < 0) return -1; - - egi.map = eina_file_map_all(f, EINA_FILE_RANDOM); - if (!egi.map) goto on_error; - egi.length = eina_file_size_get(f); - egi.position = 0; + // actually ask libgif to open the file #if GIFLIB_MAJOR >= 5 - gif = DGifOpen(&egi, _evas_image_load_file_read, NULL); + gif = DGifOpen(&fi, _file_read, NULL); #else - gif = DGifOpen(&egi, _evas_image_load_file_read); + gif = DGifOpen(&fi, _file_read); #endif - if (!gif) goto on_error; + if (!gif) LOADERR(EVAS_LOAD_ERROR_UNKNOWN_FORMAT); - duration = 0; + imgnum = 1; + // walk through gif records in file to figure out info do { if (DGifGetRecordType(gif, &rec) == GIF_ERROR) - { - rec = TERMINATE_RECORD_TYPE; - } - if (rec == IMAGE_DESC_RECORD_TYPE) - { - int img_code; - GifByteType *img; - - if (DGifGetImageDesc(gif) == GIF_ERROR) - { - /* PrintGifError(); */ - rec = TERMINATE_RECORD_TYPE; - } - current_frame++; - /* we have to count frame, so use DGifGetCode and skip decoding */ - if (DGifGetCode(gif, &img_code, &img) == GIF_ERROR) - { - rec = TERMINATE_RECORD_TYPE; - } - while (img) - { - img = NULL; - DGifGetExtensionNext(gif, &img); - } - } - else if (rec == EXTENSION_RECORD_TYPE) + LOADERR(EVAS_LOAD_ERROR_UNKNOWN_FORMAT); + if (rec == EXTENSION_RECORD_TYPE) { int ext_code; GifByteType *ext; - + ext = NULL; DGifGetExtension(gif, &ext_code, &ext); while (ext) { - if (ext_code == 0xf9) /* Graphic Control Extension */ - { - if ((current_frame >= start_frame) && (current_frame <= frame_count)) - { - int frame_duration = 0; - if (remain_frames < 0) break; - frame_duration = byte2_to_int (ext[2], ext[3]); - if (frame_duration == 0) - duration += 0.1; - else - duration += (double)frame_duration/100; - remain_frames --; - } - } ext = NULL; DGifGetExtensionNext(gif, &ext); } - } - } while (rec != TERMINATE_RECORD_TYPE); + } + // get image description section + else if (rec == IMAGE_DESC_RECORD_TYPE) + { + int xin = 0, yin = 0, x = 0, y = 0, w = 0, h = 0; + int img_code; + GifByteType *img; + Image_Entry_Frame *prevframe = NULL; + Image_Entry_Frame *thisframe = NULL; - on_error: + // get image desc + if (DGifGetImageDesc(gif) == GIF_ERROR) + LOADERR(EVAS_LOAD_ERROR_UNKNOWN_FORMAT); + // get the previous frame entry AND the current one to fill in + prevframe = _find_frame(animated, imgnum - 1); + thisframe = _find_frame(animated, imgnum); + // if we have a frame AND we're animated AND we have no data... + if ((thisframe) && (!thisframe->data) && (animated->animated)) + { + Eina_Bool first = EINA_FALSE; + + // allocate it + thisframe->data = + malloc(prop->w * prop->h * sizeof(DATA32)); + if (!thisframe->data) + LOADERR(EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED); + // if we have no prior frame OR prior frame data... empty + if ((!prevframe) || (!prevframe->data)) + { + first = EINA_TRUE; + finfo = thisframe->info; + memset(thisframe->data, 0, + prop->w * prop->h * sizeof(DATA32)); + } + // we have a prior frame to copy data from... + else + { + finfo = prevframe->info; + + // fix coords of sub image in case it goes out... + _clip_coords(prop->w, prop->h, &xin, &yin, + finfo->x, finfo->y, finfo->w, finfo->h, + &x, &y, &w, &h); + // if dispose mode is not restore - then copy pre frame + if (finfo->dispose != 3) // GIF_DISPOSE_RESTORE + memcpy(thisframe->data, prevframe->data, + prop->w * prop->h * sizeof(DATA32)); + // if dispose mode is "background" then fill with bg + if (finfo->dispose == 2) // GIF_DISPOSE_BACKGND + _fill_frame(thisframe->data, prop->w, gif, + finfo, x, y, w, h); + else if (finfo->dispose == 3) // GIF_DISPOSE_RESTORE + { + Image_Entry_Frame *prevframe2; + + // we need to copy data from one frame back + // from the prev frame into the current frame + // (copy the whole image - at least the sample + // GifWin.cpp from libgif indicates this is what + // needs doing + prevframe2 = _find_frame(animated, imgnum - 2); + if (prevframe2) + memcpy(thisframe->data, prevframe2->data, + prop->w * prop->h * sizeof(DATA32)); + } + } + // now draw this frame on top + finfo = thisframe->info; + _clip_coords(prop->w, prop->h, &xin, &yin, + finfo->x, finfo->y, finfo->w, finfo->h, + &x, &y, &w, &h); + if (!_decode_image(gif, thisframe->data, prop->w, + xin, yin, finfo->transparent, + x, y, w, h, first)) + LOADERR(EVAS_LOAD_ERROR_CORRUPT_FILE); + // mark as loaded and done + thisframe->loaded = EINA_TRUE; + // and flush old memory if needed (too much) + _flush_older_frames(animated, prop->w, prop->h, + thisframe, prevframe); + } + // if we hve a frame BUT the image is not animated... different + // path + else if ((thisframe) && (!thisframe->data) && + (!animated->animated)) + { + // if we don't have the data decoded yet - decode it + if (!thisframe->loaded) + { + // use frame info but we WONT allocate frame pixels + finfo = thisframe->info; + _clip_coords(prop->w, prop->h, &xin, &yin, + finfo->x, finfo->y, finfo->w, finfo->h, + &x, &y, &w, &h); + // clear out all pixels + _fill_frame(pixels, prop->w, gif, + finfo, 0, 0, prop->w, prop->h); + // and decode the gif with overwriting + if (!_decode_image(gif, pixels, prop->w, + xin, yin, finfo->transparent, + x, y, w, h, EINA_TRUE)) + LOADERR(EVAS_LOAD_ERROR_CORRUPT_FILE); + // mark as loaded and done + thisframe->loaded = EINA_TRUE; + } + // flush mem we don't need (at expense of decode cpu) + } + else + { + // skip decoding and just walk image to next + if (DGifGetCode(gif, &img_code, &img) == GIF_ERROR) + LOADERR(EVAS_LOAD_ERROR_UNKNOWN_FORMAT); + while (img) + { + img = NULL; + DGifGetCodeNext(gif, &img); + } + } + // if we found the image we wanted - get out of here + if (imgnum == index) break; + imgnum++; + } + } + while (rec != TERMINATE_RECORD_TYPE); + +on_ok: + // no errors in header scan etc. so set err and return value + *error = EVAS_LOAD_ERROR_NONE; + ret = EINA_TRUE; + + // if it was an animated image we need to copy the data to the + // pixels for the image from the frame holding the data + if (animated->animated) + memcpy(pixels, frame->data, prop->w * prop->h * sizeof (DATA32)); + prop->premul = EINA_TRUE; + +on_error: // jump here on any errors to clean up if (gif) DGifCloseFile(gif); - if (egi.map) eina_file_map_free(f, egi.map); - return duration; + if (fi.map) eina_file_map_free(f, fi.map); + return ret; } +// get the time between 2 frames in the timeline +static double +evas_image_load_frame_duration_gif2(void *loader_data, + int start_frame, + int frame_num) +{ + Loader_Info *loader = loader_data; + Evas_Image_Animated *animated = loader->animated; + Image_Entry_Frame *frame; + int i, total = 0; + + // if its not animated or requested frame data is invalid + if (!animated->animated) return -1.0; + if ((start_frame + frame_num) > animated->frame_count) return -1.0; + if (frame_num < 0) return -1.0; + + if (frame_num < 1) frame_num = 1; + // walk frames from start frame though and total up delays + for (i = start_frame; i < (start_frame + frame_num); i++) + { + Frame_Info *finfo; + + // find the frame + frame = _find_frame(animated, i); + // no frame? barf - bad file or i/o? + if (!frame) return -1.0; + // get delay and total it up + finfo = frame->info; + // if delay is sensible - use it else assume 10/100ths of a sec + if (finfo->delay > 0) total += finfo->delay; + else total += 10; + } + // return delay in seconds (since timing in gifs is in 1/100ths of a sec) + return (double)total / 100.0; +} + +// called on opening of a file load +static void * +evas_image_load_file_open_gif2(Eina_File *f, + Eina_Stringshare *key EINA_UNUSED, // XXX: we need to use key for frame # + Evas_Image_Load_Opts *opts, + Evas_Image_Animated *animated, + int *error) +{ + Loader_Info *loader = calloc(1, sizeof (Loader_Info)); + if (!loader) + { + *error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED; + return NULL; + } + loader->f = f; + loader->opts = opts; + loader->animated = animated; + return loader; +} + +// called on closing of an image file load (end of load) +static void +evas_image_load_file_close_gif2(void *loader_data) +{ + Loader_Info *loader = loader_data; + free(loader); +} + +// general module delcaration stuff static Evas_Image_Load_Func evas_image_load_gif_func = { - evas_image_load_file_open_gif, - evas_image_load_file_close_gif, - evas_image_load_file_head_gif, - evas_image_load_file_data_gif, - evas_image_load_frame_duration_gif, + evas_image_load_file_open_gif2, + evas_image_load_file_close_gif2, + evas_image_load_file_head_gif2, + evas_image_load_file_data_gif2, + evas_image_load_frame_duration_gif2, EINA_TRUE, EINA_FALSE }; +// raw module api that the rest of the world sees static int module_open(Evas_Module *em) { @@ -1052,13 +840,13 @@ module_close(Evas_Module *em EINA_UNUSED) static Evas_Module_Api evas_modapi = { - EVAS_MODULE_API_VERSION, - "gif", - "none", - { - module_open, - module_close - } + EVAS_MODULE_API_VERSION, + "gif", + "none", + { + module_open, + module_close + } }; EVAS_MODULE_DEFINE(EVAS_MODULE_TYPE_IMAGE_LOADER, image_loader, gif);