/* ---------------------------------------------------- * p_drop_image_cache * ---------------------------------------------------- * drop the image cache. */ static void p_drop_image_cache(void) { gint32 *images; gint nimages; gint l_idi; if(gap_debug) { printf("p_drop_image_cache START pid:%d\n", (int) gap_base_getpid()); } images = gimp_image_list(&nimages); for(l_idi=0; l_idi < nimages; l_idi++) { GimpParasite *l_parasite; l_parasite = gimp_image_parasite_find(images[l_idi], GAP_IMAGE_CACHE_PARASITE); if(gap_debug) { printf("FrameFetcher: CHECK (image_id:%d) name:%s pid:%d\n" , (int)images[l_idi] , gimp_image_get_filename(images[l_idi]) , (int)gap_base_getpid() ); } if(l_parasite) { if(gap_debug) { printf("FrameFetcher: DELETE (image_id:%d) name:%s pid:%d\n" , (int)images[l_idi] , gimp_image_get_filename(images[l_idi]) , (int)gap_base_getpid() ); } /* delete image from the duplicates cache */ gap_image_delete_immediate(images[l_idi]); gimp_parasite_free(l_parasite); } } if(images) { g_free(images); } if(gap_debug) { printf("p_drop_image_cache END pid:%d\n", (int)gap_base_getpid()); } } /* end p_drop_image_cache */
/* ------------------------------------------------- * gap_frame_fetch_delete_list_of_duplicated_images * ------------------------------------------------- * deletes all duplicate imageas that wre created by the specified ffetch_user_id * (if ffetch_user_id -1 is specified delte all duplicated images) */ void gap_frame_fetch_delete_list_of_duplicated_images(gint32 ffetch_user_id) { gint32 *images; gint nimages; gint l_idi; images = gimp_image_list(&nimages); for(l_idi=0; l_idi < nimages; l_idi++) { GimpParasite *l_parasite; l_parasite = gimp_image_parasite_find(images[l_idi], GAP_IMAGE_DUP_CACHE_PARASITE); if(gap_debug) { printf("FrameFetcher: check (image_id:%d) name:%s pid:%d\n" , (int)images[l_idi] , gimp_image_get_filename(images[l_idi]) , (int)gap_base_getpid() ); } if(l_parasite) { gint32 *ffetch_user_id_ptr; ffetch_user_id_ptr = (gint32 *) l_parasite->data; if((*ffetch_user_id_ptr == ffetch_user_id) || (ffetch_user_id < 0)) { if(gap_debug) { printf("FrameFetcher: DELETE duplicate %s (image_id:%d) user_id:%d (%d) name:%s pid:%d\n" , gimp_image_get_filename(images[l_idi]) , (int)images[l_idi] , (int)ffetch_user_id , (int)*ffetch_user_id_ptr , gimp_image_get_filename(images[l_idi]) , (int)gap_base_getpid() ); } /* delete image from the duplicates cache */ gap_image_delete_immediate(images[l_idi]); } gimp_parasite_free(l_parasite); } } if(images) { g_free(images); } } /* end gap_frame_fetch_delete_list_of_duplicated_images */
/* ------------------------------------------------------- * gap_image_set_selection_from_selection_or_drawable * ------------------------------------------------------- * create a selection in the specified image_id. * The selection is a scaled copy of the selection in another image, * referred by ref_drawable_id, or a Grayscale copy of the specified ref_drawable_id * (in case the referred image has no selection or the flag force_from_drawable is TRUE) * * - operates on a duplicate of the image referred by ref_drawable_id. * - this duplicate is scaled to same size as specified image_id * * return TRUE in case the selection was successfully created . */ gboolean gap_image_set_selection_from_selection_or_drawable(gint32 image_id, gint32 ref_drawable_id , gboolean force_from_drawable) { gint32 l_aux_channel_id; gint32 ref_image_id; gint32 work_drawable_id; /* the duplicate of the layer that is used as selction mask */ gint32 dup_image_id; gboolean has_selection; gboolean non_empty; gint x1, y1, x2, y2; if ((image_id < 0) || (ref_drawable_id < 0)) { return (FALSE); } ref_image_id = gimp_item_get_image(ref_drawable_id); if (ref_image_id < 0) { printf("ref_drawable_id does not refer to a valid image layer_id:%d\n", (int)ref_drawable_id); return (FALSE); } dup_image_id = gimp_image_duplicate(ref_image_id); if (dup_image_id < 0) { printf("duplicating of image failed, referred souce image_id:%d\n", (int)ref_image_id); return (FALSE); } /* clear undo stack */ if (gimp_image_undo_is_enabled(dup_image_id)) { gimp_image_undo_disable(dup_image_id); } if ((gimp_image_width(image_id) != gimp_image_width(dup_image_id)) || (gimp_image_height(image_id) != gimp_image_height(dup_image_id))) { if(gap_debug) { printf("scaling tmp image_id: %d\n", (int)dup_image_id); } gimp_image_scale(dup_image_id, gimp_image_width(image_id), gimp_image_height(image_id)); } has_selection = gimp_selection_bounds(ref_image_id, &non_empty, &x1, &y1, &x2, &y2); if ((has_selection) && (non_empty) && (force_from_drawable != TRUE)) { /* use scaled copy of the already exisating selection in the referred image */ work_drawable_id = gimp_image_get_selection(dup_image_id); } else { gint32 active_layer_stackposition; /* create selection as gray copy of the alt_selection layer */ active_layer_stackposition = gap_layer_get_stackposition(ref_image_id, ref_drawable_id); if(gimp_image_base_type(dup_image_id) != GIMP_GRAY) { if(gap_debug) { printf("convert to GRAYSCALE tmp image_id: %d\n", (int)dup_image_id); } gimp_image_convert_grayscale(dup_image_id); } work_drawable_id = gap_layer_get_id_by_stackposition(dup_image_id, active_layer_stackposition); gimp_layer_resize_to_image_size (work_drawable_id); } gimp_selection_all(image_id); //l_sel_channel_id = gimp_image_get_selection(image_id); l_aux_channel_id = gimp_selection_save(image_id); /* copy the work drawable (layer or channel) into the selection channel * the work layer is a grayscale copy GRAY or GRAYA of the alt_selection layer * that is already scaled and resized to fit the size of the target image * the work channel is the scaled selection of the image refred by ref_drawable_id * * copying is done into an auxiliary channel from where we regulary load the selection. * this is done because subseqent queries of the selection boudaries will deliver * full channel size rectangle after a direct copy into the selection. */ gap_layer_copy_picked_channel (l_aux_channel_id /* dst_drawable_id*/ , 0 /* dst_channel_pick */ , work_drawable_id /* src_drawable_id */ , 0 /* src_channel_pick */ , FALSE /* gboolean shadow */ ); gimp_image_select_item(image_id, GIMP_CHANNEL_OP_REPLACE, l_aux_channel_id); gimp_image_remove_channel(image_id, l_aux_channel_id); gap_image_delete_immediate(dup_image_id); return (TRUE); } /* end gap_image_set_selection_from_selection_or_drawable */
/* ------------------------------------ * gap_image_merge_group_layer * ------------------------------------ * merge the specified group layer and return the id of the resulting layer. * * The merge strategy * o) create a temporary image of same size/type (l_tmp_img_id) * o) copy the specified grouplayer to the temporary image (l_tmp_img_id) * o) call gimp_image_merge_visible_layers on the temporary image (l_tmp_img_id, mode) * o) copy the merged layer back to the original image * to the same group at the position of the original layergroup * o) remove the temporary image * o) remove original layergroup * o) rename the resuling merged layer. * * returns 0 if all done OK * (or -1 on error) */ gint32 gap_image_merge_group_layer(gint32 image_id, gint32 group_layer_id, gint merge_mode) { gint32 l_tmp_img_id; gint32 l_new_layer_id; gint32 l_merged_layer_id; gint32 l_parent_id; gint32 l_position; gint l_src_offset_x; gint l_src_offset_y; gboolean l_visible; char *l_name; if (!gimp_item_is_group(group_layer_id)) { /* the specified group_layer_id is not a group * -- no merge is done, return its id as result -- */ return(group_layer_id); } l_visible = gimp_item_get_visible(group_layer_id); l_name = gimp_item_get_name(group_layer_id); /* create a temporary image */ l_tmp_img_id = gap_image_new_of_samesize(image_id); /* copy the grouplayer to the temporary image */ l_new_layer_id = gap_layer_copy_to_dest_image(l_tmp_img_id, group_layer_id, 100.0, /* full opacity */ 0, /* NORMAL paintmode */ &l_src_offset_x, &l_src_offset_y ); gimp_image_insert_layer (l_tmp_img_id, l_new_layer_id, 0, 0); gimp_layer_set_offsets(l_new_layer_id, l_src_offset_x, l_src_offset_y); gimp_item_set_visible(l_new_layer_id, TRUE); /* merge visible layers in the temporary image */ l_merged_layer_id = gimp_image_merge_visible_layers (l_tmp_img_id, merge_mode); l_new_layer_id = gap_layer_copy_to_dest_image(image_id, l_merged_layer_id, gimp_layer_get_opacity(group_layer_id), gimp_layer_get_mode(group_layer_id), &l_src_offset_x, &l_src_offset_y ); l_position = gimp_image_get_item_position (image_id, group_layer_id); l_parent_id = gimp_item_get_parent(group_layer_id); if (l_parent_id < 0) { l_parent_id = 0; } gimp_image_insert_layer (image_id, l_new_layer_id, l_parent_id, l_position); gimp_layer_set_offsets(l_new_layer_id, l_src_offset_x, l_src_offset_y); /* remove the original group layer from the original image */ gimp_image_remove_layer(image_id, group_layer_id); /* restore the original layer name */ if (l_name != NULL) { gimp_item_set_name(l_new_layer_id, l_name); g_free(l_name); } gimp_item_set_visible(l_new_layer_id, l_visible); /* remove the temporary image */ gap_image_delete_immediate(l_tmp_img_id); return(l_new_layer_id); } /* end gap_image_merge_group_layer */
/* -------------------------------- * gap_moprhShapeDetectionEdgeBased * -------------------------------- * * generates morph workpoints via edge based shape detection. * This is done by edge detection in the source image * (specified via mgup->mgpp->osrc_layer_id) * * and picking workpoints on the detected edges * and locating the corresponding point in the destination image. * * IN: mgup->num_shapepoints specifies the number of workpoints to be generated. */ void gap_moprhShapeDetectionEdgeBased(GapMorphGUIParams *mgup, gboolean *cancelFlagPtr) { GapMorphShapeContext morphShapeContext; GapMorphShapeContext *msctx; gboolean deleteEdgeImage; GapMorphGlobalParams *mgpp; mgpp = mgup->mgpp; if(mgup->workpointGenerationBusy == TRUE) { return; } mgup->workpointGenerationBusy = TRUE; deleteEdgeImage = TRUE; /* init context */ msctx = &morphShapeContext; msctx->edgeColordiffThreshold = CLAMP(mgpp->edgeColordiffThreshold, 0.0, 1.0); msctx->locateColordiffThreshold = CLAMP(mgpp->locateColordiffThreshold, 0.0, 1.0); msctx->locateDetailShapeRadius = mgpp->locateDetailShapeRadius; msctx->locateDetailMoveRadius = mgpp->locateDetailMoveRadius; msctx->colorSensitivity = GAP_COLORDIFF_DEFAULT_SENSITIVITY; msctx->refLayerId = mgpp->osrc_layer_id; msctx->targetLayerId = mgpp->fdst_layer_id; msctx->numShapePoints = mgpp->numWorkpoints; msctx->countGeneratedPoints = 0; msctx->doProgress = TRUE; msctx->progressBar = mgup->progressBar; if(msctx->progressBar == NULL) { msctx->doProgress = FALSE; } msctx->cancelWorkpointGenerationPtr = cancelFlagPtr; if(gap_debug) { printf("gap_moprhShapeDetectionEdgeBased START edgeThres:%.5f locateThres:%.5f locateRadius:%d\n" , (float)msctx->edgeColordiffThreshold , (float)msctx->locateDetailMoveRadius , (int)msctx->locateDetailMoveRadius ); } msctx->edgeLayerId = gap_edgeDetection(mgup->mgpp->osrc_layer_id /* refDrawableId */ ,mgup->mgpp->edgeColordiffThreshold ,&msctx->countEdgePixels ); msctx->edgeImageId = gimp_drawable_get_image(msctx->edgeLayerId); if(gap_debug) { /* show the edge image for debug purpose * (this image is intended for internal processing usage) */ gimp_display_new(msctx->edgeImageId); deleteEdgeImage = FALSE; } msctx->edgeDrawable = gimp_drawable_get(msctx->edgeLayerId); if(msctx->edgeDrawable != NULL) { gint maxWidth; gint maxHeight; maxWidth = msctx->edgeDrawable->width-1; maxHeight = msctx->edgeDrawable->height-1; if((mgup->src_win.zoom < 1.0) && (mgup->src_win.zoom > 0.0)) { gdouble fwidth; gdouble fheight; gint width; gint height; fwidth = mgup->src_win.zoom * (gdouble)mgup->src_win.pv_ptr->pv_width; fheight = mgup->src_win.zoom * (gdouble)mgup->src_win.pv_ptr->pv_height; width = CLAMP((gint)fwidth, 1, maxWidth); height = CLAMP((gint)fheight, 1, maxHeight); msctx->sel1X = CLAMP(mgup->src_win.offs_x, 0, maxWidth); msctx->sel1Y = CLAMP(mgup->src_win.offs_y, 0, maxHeight); msctx->sel2X = CLAMP((msctx->sel1X + width), 0, maxWidth); msctx->sel2Y = CLAMP((msctx->sel1Y + height), 0, maxHeight); } else { msctx->sel1X = 0; msctx->sel1Y = 0; msctx->sel2X = maxWidth; msctx->sel2Y = maxHeight; } if(gap_debug) { printf("Boundaries: sel1: %d / %d sel2: %d / %d zoom:%.5f\n" , (int)msctx->sel1X , (int)msctx->sel1Y , (int)msctx->sel2X , (int)msctx->sel2Y , (float)mgup->src_win.zoom ); } msctx->pftEdge = gimp_pixel_fetcher_new (msctx->edgeDrawable, FALSE /* shadow */); gimp_pixel_fetcher_set_edge_mode (msctx->pftEdge, GIMP_PIXEL_FETCHER_EDGE_BLACK); p_generateWorkpoints(msctx, mgpp); gimp_pixel_fetcher_destroy (msctx->pftEdge); } if(msctx->edgeDrawable != NULL) { gimp_drawable_detach(msctx->edgeDrawable); msctx->edgeDrawable = NULL; } if(deleteEdgeImage == TRUE) { gap_image_delete_immediate(msctx->edgeImageId); } if(gap_debug) { printf("gap_moprhShapeDetectionEdgeBased END\n"); } mgup->workpointGenerationBusy = FALSE; return ; } /* end gap_moprhShapeDetectionEdgeBased */
/* ----------------------------------------------- * gap_morph_shape_generate_frame_tween_workpoints * ----------------------------------------------- * generate workpoint files (one per frame) * for the specified frame range. * */ gint32 gap_morph_shape_generate_frame_tween_workpoints(GapAnimInfo *ainfo_ptr , GapMorphGlobalParams *mgpp, GtkWidget *masterProgressBar, GtkWidget *progressBar, gboolean *cancelFlagPtr) { GapMorphGUIParams morph_gui_params; GapMorphGUIParams *mgup; gint32 frameCount; gint32 currFrameNr; gint32 nextFrameNr; gint32 currLayerId; gint32 nextLayerId; gint32 firstFrameNr; gint32 lastFrameNr; gdouble framesToProcess; mgup = &morph_gui_params; mgup->mgpp = mgpp; mgup->src_win.zoom = 1.0; /* no zoom available here (use full drawable size for workpoint generation */ mgup->cancelWorkpointGeneration = FALSE; mgup->progressBar = progressBar; mgpp->master_wp_list = NULL; gimp_rgb_set(&mgup->pointcolor, 0.1, 1.0, 0.1); /* startup with GREEN pointcolor */ gimp_rgb_set_alpha(&mgup->pointcolor, 1.0); gimp_rgb_set(&mgup->curr_pointcolor, 1.0, 1.0, 0.1); /* startup with YELLOW color */ gimp_rgb_set_alpha(&mgup->curr_pointcolor, 1.0); frameCount = 0; nextLayerId = -1; if(gap_lib_chk_framerange(ainfo_ptr) != 0) { return(0); } firstFrameNr = CLAMP(mgpp->range_from, ainfo_ptr->first_frame_nr, ainfo_ptr->last_frame_nr); currFrameNr = firstFrameNr; currLayerId = p_getMergedFrameImageLayer(ainfo_ptr, currFrameNr); nextFrameNr = currFrameNr + 1; while(TRUE) { lastFrameNr = MIN(mgpp->range_to, ainfo_ptr->last_frame_nr); framesToProcess = MAX(1, (lastFrameNr - currFrameNr) -1); if (nextFrameNr > lastFrameNr) { break; } nextLayerId = p_getMergedFrameImageLayer(ainfo_ptr, nextFrameNr); if((nextLayerId >= 0) && (currLayerId >= 0)) { char *workpointFileName; gboolean success; workpointFileName = gap_lib_alloc_fname(ainfo_ptr->basename, currFrameNr, "." GAP_MORPH_WORKPOINT_EXTENSION); p_do_master_progress(masterProgressBar , workpointFileName , framesToProcess , (currFrameNr - firstFrameNr) ); success = p_generateWorkpointFileForFramePair(workpointFileName , mgup , cancelFlagPtr , currLayerId , nextLayerId ); g_free(workpointFileName); gap_image_delete_immediate(gimp_drawable_get_image(currLayerId)); if(!success) { break; } currFrameNr = nextFrameNr; currLayerId = nextLayerId; } nextFrameNr++; } if(nextLayerId >= 0) { gap_image_delete_immediate(gimp_drawable_get_image(nextLayerId)); } return (frameCount); } /* end gap_morph_shape_generate_frame_tween_workpoints */
/* ---------------------------- * gap_frame_fetch_dup_image * ---------------------------- * returns merged or selected layer_id * (that is the only visible layer in temporary created scratch image) * the caller is resonsible to delete the scratch image when processing is done. * this can be done by calling gap_frame_fetch_delete_list_of_duplicated_images() */ gint32 gap_frame_fetch_dup_image(gint32 ffetch_user_id ,const char *filename /* full filename of the image (already contains framenr) */ ,gint32 stackpos /* 0 pick layer on top of stack, -1 merge visible layers */ ,gboolean addToCache /* enable caching */ ) { gint32 resulting_layer; gint32 image_id; gint32 dup_image_id; resulting_layer = -1; dup_image_id = -1; image_id = p_load_cache_image(filename, ffetch_user_id, addToCache); if (image_id < 0) { return(-1); } if (stackpos < 0) { dup_image_id = gimp_image_duplicate(image_id); gap_frame_fetch_remove_parasite(dup_image_id); resulting_layer = gap_image_merge_visible_layers(dup_image_id, GIMP_CLIP_TO_IMAGE); } else { gint l_nlayers; gint32 *l_layers_list; l_layers_list = gimp_image_get_layers(image_id, &l_nlayers); if(l_layers_list != NULL) { if (stackpos < l_nlayers) { gint32 src_layer_id; gdouble l_xresoulution, l_yresoulution; gint32 l_unit; src_layer_id = l_layers_list[stackpos]; dup_image_id = gimp_image_new (gimp_image_width(image_id) , gimp_image_height(image_id) , gimp_image_base_type(image_id) ); gimp_image_get_resolution(image_id, &l_xresoulution, &l_yresoulution); gimp_image_set_resolution(dup_image_id, l_xresoulution, l_yresoulution); l_unit = gimp_image_get_unit(image_id); gimp_image_set_unit(dup_image_id, l_unit); resulting_layer = gap_layer_copy_to_image (dup_image_id, src_layer_id); } g_free (l_layers_list); } } p_add_image_to_list_of_duplicated_images(dup_image_id, ffetch_user_id); if (addToCache != TRUE) { GimpParasite *l_parasite; l_parasite = gimp_image_parasite_find(image_id, GAP_IMAGE_CACHE_PARASITE); if(l_parasite) { gimp_parasite_free(l_parasite); } else { /* the original image is not cached * (delete it because the caller gets the preprocessed duplicate) */ gap_image_delete_immediate(image_id); } } return(resulting_layer); } /* end gap_frame_fetch_dup_image */
/* ---------------------------------------------------- * p_load_cache_image * ---------------------------------------------------- * load an image from cache or from file (in case image is not already cached) * in case the flag addToCache is TRUE the image will be automatically added * to the cache after read from file operation. */ static gint32 p_load_cache_image(const char* filename, gint32 ffetch_user_id, gboolean addToCache) { gint32 l_image_id; char *l_filename; gint32 *images; gint nimages; gint l_idi; gint l_number_of_cached_images; gint32 l_first_cached_image_id; GimpParasite *l_parasite; if(filename == NULL) { printf("p_load_cache_image: ** ERROR cant load filename == NULL! pid:%d\n", (int)gap_base_getpid()); return -1; } l_image_id = -1; l_first_cached_image_id = -1; l_number_of_cached_images = 0; images = gimp_image_list(&nimages); for(l_idi=0; l_idi < nimages; l_idi++) { l_parasite = gimp_image_parasite_find(images[l_idi], GAP_IMAGE_CACHE_PARASITE); if(l_parasite) { gint32 *mtime_ptr; gint32 *ffetch_id_ptr; gchar *filename_ptr; mtime_ptr = (gint32 *) l_parasite->data; ffetch_id_ptr = (gint32 *)&l_parasite->data[sizeof(gint32)]; filename_ptr = (gchar *)&l_parasite->data[sizeof(gint32) + sizeof(gint32)]; l_number_of_cached_images++; if (l_first_cached_image_id < 0) { l_first_cached_image_id = images[l_idi]; } if(strcmp(filename, filename_ptr) == 0) { gint32 mtimefile; mtimefile = gap_file_get_mtime(filename); if(mtimefile == *mtime_ptr) { /* image found in cache */ l_image_id = images[l_idi]; } else { /* image found in cache, but has changed modification timestamp * (delete from cache and reload) */ if(gap_debug) { printf("FrameFetcher: DELETE because mtime changed : (image_id:%d) ffetchId:%d name:%s mtimefile:%d mtimecache:%d pid:%d\n" , (int)images[l_idi] , (int)*ffetch_id_ptr , gimp_image_get_filename(images[l_idi]) , (int)mtimefile , (int)*mtime_ptr , (int)gap_base_getpid() ); } gap_image_delete_immediate(images[l_idi]); } l_idi = nimages -1; /* force break at next loop iteration */ } gimp_parasite_free(l_parasite); } } if(images) { g_free(images); } if (l_image_id >= 0) { if(gap_debug) { printf("FrameFetcher: p_load_cache_image CACHE-HIT :%s (image_id:%d) pid:%d\n" , filename, (int)l_image_id, (int)gap_base_getpid()); } return(l_image_id); } l_filename = g_strdup(filename); l_image_id = gap_lib_load_image(l_filename); if(gap_debug) { printf("FrameFetcher: loaded image from disk:%s (image_id:%d) pid:%d\n" , l_filename, (int)l_image_id, (int)gap_base_getpid()); } if((l_image_id >= 0) && (addToCache == TRUE)) { guchar *parasite_data; gint32 parasite_size; gint32 *parasite_mtime_ptr; gint32 *parasite_ffetch_id_ptr; gchar *parasite_filename_ptr; gint32 len_filename0; /* filename length including the terminating 0 */ if (l_number_of_cached_images > p_get_ffetch_max_img_cache_elements()) { /* the image cache already has more elements than desired, * drop the 1st cached image */ if(gap_debug) { printf("FrameFetcher: DELETE because cache is full: (image_id:%d) name:%s number_of_cached_images:%d pid:%d\n" , (int)l_first_cached_image_id , gimp_image_get_filename(l_first_cached_image_id) , (int)l_number_of_cached_images , (int)gap_base_getpid() ); } gap_image_delete_immediate(l_first_cached_image_id); } /* build parasite data including mtime and full filename with terminating 0 byte */ len_filename0 = strlen(filename) + 1; parasite_size = sizeof(gint32) + sizeof(gint32) + len_filename0; parasite_data = g_malloc0(parasite_size); parasite_mtime_ptr = (gint32 *)parasite_data; parasite_ffetch_id_ptr = (gint32 *)¶site_data[sizeof(gint32)]; parasite_filename_ptr = (gchar *)¶site_data[sizeof(gint32) + sizeof(gint32)]; *parasite_mtime_ptr = gap_file_get_mtime(filename); *parasite_ffetch_id_ptr = ffetch_user_id; memcpy(parasite_filename_ptr, filename, len_filename0); /* attach a parasite to mark the image as part of the gap image cache */ l_parasite = gimp_parasite_new(GAP_IMAGE_CACHE_PARASITE ,0 /* GIMP_PARASITE_PERSISTENT 0 for non persistent */ ,parasite_size ,parasite_data ); if(l_parasite) { gimp_image_parasite_attach(l_image_id, l_parasite); gimp_parasite_free(l_parasite); } g_free(parasite_data); } g_free(l_filename); return(l_image_id); } /* end p_load_cache_image */