[gimp-gap] Performance relevant changes for storyboard when rendering images.



commit 5e5492f38a69883285664ff51c441ef3ea537f40
Author: Wolfgang Hofer <wolfgangh svn gnome org>
Date:   Wed Sep 26 17:37:50 2012 +0200

    Performance relevant changes for storyboard when rendering images.

 ChangeLog                        |   36 ++
 gap/gap_frame_fetcher.c          |  660 +++++++++++++++++++++++++++++-----
 gap/gap_frame_fetcher.h          |   43 ++-
 gap/gap_image.c                  |   23 +-
 gap/gap_story_render_processor.c |  741 ++++++++++++++++++++++++++++++++++++--
 5 files changed, 1381 insertions(+), 122 deletions(-)
---
diff --git a/ChangeLog b/ChangeLog
index 75b6853..651960f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,39 @@
+2012-09-26 Wolfgang Hofer <hof gimp org>
+
+- Performance relevant changes for storyboard when rendering
+  large images.
+  o) gap_frame_fetcher supports caching of prescaled images.
+  o) storyboard render processor look ahead when processing
+   frame types GAP_FRN_IMAGE or GAP_FRN_ANIMIMAGE
+   and calculate the max prescale size.
+   
+   look ahead as far as the same imagename is used.
+   
+   procedure: p_stb_render_image_or_animimage
+
+   - "gap-image-delete-workarond"
+   
+    The workaround disables undo and scales the image down to miniumum size
+    before calling the gimp_image_delete procedure.
+    this way memory resources for big layers will be freed up immediate.
+    
+    Note that this workaround implematation is already present in previous
+    GIMP-GAP versions but now can be turned off via gimprc configuration.
+    
+    When using GIMP-GAP with gimp-2.6.x it is recommanded to enable this workaround
+    (otherwise encoding videos or stroryboard editing may quickly run out of memory
+    because lots of internally rendering is done by creating temporary images
+    and deletes them immediate after processing).
+    
+    This workaround may not be required when GIMP-GAP is used with newer gimp
+    versions. (not yet tested)
+
+ * gap/gap_image.c
+ * gap/gap_frame_fetcher.c [.h]
+ * gap/gap_story_render_processor.c
+
+
+
 2012-08-28 Wolfgang Hofer <hof gimp org>
 
 - Speed up for the BlendFill filter.
diff --git a/gap/gap_frame_fetcher.c b/gap/gap_frame_fetcher.c
index c6d4814..bbdfdfa 100644
--- a/gap/gap_frame_fetcher.c
+++ b/gap/gap_frame_fetcher.c
@@ -102,13 +102,29 @@
 
 /* the lists of cached images and duplicates are implemented via GIMP image parasites,
  * where images are simply loaded by GIMP without adding a display and marked with a non persistent parasite.
- * the GAP_IMAGE_CACHE_PARASITE holds the modification timestamp (mtime), gint32 ffetch_user_id and full filename (inclusive terminating 0)
+ * the GAP_IMAGE_CACHE_PARASITE 
+ *     holds the modification timestamp (mtime), gint32 ffetch_user_id
+ *     gint32 type (GAP_IMAGE_CACHE_TYPE_ORIGINAL_SIZE or GAP_IMAGE_CACHE_TYPE_PRESCALE_ENABLED)
+ *     gint32 orig_width, gint32 orig_height (the unscaled original size of the image)
+ *     and full filename (inclusive terminating 0)
  * the GAP_IMAGE_DUP_CACHE_PARASITE holds the gint32 ffetch_user_id
  */
 
 #define GAP_IMAGE_CACHE_PARASITE "GAP-IMAGE-CACHE-PARASITE"
 #define GAP_IMAGE_DUP_CACHE_PARASITE "GAP-IMAGE-DUP-CACHE-PARASITE"
 
+#define GAP_IMAGE_CACHE_TYPE_ORIGINAL_SIZE    0
+#define GAP_IMAGE_CACHE_TYPE_PRESCALE_ENABLED 1
+
+typedef struct GapImageCacheParasitePointers {
+  gint32 *mtime_ptr;
+  gint32 *ffetch_id_ptr;
+  gint32 *type_ptr;
+  gint32 *orig_width_ptr;
+  gint32 *orig_height_ptr;
+  gchar  *filename_ptr;
+} GapImageCacheParasitePointers;
+
 
 typedef struct GapFFetchResourceUserElem
 {
@@ -116,6 +132,15 @@ typedef struct GapFFetchResourceUserElem
    void *next;
 } GapFFetchResourceUserElem;
 
+#define MAX_OLD_IMAGE_IDS 10
+
+typedef struct GapImageChacheInfo  /* nickname: cinf */
+{
+   gint32 old_cached_image_ids[MAX_OLD_IMAGE_IDS];
+   gint32 number_of_old_cached_images;
+   gint32 number_of_cached_images;
+} GapImageChacheInfo;
+
 
 /* -------- types for the video handle cache  ------- */
 
@@ -153,6 +178,20 @@ static GapFFetchResourceUserElem *global_rsource_users = NULL;
  *         FRAME FETCHER procedures                          *
  *************************************************************
  */
+static void          p_init_GapImageCacheParasitePointers(GapImageCacheParasitePointers *paraPtr
+                        , guchar *parasite_data
+                        );
+static gint32        p_find_cache_image(const char* filename, gint32 ffetch_user_id
+                         , gint32 cachedImageType
+                         , GapImageChacheInfo *cinf
+                         , gint32 *originalWidthPtr, gint32 *originalHeightPtr
+                         );
+static gint32        p_load_image_and_add_to_cache(const char* filename, gint32 ffetch_user_id
+                         , gboolean addToCache, gint32 cachedImageType
+                         , GapImageChacheInfo *cinf
+                         , gint32 *originalWidthPtr, gint32 *originalHeightPtr
+                         );
+
 static gint32         p_load_cache_image(const char* filename, gint32 ffetch_user_id, gboolean addToCache);
 static void           p_drop_image_cache(void);
 #ifdef GAP_ENABLE_VIDEOAPI_SUPPORT
@@ -169,6 +208,25 @@ static gint32 p_get_ffetch_max_img_cache_elements();
 static gint32 p_get_ffetch_max_gvc_cache_elements();
 static gint32 ffetch_gva_frames_to_keep_cached();
 
+/* ----------------------------------------------------
+ * p_init_GapImageCacheParasitePointers
+ * ----------------------------------------------------
+ * init pointers in the GapImageCacheParasitePointers to access the
+ * parasite data.
+ * Note that the filename_ptr refers to the last entry that has variable size
+ * and is a \0 terminated string.
+ */
+static void
+p_init_GapImageCacheParasitePointers(GapImageCacheParasitePointers *paraPtr, guchar *parasite_data)
+{
+  paraPtr->mtime_ptr =       (gint32 *)parasite_data;
+  paraPtr->ffetch_id_ptr =   (gint32 *)&parasite_data[sizeof(gint32)];
+  paraPtr->type_ptr =        (gint32 *)&parasite_data[2 * sizeof(gint32)];
+  paraPtr->orig_width_ptr =  (gint32 *)&parasite_data[3 * sizeof(gint32)];
+  paraPtr->orig_height_ptr = (gint32 *)&parasite_data[4 * sizeof(gint32)];
+  paraPtr->filename_ptr =    (gchar *) &parasite_data[5 * sizeof(gint32)];
+
+}  /* end p_init_GapImageCacheParasitePointers */
 
 /* ----------------------------------------------------
  * p_get_ffetch_max_img_cache_elements
@@ -272,36 +330,54 @@ p_get_ffetch_gva_frames_to_keep_cached()
   return (value);
 }  /* end p_get_ffetch_gva_frames_to_keep_cached */
 
+
 /* ----------------------------------------------------
- * p_load_cache_image
+ * p_find_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.
+ * find an image with filename and matching cachedImageType 
+ *   GAP_IMAGE_CACHE_TYPE_ORIGINAL_SIZE or
+ *   GAP_IMAGE_CACHE_TYPE_PRESCALE_ENABLED
+ * in the cache.
+ * returns the imageId of the cached image or -1 if not found in the cache.
+ *
+ * OUT parameters
+ *   first_cached_image_id    .. id of the 1st image in the cache.
+ *   number_of_cached_images  .. total number of cached images (only valid when image was NOT FOUND!)
+ *   originalWidth[Height]Ptr .. the unscaled original size of the image.
  */
 static gint32
