#include "backend.h" #include "bitmap.h" #include "image.h" #include "log.h" #include "source.h" #include "source_private.h" #include #include #include struct private { char *path; FIMEMORY *memory; FREE_IMAGE_FORMAT format; FIMULTIBITMAP *multibitmap; FIBITMAP *last_frame; int num_frames; int next_frame; int width; int height; }; static void free_private(void *raw_private) { if (!raw_private) { return; } struct private *private = raw_private; free(private->path); private->path = NULL; if (private->memory) { FreeImage_CloseMemory(private->memory); private->memory = NULL; } if (private->multibitmap) { FreeImage_CloseMultiBitmap(private->multibitmap, 0); private->multibitmap = NULL; } if (private->last_frame) { FreeImage_Unload(private->last_frame); private->last_frame = NULL; } free(private); } static struct imv_image *to_image(FIBITMAP *in_bmp) { struct imv_bitmap *bmp = malloc(sizeof *bmp); bmp->width = FreeImage_GetWidth(in_bmp); bmp->height = FreeImage_GetHeight(in_bmp); bmp->format = IMV_ARGB; bmp->data = malloc(4 * bmp->width * bmp->height); FreeImage_ConvertToRawBits(bmp->data, in_bmp, 4 * bmp->width, 32, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK, TRUE); struct imv_image *image = imv_image_create_from_bitmap(bmp); return image; } static FIBITMAP *normalise_bitmap(FIBITMAP *input) { FIBITMAP *output = NULL; switch (FreeImage_GetImageType(input)) { case FIT_RGB16: case FIT_RGBA16: case FIT_RGBF: case FIT_RGBAF: output = FreeImage_ConvertTo32Bits(input); FreeImage_Unload(input); break; case FIT_UINT16: case FIT_INT16: case FIT_UINT32: case FIT_INT32: case FIT_FLOAT: case FIT_DOUBLE: case FIT_COMPLEX: output = FreeImage_ConvertTo8Bits(input); FreeImage_Unload(input); break; case FIT_BITMAP: default: output = input; } imv_log(IMV_DEBUG, "freeimage: bitmap normalised to 32 bits: before=%p after=%p\n", input, output); return output; } static void first_frame(void *raw_private, struct imv_image **image, int *frametime) { *image = NULL; *frametime = 0; imv_log(IMV_DEBUG, "freeimage: first_frame called\n"); FIBITMAP *bmp = NULL; struct private *private = raw_private; if (private->format == FIF_GIF) { if (private->path) { private->multibitmap = FreeImage_OpenMultiBitmap(FIF_GIF, private->path, /* don't create file */ 0, /* read only */ 1, /* keep in memory */ 1, /* flags */ GIF_LOAD256); } else if (private->memory) { private->multibitmap = FreeImage_LoadMultiBitmapFromMemory(FIF_GIF, private->memory, /* flags */ GIF_LOAD256); } else { imv_log(IMV_ERROR, "private->path and private->memory both NULL"); return; } if (!private->multibitmap) { imv_log(IMV_ERROR, "first frame already loaded"); return; } FIBITMAP *frame = FreeImage_LockPage(private->multibitmap, 0); private->num_frames = FreeImage_GetPageCount(private->multibitmap); /* Get duration of first frame */ FITAG *tag = NULL; FreeImage_GetMetadata(FIMD_ANIMATION, frame, "FrameTime", &tag); if (FreeImage_GetTagValue(tag)) { *frametime = *(int*)FreeImage_GetTagValue(tag); } else { *frametime = 100; /* default value for gifs */ } bmp = FreeImage_ConvertTo24Bits(frame); FreeImage_UnlockPage(private->multibitmap, frame, 0); } else { /* not a gif */ private->num_frames = 1; int flags = (private->format == FIF_JPEG) ? JPEG_EXIFROTATE : 0; FIBITMAP *fibitmap = NULL; if (private->path) { fibitmap = FreeImage_Load(private->format, private->path, flags); } else if (private->memory) { fibitmap = FreeImage_LoadFromMemory(private->format, private->memory, flags); } if (!fibitmap) { imv_log(IMV_ERROR, "FreeImage_Load returned NULL"); return; } bmp = normalise_bitmap(fibitmap); } private->width = FreeImage_GetWidth(bmp); private->height = FreeImage_GetHeight(bmp); private->last_frame = bmp; private->next_frame = 1 % private->num_frames; *image = to_image(bmp); } static void next_frame(void *raw_private, struct imv_image **image, int *frametime) { *image = NULL; *frametime = 0; struct private *private = raw_private; if (private->num_frames == 1) { *image = to_image(private->last_frame); return; } FITAG *tag = NULL; char disposal_method = 0; short top = 0; short left = 0; FIBITMAP *frame = FreeImage_LockPage(private->multibitmap, private->next_frame); FIBITMAP *frame32 = FreeImage_ConvertTo32Bits(frame); /* First frame is always going to use the raw frame */ if (private->next_frame > 0) { FreeImage_GetMetadata(FIMD_ANIMATION, frame, "DisposalMethod", &tag); if (FreeImage_GetTagValue(tag)) { disposal_method = *(char*)FreeImage_GetTagValue(tag); } } FreeImage_GetMetadata(FIMD_ANIMATION, frame, "FrameLeft", &tag); if (FreeImage_GetTagValue(tag)) { left = *(short*)FreeImage_GetTagValue(tag); } FreeImage_GetMetadata(FIMD_ANIMATION, frame, "FrameTop", &tag); if (FreeImage_GetTagValue(tag)) { top = *(short*)FreeImage_GetTagValue(tag); } FreeImage_GetMetadata(FIMD_ANIMATION, frame, "FrameTime", &tag); if (FreeImage_GetTagValue(tag)) { *frametime = *(int*)FreeImage_GetTagValue(tag); } /* some gifs don't provide a frame time at all */ if (*frametime == 0) { *frametime = 100; } FreeImage_UnlockPage(private->multibitmap, frame, 0); /* If this frame is inset, we need to expand it for compositing */ if (private->width != (int)FreeImage_GetWidth(frame32) || private->height != (int)FreeImage_GetHeight(frame32)) { FIBITMAP *expanded = FreeImage_Allocate(private->width, private->height, 32, 0,0,0); FreeImage_Paste(expanded, frame32, left, top, 255); FreeImage_Unload(frame32); frame32 = expanded; } switch(disposal_method) { case 0: /* nothing specified, fall through to compositing */ case 1: /* composite over previous frame */ if (private->last_frame && private->next_frame > 0) { FIBITMAP *bg_frame = FreeImage_ConvertTo24Bits(private->last_frame); FreeImage_Unload(private->last_frame); FIBITMAP *comp = FreeImage_Composite(frame32, 1, NULL, bg_frame); FreeImage_Unload(bg_frame); FreeImage_Unload(frame32); private->last_frame = comp; } else { /* No previous frame, just render directly */ if(private->last_frame) { FreeImage_Unload(private->last_frame); } private->last_frame = frame32; } break; case 2: /* TODO - set to background, composite over that */ if (private->last_frame) { FreeImage_Unload(private->last_frame); } private->last_frame = frame32; break; case 3: /* TODO - restore to previous content */ if (private->last_frame) { FreeImage_Unload(private->last_frame); } private->last_frame = frame32; break; } private->next_frame = (private->next_frame + 1) % private->num_frames; *image = to_image(private->last_frame); } static const struct imv_source_vtable vtable = { .load_first_frame = first_frame, .load_next_frame = next_frame, .free = free_private }; static enum backend_result open_path(const char *path, struct imv_source **src) { imv_log(IMV_DEBUG, "freeimage: open_path(%s)\n", path); FREE_IMAGE_FORMAT fmt = FreeImage_GetFileType(path, 0); if (fmt == FIF_UNKNOWN) { imv_log(IMV_DEBUG, "freeimage: unknown file format\n"); return BACKEND_UNSUPPORTED; } struct private *private = calloc(1, sizeof(struct private)); private->format = fmt; private->path = strdup(path); *src = imv_source_create(&vtable, private); return BACKEND_SUCCESS; } static enum backend_result open_memory(void *data, size_t len, struct imv_source **src) { FIMEMORY *fmem = FreeImage_OpenMemory(data, len); FREE_IMAGE_FORMAT fmt = FreeImage_GetFileTypeFromMemory(fmem, 0); if (fmt == FIF_UNKNOWN) { FreeImage_CloseMemory(fmem); return BACKEND_UNSUPPORTED; } struct private *private = calloc(1, sizeof(struct private)); private->format = fmt; private->memory = fmem; private->path = NULL; *src = imv_source_create(&vtable, private); return BACKEND_SUCCESS; } const struct imv_backend imv_backend_freeimage = { .name = "FreeImage", .description = "Open source image library supporting a large number of formats", .website = "http://freeimage.sourceforge.net/", .license = "FreeImage Public License v1.0", .open_path = &open_path, .open_memory = &open_memory, };