Exemplo n.º 1
0
/* ----------------------------------------------------
 * 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 */
Exemplo n.º 2
0
/* -------------------------------------------------
 * 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 */
Exemplo n.º 3
0
/* -------------------------------------------------------
 * 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 */
Exemplo n.º 4
0
/* ------------------------------------
 * 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 */
Exemplo n.º 5
0
/* --------------------------------
 * 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 */
Exemplo n.º 6
0
/* -----------------------------------------------
 * 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 */
Exemplo n.º 7
0
/* ----------------------------
 * 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 */
Exemplo n.º 8
0
/* ----------------------------------------------------
 * 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 *)&parasite_data[sizeof(gint32)];
    parasite_filename_ptr = (gchar *)&parasite_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 */