-p_load_cache_image(const char* filename, gint32 ffetch_user_id, gboolean addToCache)
+p_find_cache_image(const char* filename, gint32 ffetch_user_id
+   , gint32 cachedImageType
+   ,  GapImageChacheInfo *cinf
+   , gint32 *originalWidthPtr, gint32 *originalHeightPtr
+   )
 {
   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;
 
-
+  cinf->old_cached_image_ids[0] = -1;
+  cinf->number_of_old_cached_images = 0;
+  cinf->number_of_cached_images = 0;
+  *originalWidthPtr = 0;
+  *originalHeightPtr = 0;
+  if(gap_debug)
+  {
+    printf("p_find_cache_image START cachedImageType:%d %s\n"
+      ,(int)cachedImageType
+      ,filename
+      );
+  }
   if(filename == NULL)
   {
-    printf("p_load_cache_image: ** ERROR cant load filename == NULL! pid:%d\n", (int)gap_base_getpid());
+    printf("p_find_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++)
   {
@@ -309,29 +385,32 @@ p_load_cache_image(const char* filename, gint32 ffetch_user_id, gboolean addToCa
 
     if(l_parasite)
     {
-      gint32 *mtime_ptr;
-      gint32 *ffetch_id_ptr;
-      gchar  *filename_ptr;
+      GapImageCacheParasitePointers para;
+      GapImageCacheParasitePointers *paraPtr;
       
-      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)];
+      paraPtr = &para;
+      p_init_GapImageCacheParasitePointers(paraPtr, l_parasite->data);
+
     
-      l_number_of_cached_images++;
-      if (l_first_cached_image_id < 0)
+      cinf->number_of_cached_images++;
+      if (cinf->number_of_old_cached_images < MAX_OLD_IMAGE_IDS)
       {
-        l_first_cached_image_id = images[l_idi];
+        cinf->old_cached_image_ids[cinf->number_of_old_cached_images] = images[l_idi];
+        cinf->number_of_old_cached_images++;
       }
       
-      if(strcmp(filename, filename_ptr) == 0)
+      if ((*(paraPtr->type_ptr) == cachedImageType)
+      && (strcmp(filename, paraPtr->filename_ptr) == 0))
       {
         gint32 mtimefile;
         
         mtimefile = gap_file_get_mtime(filename);
-        if(mtimefile == *mtime_ptr)
+        if(mtimefile == *(paraPtr->mtime_ptr))
         {
           /* image found in cache */
           l_image_id = images[l_idi];
+          *originalWidthPtr = *(paraPtr->orig_width_ptr);
+          *originalHeightPtr = *(paraPtr->orig_height_ptr);
         }
         else
         {
@@ -342,10 +421,10 @@ p_load_cache_image(const char* filename, gint32 ffetch_user_id, gboolean addToCa
           {
             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
+                  , (int)*(paraPtr->ffetch_id_ptr)
                   , gimp_image_get_filename(images[l_idi])
                   , (int)mtimefile
-                  , (int)*mtime_ptr
+                  , (int)*(paraPtr->mtime_ptr)
                   , (int)gap_base_getpid()
                   );
           }
@@ -361,16 +440,59 @@ p_load_cache_image(const char* filename, gint32 ffetch_user_id, gboolean addToCa
     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"
+      printf("FrameFetcher: p_find_cache_image CACHE-HIT :%s (image_id:%d) pid:%d\n"
             , filename, (int)l_image_id, (int)gap_base_getpid());
     }
     return(l_image_id);
   }
 
+  if(gap_debug)
+  {
+    printf("p_find_cache_image END Not found in cache %s\n"
+      ,filename
+      );
+  }
+  
+  return (-1);  /* indicates image not found in cache */
+
+}  /* end p_find_cache_image */
+
+
+
+
+/* ----------------------------------------------------
+ * p_load_image_and_add_to_cache
+ * ----------------------------------------------------
+ * load the image from file (depends on enableLoad flag)
+ *
+ * in case the flag addToCache is TRUE the loaded image will be automatically added
+ * to the cache after read from file operation.
+ * This will also delete the the first cached imge in case the number_of_cached_images
+ * exceded the configured limit (gimprc paramter "gap_ffetch_max_img_cache_elements")
+ */
+static gint32
+p_load_image_and_add_to_cache(const char* filename, gint32 ffetch_user_id
+   , gboolean addToCache, gint32 cachedImageType
+   , GapImageChacheInfo *cinf
+   , gint32 *originalWidthPtr, gint32 *originalHeightPtr
+   )
+{
+  gint32 l_image_id;
+  char *l_filename;
+
+  GimpParasite  *l_parasite;
+
+  if(filename == NULL)
+  {
+    printf("p_load_image_and_add_to_cache: ** ERROR cant load filename == NULL! pid:%d\n", (int)gap_base_getpid());
+    return -1;
+  }
+
   l_filename = g_strdup(filename);
   l_image_id = gap_lib_load_image(l_filename);
   if(gap_debug)
@@ -379,64 +501,146 @@ p_load_cache_image(const char* filename, gint32 ffetch_user_id, gboolean addToCa
       , l_filename, (int)l_image_id, (int)gap_base_getpid());
   }
 
-  if((l_image_id >= 0) && (addToCache == TRUE))
+  if(l_image_id >= 0)
   {
-    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())
+    *originalWidthPtr = gimp_image_width(l_image_id);
+    *originalHeightPtr = gimp_image_height(l_image_id);
+    
+    if(addToCache == TRUE)
     {
-      /* the image cache already has more elements than desired,
-       * drop the 1st cached image
-       */
+      guchar *parasite_data;
+      gint32  parasite_size;
+      gint32  len_filename0;           /* filename length including the terminating 0 */
+      GapImageCacheParasitePointers para;
+      GapImageCacheParasitePointers *paraPtr;
+      gint    ii;
+      
       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()
-              );
+         printf("(L2) number_of_cached_images:%d first_cached_image_id:%d\n"
+            , (int)cinf->number_of_cached_images
+            , (int)cinf->old_cached_image_ids[0]
+            );
+      }
+      
+      ii = 0;
+      while ((cinf->number_of_cached_images >= p_get_ffetch_max_img_cache_elements())
+      && (cinf->old_cached_image_ids[ii] >= 0))
+      {
+        /* 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)cinf->old_cached_image_ids[ii]
+                , gimp_image_get_filename(cinf->old_cached_image_ids[ii])
+                , (int)cinf->number_of_cached_images
+                , (int)gap_base_getpid()
+                );
+        }
+        gap_image_delete_immediate(cinf->old_cached_image_ids[ii]);
+        cinf->number_of_cached_images--;
+        ii++;
+        if((ii >= MAX_OLD_IMAGE_IDS)
+        || (ii >= cinf->number_of_old_cached_images))
+        {
+          break;
+        }
       }
-      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);
+      /* build parasite data including mtime and full filename with terminating 0 byte */
+      len_filename0 = strlen(filename) + 1;
+      parasite_size = 5 * sizeof(gint32) + len_filename0;  
+      parasite_data = g_malloc0(parasite_size);
+
+      paraPtr = &para;
+      p_init_GapImageCacheParasitePointers(paraPtr, parasite_data);
+
+      *(paraPtr->mtime_ptr) = gap_file_get_mtime(filename);
+      *(paraPtr->ffetch_id_ptr) = ffetch_user_id;
+      *(paraPtr->type_ptr) = cachedImageType;
+      *(paraPtr->orig_width_ptr) = gimp_image_width(l_image_id);
+      *(paraPtr->orig_height_ptr) = gimp_image_height(l_image_id);
+      memcpy(paraPtr->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);
     
-    /* 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 */
+}  /* end p_load_image_and_add_to_cache */
+
+
+
+/* ----------------------------------------------------
+ * 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.
+ * Note: this procedure handles only images at original size (GAP_IMAGE_CACHE_TYPE_ORIGINAL_SIZE)
+ * a prescaled cached variant of the image will be ignored (e.g NOT be found by this procedure)
+ */
+static gint32
+p_load_cache_image(const char* filename, gint32 ffetch_user_id, gboolean addToCache)
+{
+  gint32 retImgageId;
+  gint32 originalWidth;
+  gint32 originalHeight;
+  GapImageChacheInfo  gapImageChacheInfo;
+  if(gap_debug)
+  {
+    printf("p_load_cache_image START %s\n"
+      ,filename
+      );
+  }
+  retImgageId = p_find_cache_image(filename, ffetch_user_id
+     , GAP_IMAGE_CACHE_TYPE_ORIGINAL_SIZE  /* cachedImageType */
+     , &gapImageChacheInfo
+     , &originalWidth, &originalHeight
+     );
+  if (retImgageId < 0)
+  {
+    retImgageId = p_load_image_and_add_to_cache(filename, ffetch_user_id
+                           , addToCache
+                           , GAP_IMAGE_CACHE_TYPE_ORIGINAL_SIZE  /* cachedImageType */
+                           , &gapImageChacheInfo
+                           , &originalWidth
+                           , &originalHeight
+                         );
+  }
+
+  if(gap_debug)
+  {
+    printf("p_load_cache_image END id:%d %s\n"
+      ,(int)retImgageId
+      , filename
+      );
+  }
+  
+  return (retImgageId);
+  
+}   /* end p_load_cache_image */
+
+
 
 /* ----------------------------------------------------
  * p_drop_image_cache
@@ -760,6 +964,7 @@ gap_frame_fetch_delete_list_of_duplicated_images(gint32 ffetch_user_id)
  * gap_frame_fetch_orig_image
  * ----------------------------
  * returns image_id of the original cached image.
+ *    RESTRICTION: the Caller must NOT not modify that image and shall not open a display for it!
  */
 gint32
 gap_frame_fetch_orig_image(gint32 ffetch_user_id
@@ -771,6 +976,256 @@ gap_frame_fetch_orig_image(gint32 ffetch_user_id
 }  /* end gap_frame_fetch_orig_image */
 
 
+/* -------------------------------
+ * gap_frame_fetch_image_scale
+ * -------------------------------
+ */
+void
+gap_frame_fetch_image_scale(gint32 imageId, gint32 prescaleWidth, gint32 prescaleHeight)
+{
+  if(gap_debug)
+  {
+    char          *l_filename;
+    l_filename = gimp_image_get_filename(imageId);
+    
+    printf("gap_frame_fetch_image_SCALE id:%d (%dx%d) to newSize ==> (%dx%d) %s\n"
+      ,(int)imageId
+      ,(int)gimp_image_width(imageId)
+      ,(int)gimp_image_height(imageId)
+      ,(int)prescaleWidth
+      ,(int)prescaleHeight
+      ,l_filename
+      );
+
+    if(l_filename != NULL)
+    {
+      g_free(l_filename);
+    }
+  }
+  gimp_image_undo_disable(imageId);
+  gimp_image_scale(imageId, prescaleWidth, prescaleHeight);
+
+}  /* end gap_frame_fetch_image_scale */
+
+/* -------------------------------
+ * gap_frame_fetch_image_duplicate
+ * -------------------------------
+ */
+gint32
+gap_frame_fetch_image_duplicate(gint32 imageId)
+{
+  gint32 dupImageId;
+  if(gap_debug)
+  {
+    char          *l_filename;
+    l_filename = gimp_image_get_filename(imageId);
+    
+    printf("gap_frame_fetch_image_DUPLICATE id:%d (%dx%d) %s\n"
+      ,(int)imageId
+      ,(int)gimp_image_width(imageId)
+      ,(int)gimp_image_height(imageId)
+      ,l_filename
+      );
+
+    if(l_filename != NULL)
+    {
+      g_free(l_filename);
+    }
+  }
+  
+  dupImageId = gimp_image_duplicate(imageId);
+  return (dupImageId);
+}
+
+
+/* -------------------------------
+ * p_duplicate_image_for_prescale
+ * -------------------------------
+ * duplicate the specified image
+ * and mark the duplicate as "prescaleEnabled" 
+ */
+static gint32
+p_duplicate_image_for_prescale(gint32 origsizeImageId)
+{
+  gint32 retImageId;
+  GimpParasite  *l_parasite;
+
+  retImageId = gap_frame_fetch_image_duplicate(origsizeImageId);
+
+  /* change the type information in the image parasite in the copy */
+  l_parasite = gimp_image_parasite_find(retImageId, GAP_IMAGE_CACHE_PARASITE);
+  if (l_parasite)
+  {
+    GapImageCacheParasitePointers para;
+    GapImageCacheParasitePointers *paraPtr;
+
+    paraPtr = &para;
+    p_init_GapImageCacheParasitePointers(paraPtr, l_parasite->data);
+
+    gap_frame_fetch_remove_parasite(retImageId);
+
+    *(paraPtr->type_ptr) = GAP_IMAGE_CACHE_TYPE_PRESCALE_ENABLED;
+    gimp_image_parasite_attach(retImageId, l_parasite);
+
+    gimp_parasite_free(l_parasite);
+  }
+  return (retImageId);
+  
+}  /* end p_duplicate_image_for_prescale */
+
+
+/* -------------------------------
+ * gap_frame_fetch_prescaled_image
+ * -------------------------------
+ * returns image_id of the prescaled cached image.
+ *    RESTRICTION: the Caller must NOT not modify that image and shall not open a display for it!
+ * Note that the returned image can be either the prescaled copy or the cached image at
+ * original size (in case prescale Size not yet known or exactly matching original size.
+ */
+gint32
+gap_frame_fetch_prescaled_image(gint32 ffetch_user_id
+    ,const char *filename            /* full filename of the image */
+    ,gboolean addToCache             /* enable caching on prescaled image */
+    ,gint32 prescaleWidth            /* use 0 in case prescale size is not yet known */
+    ,gint32 prescaleHeight           /* use 0 in case prescale size is not yet known */
+    ,gint32 *originalWidthPtr        /* OUT: width of the unscaled original image file */
+    ,gint32 *originalHeightPtr       /* OUT: width of the unscaled original image file */
+    )
+{
+  GapImageChacheInfo  gapImageChacheInfo;
+  gint32 retImageId = -1;
+  gint32 prescaleImageId = -1;
+  gint32 origsizeImageId = -1;
+
+  if(gap_debug)
+  {
+    printf("gap_frame_fetch_prescaled_image START %s\n"
+      ,filename
+      );
+  }
+  /* search in cache for images marked as "prescaleEnabled" */
+  prescaleImageId = p_find_cache_image(filename, ffetch_user_id
+          , GAP_IMAGE_CACHE_TYPE_PRESCALE_ENABLED  /* cachedImageType */
+          , &gapImageChacheInfo
+          , originalWidthPtr
+          , originalHeightPtr
+          );
+
+  if (prescaleImageId >= 0)
+  {
+     retImageId = prescaleImageId;
+     gint32 pWidth = gimp_image_width(prescaleImageId);
+     gint32 pHeight = gimp_image_height(prescaleImageId);
+
+     /* check width for re-upscale attempt */
+     if ((prescaleWidth > pWidth)
+     &&  (*originalWidthPtr > pWidth))
+     {
+        retImageId = -1;
+     }          
+        
+     /* check height for re-upscale attempt */
+     if ((prescaleHeight > pHeight)
+     &&  (*originalHeightPtr > pHeight))
+     {
+        retImageId = -1;
+     }
+     
+     if (retImageId >= 0)
+     {
+       if ((pWidth != prescaleWidth)
+       ||  (pHeight != prescaleHeight))
+       {
+         if ((prescaleWidth > 0)
+         &&  (prescaleHeight >0))
+         {
+           gap_frame_fetch_image_scale(retImageId, prescaleWidth, prescaleHeight);
+         }
+       }
+       return(retImageId);
+     }
+
+  
+     if(gap_debug)
+     {
+       printf("gap_frame_fetch_prescaled_image DELETE img: %d from cache %s\n"
+         ,(int)prescaleImageId
+         ,filename
+       );
+     }
+     /* remove prescaleImageId from cache.
+      * The current requested prescale size is greater than the available
+      * prescaled image in the cache AND the original is larger than the cached variante.
+      * in this case remove the prescaled image from the cache
+      * (to force re-caching of a larger version and avoid quality loss
+      *  that could be caused by upscaling of an already downscaled image)
+      */
+     gap_image_delete_immediate(prescaleImageId);
+  }
+
+  /* search in cache for non prescaled image at original size (not marked as "prescaleEnabled" */
+  origsizeImageId = p_find_cache_image(filename, ffetch_user_id
+          , GAP_IMAGE_CACHE_TYPE_ORIGINAL_SIZE  /* cachedImageType */
+          , &gapImageChacheInfo
+          , originalWidthPtr
+          , originalHeightPtr
+          );
+  if (origsizeImageId >= 0)
+  {
+    if ((gimp_image_width(origsizeImageId) == prescaleWidth)
+    &&  (gimp_image_height(origsizeImageId) == prescaleHeight))
+    {
+      return (origsizeImageId);
+    }
+    retImageId = p_duplicate_image_for_prescale(origsizeImageId);
+  }
+
+  if (retImageId < 0)
+  {
+    origsizeImageId = p_load_image_and_add_to_cache(filename, ffetch_user_id
+         , addToCache
+         , GAP_IMAGE_CACHE_TYPE_ORIGINAL_SIZE  /* cachedImageType */
+         , &gapImageChacheInfo
+         , originalWidthPtr
+         , originalHeightPtr
+         );
+    if (origsizeImageId >= 0)
+    {
+      if ((prescaleWidth <= 0)
+      ||  (prescaleHeight <= 0))
+      {
+        return (origsizeImageId);
+      }
+      if ((gimp_image_width(origsizeImageId) == prescaleWidth)
+      &&  (gimp_image_height(origsizeImageId) == prescaleHeight))
+      {
+        return (origsizeImageId);
+      }
+      
+    
+      retImageId = p_duplicate_image_for_prescale(origsizeImageId);
+    }
+  }
+
+  if (retImageId >= 0)
+  {
+     if ((gimp_image_width(retImageId) != prescaleWidth)
+     ||  (gimp_image_height(retImageId) != prescaleHeight))
+     {
+       if ((prescaleWidth > 0)
+       &&  (prescaleHeight >0))
+       {
+         gap_frame_fetch_image_scale(retImageId, prescaleWidth, prescaleHeight);
+       }
+     }
+  }
+
+  return (retImageId);
+  
+
+}  /* end gap_frame_fetch_prescaled_image */
+
+
 
 /* ----------------------------
  * gap_frame_fetch_dup_image
@@ -801,7 +1256,7 @@ gap_frame_fetch_dup_image(gint32 ffetch_user_id
   
   if (stackpos < 0)
   {
-    dup_image_id = gimp_image_duplicate(image_id);
+    dup_image_id = gap_frame_fetch_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);
@@ -1079,6 +1534,31 @@ gap_frame_fetch_unregister_user(gint32 ffetch_user_id)
 }  /* end gap_frame_fetch_unregister_user */
 
 
+/* ---------------------------------
+ * gap_frame_fetch_is_image_in_cache
+ * ---------------------------------
+ * checks the image for presence of the parasite that marks the image as member
+ * of the gap frame fetcher cache.
+ * return TRUE if the parasite was found (e.g. image is cache member)
+ */
+gboolean
+gap_frame_fetch_is_image_in_cache(gint32 image_id)
+{
+  GimpParasite  *l_parasite;
+ 
+  l_parasite = gimp_image_parasite_find(image_id, GAP_IMAGE_CACHE_PARASITE);
+
+  if(l_parasite)
+  {
+    gimp_parasite_free(l_parasite);
+    return(TRUE);  /* isCacheMember */
+  }
+  
+  return (FALSE);
+  
+}  /* end gap_frame_fetch_is_image_in_cache */
+
+
 /* -------------------------------
  * gap_frame_fetch_remove_parasite
  * -------------------------------
@@ -1117,7 +1597,6 @@ static void
 p_dump_resources_gvahand()
 {
 #ifdef GAP_ENABLE_VIDEOAPI_SUPPORT
-  t_GVA_Handle *l_gvahand;
   GapFFetchGvahandCacheElem  *gvc_ptr;
   GapFFetchGvahandCacheElem  *gvc_last;
   GapFFetchGvahandCache      *gvcache;
@@ -1192,14 +1671,14 @@ p_dump_process_resource_usage()
   if(rc == 0)
   {
     printf ("FrameFetcher sysinfo memory in bytes:\n"
-            "  memory total size:         %u\n"
-            "  memory free:               %u\n"
-            "  shared memory total size:  %u\n"
-            "  memory used by buffers:    %u\n"
-            "  swap total size:           %u\n"
-            "  swap free:                 %u\n"
-            "  high memory total size:    %u\n"
-            "  high memory free:          %u\n"
+            "  memory total size:         %lu\n"
+            "  memory free:               %lu\n"
+            "  shared memory total size:  %lu\n"
+            "  memory used by buffers:    %lu\n"
+            "  swap total size:           %lu\n"
+            "  swap free:                 %lu\n"
+            "  high memory total size:    %lu\n"
+            "  high memory free:          %lu\n"
       , (info.mem_unit * info.totalram)
       , (info.mem_unit * info.freeram)
       , (info.mem_unit * info.sharedram)
@@ -1263,20 +1742,19 @@ gap_frame_fetch_dump_resources()
 
     if(l_parasite)
     {
-      gint32 *mtime_ptr;
-      gint32 *ffetch_id_ptr;
-      gchar  *filename_ptr;
+      GapImageCacheParasitePointers para;
+      GapImageCacheParasitePointers *paraPtr;
       
-      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)];
+      paraPtr = &para;
+      p_init_GapImageCacheParasitePointers(paraPtr, l_parasite->data);
+
     
       l_number_of_cached_images++;
 
       l_cacheInfoString = g_strdup_printf("Cache member: mtime:%d ffetchId:%d %s"
-                                         ,*mtime_ptr
-                                         ,*ffetch_id_ptr
-                                         ,filename_ptr
+                                         ,*(paraPtr->mtime_ptr)
+                                         ,*(paraPtr->ffetch_id_ptr)
+                                         ,paraPtr->filename_ptr
                                          );
       
       gimp_parasite_free(l_parasite);
diff --git a/gap/gap_frame_fetcher.h b/gap/gap_frame_fetcher.h
index 1134e28..a757ca7 100644
--- a/gap/gap_frame_fetcher.h
+++ b/gap/gap_frame_fetcher.h
@@ -92,9 +92,6 @@ gap_frame_fetch_delete_list_of_duplicated_images(gint32 ffetch_user_id);
  * ----------------------------
  * returns image_id of the original cached image.
  *    RESTRICTION: the Caller must NOT not modify that image and shall not open a display for it!
- *    In case this image is duplicated, the parasite that marks an image as member of the gap frame fetcher cache
- *    must be removed (by calling procedure gap_frame_fetch_remove_parasite on the duplicate)
- *    otherwise the duplicate might be unexpectedly deleted  when the frame fetcher cache is full.
  */
 gint32
 gap_frame_fetch_orig_image(gint32 ffetch_user_id
@@ -102,6 +99,22 @@ gap_frame_fetch_orig_image(gint32 ffetch_user_id
     ,gboolean addToCache             /* enable caching */
     );
 
+/* -------------------------------
+ * gap_frame_fetch_prescaled_image
+ * -------------------------------
+ * returns image_id of the prescaled cached image.
+ * NOTE: the returned image MUST NOT not be changed by the caller
+ *
+ */
+gint32
+gap_frame_fetch_prescaled_image(gint32 ffetch_user_id
+    ,const char *filename            /* full filename of the image */
+    ,gboolean addToCache             /* enable caching on prescaled image */
+    ,gint32 prescaleWidth            /* use 0 in case prescale size is not yet known */
+    ,gint32 prescaleHeight           /* use 0 in case prescale size is not yet known */
+    ,gint32 *originalWidthPtr        /* OUT: width of the unscaled original image file */
+    ,gint32 *originalHeightPtr       /* OUT: width of the unscaled original image file */
+    );
 
 /* ----------------------------
  * gap_frame_fetch_dup_image
@@ -135,6 +148,30 @@ gap_frame_fetch_dup_video(gint32 ffetch_user_id
     ,const char *preferred_decoder
     );
 
+/* -------------------------------
+ * gap_frame_fetch_image_scale
+ * -------------------------------
+ */
+void
+gap_frame_fetch_image_scale(gint32 imageId, gint32 width, gint32 height);
+
+/* -------------------------------
+ * gap_frame_fetch_image_duplicate
+ * -------------------------------
+ */
+gint32
+gap_frame_fetch_image_duplicate(gint32 imageId);
+
+
+/* ---------------------------------
+ * gap_frame_fetch_is_image_in_cache
+ * ---------------------------------
+ * checks the image for presence of the parasite that marks the image as member
+ * of the gap frame fetcher cache.
+ * return TRUE if the parasite was found (e.g. image is cache member)
+ */
+gboolean
+gap_frame_fetch_is_image_in_cache(gint32 image_id);
 
 /* -------------------------------
  * gap_frame_fetch_remove_parasite
diff --git a/gap/gap_image.c b/gap/gap_image.c
index 90a49b7..794748a 100644
--- a/gap/gap_image.c
+++ b/gap/gap_image.c
@@ -34,6 +34,7 @@
 
 
 #include <gap_image.h>
+#include <gap_base.h>
 #include <gap_layer_copy.h>
 
 extern int gap_debug;
@@ -44,18 +45,32 @@ extern int gap_debug;
  *    delete image (with workaround to ensure that most of the
  *    allocatd memory is freed)
  * ============================================================================
+ * The workaround disables undo and scales the image down to miniumum size
+ * before calling the gimp_image_delete procedure.
+ * this way memory resources for big layers will be freed up immediate.
  */
 void
 gap_image_delete_immediate (gint32 image_id)
 {
-    if(gap_debug) printf("gap_image_delete_immediate: SCALED down to 2x2 id = %d (workaround for gimp_image-delete problem)\n", (int)image_id);
-
+  gboolean imageDeleteWorkaroundDefault = TRUE;
+  if(gap_base_get_gimprc_gboolean_value("gap-image-delete-workaround"
+         , imageDeleteWorkaroundDefault))
+  {         
+    if(gap_debug)
+    {
+      printf("gap_image_delete_immediate: SCALED down to 2x2 id = %d (workaround for gimp_image-delete problem)\n"
+              , (int)image_id
+              );
+    }
+    
     gimp_image_undo_disable(image_id);
 
-    gimp_image_scale(image_id, 2, 2);
+    gimp_image_scale_full(image_id, 2, 2, 0 /*INTERPOLATION_NONE*/);
 
     gimp_image_undo_enable(image_id); /* clear undo stack */
-    gimp_image_delete(image_id);
+  }
+
+  gimp_image_delete(image_id);
 }       /* end  gap_image_delete_immediate */
 
 
diff --git a/gap/gap_story_render_processor.c b/gap/gap_story_render_processor.c
index 0ade8ff..b9d7dd1 100644
--- a/gap/gap_story_render_processor.c
+++ b/gap/gap_story_render_processor.c
@@ -184,6 +184,8 @@ typedef struct StbMutexPool   /* mutxp */
 #define GVAHAND_HOLDER_RANK_4                4
 #define GVAHAND_HOLDER_RANK_MAX_LEVEL        5
 
+#define GAP_VIDEO_STORYBOARD_PRESCALE_ENABLE_DOWNSCALE_CHAIN "video-storyboard-prescale-enable-downscale-chain"
+
 
 extern int gap_debug;  /* 1 == print debug infos , 0 dont print debug infos */
 
@@ -446,9 +448,30 @@ static void       p_split_delace_value(gdouble delace
                       , gdouble *threshold_ptr);
 static void       p_conditional_delace_drawable(GapStbFetchData *gfd, gint32 drawable_id);
 
+static void       p_init_gfd(GapStbFetchData *gfd);
+
+static gboolean   p_is_larger_image_variant_expected(GapStbFetchData *gfdCurrent
+                      , GapStoryRenderVidHandle *vidhand
+                      , gint32 master_frame_nr
+                      , gint32 vid_width
+                      , gint32 vid_height
+                      , gint32 originalWidth
+                      , gint32 originalHeight
+                      , gint32 currentPrescaleWidth
+                      , gint32 currentPrescaleHeight
+                      );
+
+static gint32     p_prescale_image_size_handling(GapStbFetchData *gfdCurrent
+                      , GapStoryRenderVidHandle *vidhand
+                      , gint32 master_frame_nr
+                      , gint32 vid_width
+                      , gint32 vid_height
+                      );
+
 static void       p_stb_render_image_or_animimage(GapStbFetchData *gfd
                       , GapStoryRenderVidHandle *vidhand
-                      , gint32 master_frame_nr);
+                      , gint32 master_frame_nr
+                      , gint32 vid_width, gint32 vid_height);
 static gboolean   p_is_another_clip_playing_the_same_video_backwards(GapStoryRenderFrameRangeElem *frn_elem_ref);
 static void       p_check_and_open_video_handle(GapStoryRenderFrameRangeElem *frn_elem
                       , GapStoryRenderVidHandle *vidhand
@@ -489,7 +512,12 @@ static void       p_stb_render_section(GapStbFetchData *gfd
                       , gint32 master_frame_nr
                       , gint32  vid_width, gint32  vid_height
                       , const char *section_name);
-static void       p_stb_render_frame_images(GapStbFetchData *gfd, gint32 master_frame_nr);
+static gboolean    p_check_next_composite_frame_includes_same_image(GapStbFetchData *gfdCurrent
+                      , GapStoryRenderVidHandle *vidhand
+                      , gint32 master_frame_nr);
+static void       p_stb_render_frame_images(GapStbFetchData *gfd
+                      , GapStoryRenderVidHandle *vidhand
+                      , gint32 master_frame_nr, gint32 vid_width, gint32 vid_height);
 static void       p_stb_render_composite_image_postprocessing(GapStbFetchData *gfd
                       , GapStoryRenderVidHandle *vidhand
                       , gint32 master_frame_nr
@@ -5119,7 +5147,7 @@ p_transform_with_movepath_processing( gint32 comp_image_id
     && (result_height >= vid_height))
     {
       /* handle enlarge image in both dimensions scenario */
-      gimp_image_scale(l_tmp_movpath_image_id, result_width, result_height);
+      gap_frame_fetch_image_scale(l_tmp_movpath_image_id, result_width, result_height);
       offs_x = rint((result_width - vid_width) / 2.0);
       offs_y = rint((result_height - vid_height) / 2.0);
       gimp_image_crop(l_tmp_movpath_image_id, vid_width, vid_height, offs_x, offs_y);
@@ -5128,7 +5156,7 @@ p_transform_with_movepath_processing( gint32 comp_image_id
     && (result_height <= vid_height))
     {
       /* handle shrink image in both dimensions scenario */
-      gimp_image_scale(l_tmp_movpath_image_id, result_width, result_height);
+      gap_frame_fetch_image_scale(l_tmp_movpath_image_id, result_width, result_height);
       offs_x = rint((vid_width - result_width) / 2.0);
       offs_y = rint((vid_height - result_height) / 2.0);
 
@@ -5140,13 +5168,13 @@ p_transform_with_movepath_processing( gint32 comp_image_id
     {
       /* handle enlarge width but shrink height scenario */
       /* 1. enlarge width, keep same image height */
-      gimp_image_scale(l_tmp_movpath_image_id, result_width, vid_height);
+      gap_frame_fetch_image_scale(l_tmp_movpath_image_id, result_width, vid_height);
       offs_x = rint((result_width - vid_width) / 2.0);
       offs_y = 0;
       gimp_image_crop(l_tmp_movpath_image_id, vid_width, vid_height, offs_x, offs_y);
 
       /* 2. shrink height, keep same image width */
-      gimp_image_scale(l_tmp_movpath_image_id, vid_width, result_height);
+      gap_frame_fetch_image_scale(l_tmp_movpath_image_id, vid_width, result_height);
       offs_x = 0;
       offs_y = rint((vid_height - result_height) / 2.0);
 
@@ -5159,13 +5187,13 @@ p_transform_with_movepath_processing( gint32 comp_image_id
     {
       /* handle enlarge height but shrink width scenario */
       /* 1. enlarge height, keep same image width */
-      gimp_image_scale(l_tmp_movpath_image_id, vid_width, result_height);
+      gap_frame_fetch_image_scale(l_tmp_movpath_image_id, vid_width, result_height);
       offs_x = 0;
       offs_y = rint((result_height - vid_height) / 2.0);
       gimp_image_crop(l_tmp_movpath_image_id, vid_width, vid_height, offs_x, offs_y);
 
       /* 2. shrink width, keep same image height */
-      gimp_image_scale(l_tmp_movpath_image_id, result_width, vid_height);
+      gap_frame_fetch_image_scale(l_tmp_movpath_image_id, result_width, vid_height);
       offs_x = rint((vid_width - result_width) / 2.0);
       offs_y = 0;
 
@@ -5596,6 +5624,16 @@ p_transform_and_add_layer( gint32 comp_image_id
       ||  (gimp_drawable_height(l_fsel_layer_id) != calculated->visible_height) )
       {
         GAP_TIMM_START_FUNCTION(funcIdClipScale);
+        if(gap_debug)
+        {
+          printf("DEBUG: p_transform_and_add_layer scaling floating sel layer from (%dx%d) to ==> (%dx%d)\n"
+                            , (int)gimp_drawable_width(l_fsel_layer_id)
+                            , (int)gimp_drawable_height(l_fsel_layer_id)
+                            , (int)calculated->visible_width
+                            , (int)calculated->visible_height
+                            );
+
+        }
 
         gimp_layer_scale(l_fsel_layer_id, calculated->visible_width, calculated->visible_height
                       , FALSE  /* FALSE: centered at image TRUE: centered local on layer */
@@ -6544,10 +6582,460 @@ p_conditional_delace_drawable(GapStbFetchData *gfd, gint32 drawable_id)
 #endif
 }  /* end p_conditional_delace_drawable */
 
+/* ------------
+ * p_init_gfd
+ * ------------
+ */
+static void
+p_init_gfd(GapStbFetchData *gfd)
+{
+  gfd->localframe_tween_rest = 0.0;
+  gfd->comp_image_id   = -1;
+  gfd->tmp_image_id    = -1;
+  gfd->layer_id        = -1;
+  gfd->gapStoryFetchResult = NULL;
+  gfd->isRgb888Result      = FALSE;
+}  /* end p_init_gfd */
+
+
+/* -------------------------------------------------------------------
+ * p_is_larger_image_variant_expected
+ * -------------------------------------------------------------------
+ */
+static gboolean
+p_is_larger_image_variant_expected(GapStbFetchData *gfdCurrent
+  , GapStoryRenderVidHandle *vidhand
+  , gint32 master_frame_nr
+  , gint32 vid_width
+  , gint32 vid_height
+  , gint32 originalWidth
+  , gint32 originalHeight
+  , gint32 currentPrescaleWidth
+  , gint32 currentPrescaleHeight
+  )
+{
+  GapStbFetchData gapStbFetchData;
+  GapStbFetchData *gfd;
+  GapStoryCalcAttr  calculate_attributes;
+  GapStoryCalcAttr  *calculated;
+  gint32 lookForwardMasterFframeNr;
+  gboolean foundLargerVariant;
+
+
+  gfd = &gapStbFetchData;
+  p_init_gfd(gfd);
+  calculated = &calculate_attributes;
+  
+  foundLargerVariant = FALSE;
+  
+  /* check upto 500 further frames for usage of the same image
+   * to findout maximum required prescale size in the near rendering future..
+   * (note that in practice it is typical that one of the break conditions
+   * occurs much earlier before the 500 checks are done)
+   */
+  for(lookForwardMasterFframeNr = master_frame_nr + 1; 
+      lookForwardMasterFframeNr < master_frame_nr + 500;
+      lookForwardMasterFframeNr++)
+  {
+    gint32 l_track;
+    gboolean nextCompositeFrameIncludesSameImage;
+
+    nextCompositeFrameIncludesSameImage = FALSE;
+
+    if(gap_debug)
+    {
+      printf("  o lookForwardMasterFframeNr:%d\n", lookForwardMasterFframeNr);
+    }
+    
+    for(l_track = vidhand->maxVidTrack; l_track >= vidhand->minVidTrack; l_track--)
+    {
+      gfd->framename = p_fetch_framename(vidhand->frn_list
+                 , lookForwardMasterFframeNr /* starts at 1 */
+                 , l_track
+                 , gfd
+                 );
+      if (gfd->framename != NULL)
+      {
+        if((gfd->frn_type == GAP_FRN_ANIMIMAGE)
+        || (gfd->frn_type == GAP_FRN_IMAGE)
+        || (gfd->frn_type == GAP_FRN_FRAMES))
+        {
+          if (strcmp(gfd->framename, gfdCurrent->framename) == 0)
+          {
+            nextCompositeFrameIncludesSameImage = TRUE;
+            gap_story_file_calculate_render_attributes(&calculate_attributes
+                  , vid_width
+                  , vid_height
+                  , vid_width
+                  , vid_height
+                  , originalWidth
+                  , originalHeight
+                  , gfd->keep_proportions
+                  , gfd->fit_width
+                  , gfd->fit_height
+                  , gfd->rotate
+                  , gfd->opacity
+                  , gfd->scale_x
+                  , gfd->scale_y
+                  , gfd->move_x
+                  , gfd->move_y
+                  );
+             if ((calculated->width > currentPrescaleWidth)
+             ||  (calculated->height > currentPrescaleHeight))
+             {
+               foundLargerVariant = TRUE;
+             }
+
+  
+          }
+        }
+
+        g_free(gfd->framename);
+      }
+    }
+
+    if ((nextCompositeFrameIncludesSameImage != TRUE)
+    ||  (foundLargerVariant == TRUE))
+    {
+      break;
+    }
+  }
+
+
+  return (foundLargerVariant);
+
+}  /* end p_is_larger_image_variant_expected */
 
 
 
 /* -------------------------------------------------------------------
+ * p_prescale_image_size_handling
+ * -------------------------------------------------------------------
+ * fetch a single image or animimage at prescaled size.
+ * this procedure checks some of the frames to be rendered next
+ * for usage of the same image and calculates the prescale size as the maximum
+ * refered size, except the size grows more than 150 percent.
+ * this calculated prescale size is used to (down) scale the cached image.
+ * this shall speedup rendering of large images
+ * when refered multiple times at video size (that is typically much smaller)
+ */
+static gint32
+p_prescale_image_size_handling(GapStbFetchData *gfdCurrent
+  , GapStoryRenderVidHandle *vidhand
+  , gint32 master_frame_nr
+  , gint32 vid_width
+  , gint32 vid_height
+  )
+{
+  gint32 l_fetched_image_id;
+  GapStbFetchData gapStbFetchData;
+  GapStbFetchData *gfd;
+  GapStoryCalcAttr  calculate_attributes;
+  GapStoryCalcAttr  *calculated;
+  gint32 currentPrescaleWidth;
+  gint32 currentPrescaleHeight;
+  gint32 maxPrescaleWidth;
+  gint32 maxPrescaleHeight;
+  gint32 originalWidth;
+  gint32 originalHeight;
+  gint32 lookForwardMasterFframeNr;
+  gboolean foundSmallerVariant;
+  gboolean foundTooLargeVariant;  /* larger than 150% */
+
+  gfd = gfdCurrent;
+  l_fetched_image_id = gap_frame_fetch_prescaled_image(vidhand->ffetch_user_id
+                        , gfdCurrent->framename            /* full filename of the image */
+                        , TRUE /*  enable caching */
+                        ,0     /* prescaleWidth is not yet known */
+                        ,0     /* prescaleHeight is not yet known */
+                        ,&originalWidth
+                        ,&originalHeight
+                       );
+  if (l_fetched_image_id < 0)
+  {
+    /* failed to fetch image */
+    return (l_fetched_image_id);
+  }
+  
+  calculated = &calculate_attributes;
+
+  /* calculate scaling, offsets and opacity  according to current attributes
+   */
+  gap_story_file_calculate_render_attributes(&calculate_attributes
+      , vid_width
+      , vid_height
+      , vid_width
+      , vid_height
+      , originalWidth
+      , originalHeight
+      , gfd->keep_proportions
+      , gfd->fit_width
+      , gfd->fit_height
+      , gfd->rotate
+      , gfd->opacity
+      , gfd->scale_x
+      , gfd->scale_y
+      , gfd->move_x
+      , gfd->move_y
+      );
+    
+  currentPrescaleWidth = calculated->width;
+  currentPrescaleHeight = calculated->height;
+  maxPrescaleWidth = currentPrescaleWidth;
+  maxPrescaleHeight = currentPrescaleHeight;
+
+  if(gap_debug)
+  {
+    printf("p_prescale_image_size_handling master_frame_nr:%d\n"
+           "original:(%dx%d) currentPrescale:(%dx%d) imgId:%d actualSIZE: (%dx%d) filename:%s\n"
+      , (int)master_frame_nr
+      , (int)originalWidth
+      , (int)originalHeight
+      , (int)currentPrescaleWidth
+      , (int)currentPrescaleHeight
+      , (int)l_fetched_image_id
+      , (int)gimp_image_width(l_fetched_image_id)
+      , (int)gimp_image_height(l_fetched_image_id)
+      , gfdCurrent->framename
+      );
+  }
+
+  if ((gimp_image_width(l_fetched_image_id) == currentPrescaleWidth)
+  &&  (gimp_image_height(l_fetched_image_id) == currentPrescaleHeight))
+  {
+    /* wanted prescale size for rendering current frame
+     * is same as actual size of the cached image
+     * (no need for further checks so far..)
+     */
+    if(gap_debug)
+    {
+      printf("p_prescale_image_size_handling master_frame_nr:%d EXACT SAME SIZE filename:%s\n"
+      , (int)master_frame_nr
+      , gfdCurrent->framename
+      );
+    }
+    return (l_fetched_image_id);
+  }
+  
+  if ((gimp_image_width(l_fetched_image_id) < originalWidth)
+  ||  (gimp_image_height(l_fetched_image_id) < originalHeight))
+  {
+     if ((gimp_image_width(l_fetched_image_id) >= currentPrescaleWidth)
+     &&  (gimp_image_height(l_fetched_image_id) >= currentPrescaleHeight))
+     {
+       /* fetched image is already down scaled.
+        * optional check for further (chained) downscale 
+        */
+       gboolean  foundLargerVariant = FALSE;
+       gboolean  prescaleEnabledDownscaleChainDefault = TRUE;
+       
+       if(gap_base_get_gimprc_gboolean_value(GAP_VIDEO_STORYBOARD_PRESCALE_ENABLE_DOWNSCALE_CHAIN
+         , prescaleEnabledDownscaleChainDefault))
+       {
+         foundLargerVariant = p_is_larger_image_variant_expected(gfdCurrent
+           , vidhand
+           , master_frame_nr
+           , vid_width
+           , vid_height
+           , originalWidth
+           , originalHeight
+           , currentPrescaleWidth
+           , currentPrescaleHeight
+          );
+       }
+       if(foundLargerVariant != TRUE)
+       {
+         /* there is no more reference to the same image
+          * that requires larger variant than currentPrescale size in near rendering future)
+          * ----
+          * this additional scale down step by step results in speed up downscale rendering sequences
+          * because downscale rendering sequences example would look like this:
+          *     frame 000001 800x600 --> 750x550
+          *     frame 000002 750x550 --> 700x500
+          *     frame 000003 700x500 --> 650x450
+          * this behaviour (chained muliple downscales) could also result in some quality loss
+          * (and can be disabled via prescaleEnableChainedDownscale)
+          */
+         if(gap_debug)
+         {
+           printf("p_prescale_image_size_handling master_frame_nr:%d\n"
+                " CHAINED DOWNSCALE from:(%dx%d) --> to:(%dx%d) filename:%s\n"
+           , (int)master_frame_nr
+           , (int)gimp_image_width(l_fetched_image_id)
+           , (int)gimp_image_height(l_fetched_image_id)
+           , (int)currentPrescaleWidth
+           , (int)currentPrescaleHeight
+           , gfdCurrent->framename
+           );
+         }
+         gimp_image_undo_disable(l_fetched_image_id);
+         gap_frame_fetch_image_scale(l_fetched_image_id, currentPrescaleWidth, currentPrescaleHeight);
+       }
+       else
+       {
+         /* without the optional chek the same downscale rendering sequences example looks like this:
+          *     frame 000001 800x600-->750x550
+          *     frame 000002 800x600-->700x500
+          *     frame 000003 800x600-->650x450
+          */
+         if(gap_debug)
+         {
+           printf("p_prescale_image_size_handling master_frame_nr:%d\n"
+                " ALREADY DOWNSCALED filename:%s\n"
+           , (int)master_frame_nr
+           , gfdCurrent->framename
+           );
+         }
+       }
+       
+       return (l_fetched_image_id);
+     }
+  }
+
+
+  gfd = &gapStbFetchData;
+  p_init_gfd(gfd);
+  
+  foundSmallerVariant = FALSE;
+  foundTooLargeVariant = FALSE;
+  
+  /* check upto 500 further frames for usage of the same image
+   * to findout maximum required prescale size in the near rendering future..
+   * (note that in practice it is typical that one of the break conditions
+   * occurs much earlier before the 500 checks are done)
+   */
+  for(lookForwardMasterFframeNr = master_frame_nr + 1; 
+      lookForwardMasterFframeNr < master_frame_nr + 500;
+      lookForwardMasterFframeNr++)
+  {
+    gint32 l_track;
+    gboolean nextCompositeFrameIncludesSameImage;
+    gboolean nextCompositeFrameIncludesSameImageAtSamePrescaleSize;
+
+    if(gap_debug)
+    {
+      printf("  lookForwardMasterFframeNr:%d\n", lookForwardMasterFframeNr);
+    }
+    
+    nextCompositeFrameIncludesSameImage = FALSE;
+    nextCompositeFrameIncludesSameImageAtSamePrescaleSize = FALSE;
+    for(l_track = vidhand->maxVidTrack; l_track >= vidhand->minVidTrack; l_track--)
+    {
+      gfd->framename = p_fetch_framename(vidhand->frn_list
+                 , lookForwardMasterFframeNr /* starts at 1 */
+                 , l_track
+                 , gfd
+                 );
+      if (gfd->framename != NULL)
+      {
+        if((gfd->frn_type == GAP_FRN_ANIMIMAGE)
+        || (gfd->frn_type == GAP_FRN_IMAGE)
+        || (gfd->frn_type == GAP_FRN_FRAMES))
+        {
+          if (strcmp(gfd->framename, gfdCurrent->framename) == 0)
+          {
+            nextCompositeFrameIncludesSameImage = TRUE;
+            
+            gap_story_file_calculate_render_attributes(&calculate_attributes
+                  , vid_width
+                  , vid_height
+                  , vid_width
+                  , vid_height
+                  , originalWidth
+                  , originalHeight
+                  , gfd->keep_proportions
+                  , gfd->fit_width
+                  , gfd->fit_height
+                  , gfd->rotate
+                  , gfd->opacity
+                  , gfd->scale_x
+                  , gfd->scale_y
+                  , gfd->move_x
+                  , gfd->move_y
+                  );
+             if ((calculated->width == maxPrescaleWidth)
+             &&  (calculated->height == maxPrescaleHeight))
+             {
+               nextCompositeFrameIncludesSameImageAtSamePrescaleSize = TRUE;
+             }
+             else
+             {
+               nextCompositeFrameIncludesSameImageAtSamePrescaleSize = FALSE;
+             }
+
+
+             if ((calculated->width < currentPrescaleWidth)
+             &&  (calculated->height < currentPrescaleHeight))
+             {
+               foundSmallerVariant = TRUE;
+             }
+             
+             if (calculated->width > originalWidth)
+             {
+               foundTooLargeVariant = TRUE;
+             }
+             else
+             {
+               if (calculated->width > maxPrescaleWidth)
+               {
+                 maxPrescaleWidth = calculated->width;
+                 maxPrescaleHeight = calculated->height;
+               }
+             }
+
+  
+          }
+        }
+
+        g_free(gfd->framename);
+      }
+    }
+    if ((nextCompositeFrameIncludesSameImage != TRUE)
+    ||  (nextCompositeFrameIncludesSameImageAtSamePrescaleSize == TRUE)
+    ||  (foundSmallerVariant == TRUE)
+    ||  (foundTooLargeVariant == TRUE))
+    {
+      /* stop looking forward when:
+       * o) next frame does not refere to same image
+       * o) or refers to same image at exact same prescale size
+       * o) or refers to smaller representation 
+       *    (assume zoom out where usage of larger variant is NOT expected in near future)
+       * o) or refers to much larger variant > 150 %
+       */
+      break;
+    }
+  }
+
+  /* the 2nd fetch call (with prescale size != 0) triggers prescaling */ 
+  l_fetched_image_id = gap_frame_fetch_prescaled_image(vidhand->ffetch_user_id
+                        , gfdCurrent->framename            /* full filename of the image */
+                        , TRUE /*  enable caching */
+                        , maxPrescaleWidth
+                        , maxPrescaleHeight
+                        , &originalWidth
+                        , &originalHeight
+                       );
+  if(gap_debug)
+  {
+    printf("p_prescale_image_size_handling master_frame_nr:%d lookForwardMasterFframeNr:%d\n"
+           "maxPrescaleWidth:%d maxPrescaleHeight:%d imgId:%d (%dx%d) filename:%s\n"
+      , (int)master_frame_nr
+      , (int)lookForwardMasterFframeNr
+      , (int)maxPrescaleWidth
+      , (int)maxPrescaleHeight
+      , (int)l_fetched_image_id
+      , (int)gimp_image_width(l_fetched_image_id)
+      , (int)gimp_image_height(l_fetched_image_id)
+      , gfdCurrent->framename
+      );
+  }
+
+  return (l_fetched_image_id);
+
+}  /* end p_prescale_image_size_handling */
+
+
+/* -------------------------------------------------------------------
  * p_stb_render_image_or_animimage (GAP_FRN_ANIMIMAGE or GAP_FRN_IMAGE
  * -------------------------------------------------------------------
  * fetch a single image or animimage
@@ -6555,21 +7043,62 @@ p_conditional_delace_drawable(GapStbFetchData *gfd, gint32 drawable_id)
 static void
 p_stb_render_image_or_animimage(GapStbFetchData *gfd
   , GapStoryRenderVidHandle *vidhand
-  , gint32 master_frame_nr)
+  , gint32 master_frame_nr, gint32 vid_width, gint32 vid_height)
 {
   gint32        l_orig_image_id;
   gint          l_nlayers;
   gint32       *l_layers_list;
 
-  l_orig_image_id = gap_frame_fetch_orig_image(vidhand->ffetch_user_id
+
+  if(gap_debug)
+  {
+    printf("p_stb_render_image_or_animimage START master_frame_nr:%d\n"
+      ,(int)master_frame_nr
+       );
+  }
+
+
+  /* filtermacro shall be applied at original image size
+   * therefore disable prescale handling when filtermacro or
+   * complex movepath processing is present
+   */
+  if ((gfd->trak_filtermacro_file == NULL)
+  &&  (gfd->movepath_file_xml == NULL))
+  {
+    if(gap_debug)
+    {
+      printf("CALLING p_prescale_image_size_handling master_frame_nr:%d\n"
+         ,(int)master_frame_nr
+         );
+    }
+    /* prescale handling */
+    l_orig_image_id = 
+       p_prescale_image_size_handling(gfd, vidhand, master_frame_nr, vid_width, vid_height);
+  }
+  else 
+  {
+    if(gap_debug)
+    {
+      printf("CALLING gap_frame_fetch_orig_image master_frame_nr:%d\n"
+         ,(int)master_frame_nr
+         );
+    }
+    l_orig_image_id = gap_frame_fetch_orig_image(vidhand->ffetch_user_id
                         , gfd->framename            /* full filename of the image */
                         , TRUE /*  enable caching */
                        );
+  }
+
+  if (l_orig_image_id < 0)
+  {
+    printf("Error fetching image: %s", gfd->framename);
+    return;
+  }
 
   gimp_selection_none(l_orig_image_id);
   if(gfd->frn_type == GAP_FRN_IMAGE)
   {
-    gfd->tmp_image_id = gimp_image_duplicate(l_orig_image_id);
+    gfd->tmp_image_id = gap_frame_fetch_image_duplicate(l_orig_image_id);
     gfd->layer_id = p_prepare_RGB_image(gfd->tmp_image_id);
     gap_frame_fetch_remove_parasite(gfd->tmp_image_id);
     if(gap_debug)
@@ -7790,6 +8319,55 @@ p_stb_render_section(GapStbFetchData *gfd
 }  /* end p_stb_render_section */
 
 
+/* -------------------------------------------------------------------
+ * p_check_next_composite_frame_includes_same_image
+ * -------------------------------------------------------------------
+ * return true in case the next composite frame includes the same image.
+ */
+static gboolean
+p_check_next_composite_frame_includes_same_image(GapStbFetchData *gfdCurrent
+  , GapStoryRenderVidHandle *vidhand
+  , gint32 master_frame_nr)
+{
+  gint32 l_track;
+  gboolean nextCompositeFrameIncludesSameImage;
+  GapStbFetchData gapStbFetchData;
+  GapStbFetchData *gfd;
+  
+  nextCompositeFrameIncludesSameImage = FALSE;
+  
+  gfd = &gapStbFetchData;
+  p_init_gfd(gfd);
+  
+  for(l_track = vidhand->maxVidTrack; l_track >= vidhand->minVidTrack; l_track--)
+  {
+    gfd->framename = p_fetch_framename(vidhand->frn_list
+               , master_frame_nr + 1
+               , l_track
+               , gfd
+               );
+    if (gfd->framename != NULL)
+    {
+      if((gfd->frn_type == GAP_FRN_ANIMIMAGE)
+      || (gfd->frn_type == GAP_FRN_IMAGE)
+      || (gfd->frn_type == GAP_FRN_FRAMES))
+      {
+        if (strcmp(gfd->framename, gfdCurrent->framename) == 0)
+        {
+          nextCompositeFrameIncludesSameImage = TRUE;
+        }
+      }
+
+      g_free(gfd->framename);
+    }
+  }
+    
+  return (nextCompositeFrameIncludesSameImage);
+}
+
+
+
+
 /* -------------------------------------------
  * p_stb_render_frame_images (GAP_FRN_FRAMES)
  * -------------------------------------------
@@ -7798,8 +8376,19 @@ p_stb_render_section(GapStbFetchData *gfd
  *  and includes path name, numberpart and extension)
  */
 static void
-p_stb_render_frame_images(GapStbFetchData *gfd, gint32 master_frame_nr)
+p_stb_render_frame_images(GapStbFetchData *gfd, GapStoryRenderVidHandle *vidhand, gint32 master_frame_nr
+  , gint32 vid_width, gint32 vid_height)
 {
+  gboolean nextCompositeFrameIncludesSameImage;
+  gboolean enableCaching;
+  gint32   l_fetched_image_id;
+  gint32   originalWidth;
+  gint32   originalHeight;
+  gint32   prescaleWidth;
+  gint32   prescaleHeight;
+  GapStoryCalcAttr  calculate_attributes;
+  GapStoryCalcAttr  *calculated;
+  
   if(gap_debug)
   {
     printf("FRAME fetch gfd->framename: %s\n    ===> master:%d  from: %d to: %d\n"
@@ -7809,9 +8398,117 @@ p_stb_render_frame_images(GapStbFetchData *gfd, gint32 master_frame_nr)
       ,(int)gfd->frn_elem->frame_to
       );
   }
-  gfd->tmp_image_id = gap_lib_load_image(gfd->framename);
+
+  nextCompositeFrameIncludesSameImage =
+     p_check_next_composite_frame_includes_same_image(gfd, vidhand, master_frame_nr);
+
+  enableCaching = nextCompositeFrameIncludesSameImage;
+
+  l_fetched_image_id = gap_frame_fetch_prescaled_image(vidhand->ffetch_user_id
+                        , gfd->framename            /* full filename of the image */
+                        , enableCaching
+                        ,0     /* prescaleWidth is not yet known */
+                        ,0     /* prescaleHeight is not yet known */
+                        ,&originalWidth
+                        ,&originalHeight
+                       );
+  calculated = &calculate_attributes;
+
+  /* calculate scaling, offsets and opacity  according to current attributes
+   */
+  gap_story_file_calculate_render_attributes(&calculate_attributes
+      , vid_width
+      , vid_height
+      , vid_width
+      , vid_height
+      , originalWidth
+      , originalHeight
+      , gfd->keep_proportions
+      , gfd->fit_width
+      , gfd->fit_height
+      , gfd->rotate
+      , gfd->opacity
+      , gfd->scale_x
+      , gfd->scale_y
+      , gfd->move_x
+      , gfd->move_y
+      );
+    
+  prescaleWidth = calculated->width;
+  prescaleHeight = calculated->height;
+
+  if (gap_frame_fetch_is_image_in_cache(l_fetched_image_id))
+  {
+    gboolean fetchedImageUsable;
+    
+    fetchedImageUsable = FALSE;
+    if ((gimp_image_width(l_fetched_image_id) >= prescaleWidth)
+    &&  (gimp_image_height(l_fetched_image_id) >= prescaleHeight))
+    {
+      fetchedImageUsable = TRUE;
+    }
+    else
+    {
+      if ((gimp_image_width(l_fetched_image_id) == originalWidth)
+      &&  (gimp_image_height(l_fetched_image_id) == originalHeight))
+      {
+        fetchedImageUsable = TRUE;
+      }
+    }
+    
+    
+    if(fetchedImageUsable == TRUE)
+    {
+      if(gap_debug)
+      {
+        printf("p_stb_render_frame_images DUP cached image (%dx%d) at master_frame_nr:%d %s\n"
+           ,(int)gimp_image_width(l_fetched_image_id)
+           ,(int)gimp_image_height(l_fetched_image_id)
+           ,(int)master_frame_nr
+           , gfd->framename
+           );
+      }
+      gfd->tmp_image_id = gap_frame_fetch_image_duplicate(l_fetched_image_id);
+      gap_frame_fetch_remove_parasite(gfd->tmp_image_id);
+    }
+    else
+    {
+      if(gap_debug)
+      {
+        printf("p_stb_render_frame_images RE-LOAD cached image (%dx%d) too small at master_frame_nr:%d %s\n"
+           ,(int)gimp_image_width(l_fetched_image_id)
+           ,(int)gimp_image_height(l_fetched_image_id)
+           ,(int)master_frame_nr
+           , gfd->framename
+           );
+      }
+      /* the cached image is downscaled and smaller than required size.
+       * in this case load again from file (at original size to ensure 
+       * best possible render quality)
+       */
+      gfd->tmp_image_id = gap_lib_load_image(gfd->framename);
+    }
+  }
+  else
+  {
+    if(gap_debug)
+    {
+      printf("p_stb_render_frame_images UNCACHED fetched image (%dx%d) at master_frame_nr:%d %s\n"
+         ,(int)gimp_image_width(l_fetched_image_id)
+         ,(int)gimp_image_height(l_fetched_image_id)
+         ,(int)master_frame_nr
+         , gfd->framename
+         );
+    }
+    /* use fetched image (that is not member of the cache
+     * directly without making a copy
+     */
+    gfd->tmp_image_id = l_fetched_image_id;
+  }
+
 
 }  /* end p_stb_render_frame_images */
+  
 
 /* -------------------------------------------
  * p_stb_render_composite_image_postprocessing
@@ -7912,7 +8609,7 @@ p_stb_render_composite_image_postprocessing(GapStbFetchData *gfd
   {
      if(gap_debug) printf("DEBUG: p_stb_render_composite_image_postprocessing: scaling tmp image\n");
 
-     gimp_image_scale(gfd->comp_image_id, vid_width, vid_height);
+     gap_frame_fetch_image_scale(gfd->comp_image_id, vid_width, vid_height);
   }
 
   /* check again for layerstack (macro could have add more layers)
@@ -8331,7 +9028,7 @@ p_do_insert_alpha_processing(GapStbFetchData *gfd
        {
          printf("DEBUG: p_do_insert_alpha_processing scaling alpha image\n");
        }
-       gimp_image_scale(gfd->comp_image_id, vid_width, vid_height);
+       gap_frame_fetch_image_scale(gfd->comp_image_id, vid_width, vid_height);
     }
 
     if(! gimp_drawable_has_alpha(gfd->layer_id))
@@ -8641,12 +9338,8 @@ p_story_render_fetch_composite_image_private(GapStoryRenderVidHandle *vidhand
 
 
   gfd = &gapStbFetchData;
-  gfd->localframe_tween_rest = 0.0;
-  gfd->comp_image_id   = -1;
-  gfd->tmp_image_id    = -1;
-  gfd->layer_id        = -1;
-  gfd->gapStoryFetchResult = NULL;
-  gfd->isRgb888Result      = FALSE;
+  p_init_gfd(gfd);
+  
   *layer_id         = -1;
 
 
@@ -8752,7 +9445,7 @@ p_story_render_fetch_composite_image_private(GapStoryRenderVidHandle *vidhand
            if((gfd->frn_type == GAP_FRN_ANIMIMAGE)
            || (gfd->frn_type == GAP_FRN_IMAGE))
            {
-             p_stb_render_image_or_animimage(gfd, vidhand, master_frame_nr);
+             p_stb_render_image_or_animimage(gfd, vidhand, master_frame_nr, vid_width, vid_height);
            }
            else
            {
@@ -8769,7 +9462,7 @@ p_story_render_fetch_composite_image_private(GapStoryRenderVidHandle *vidhand
                else
                {
                  /* GAP_FRN_FRAMES */
-                 p_stb_render_frame_images(gfd, master_frame_nr);
+                 p_stb_render_frame_images(gfd, vidhand, master_frame_nr, vid_width, vid_height);
                }
              }
            }
@@ -8852,7 +9545,7 @@ p_story_render_fetch_composite_image_private(GapStoryRenderVidHandle *vidhand
               {
                 printf("DEBUG: p_story_render_fetch_composite_image_private scaling composite image\n");
               }
-              gimp_image_scale(gfd->comp_image_id, vid_width, vid_height);
+              gap_frame_fetch_image_scale(gfd->comp_image_id, vid_width, vid_height);
 
               GAP_TIMM_STOP_FUNCTION(funcIdDirectScale);
            }



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]