Start of image cache implementation... breaks image functionality.
[rodin/chimara.git] / libchimara / graphics.c
index 48b3a12dfd4d18550e61ca54855dd1838b9b16e0..13048f9aa41587f81af0520c6307bcf8c0787e43 100644 (file)
@@ -6,44 +6,47 @@
 
 extern GPrivate *glk_data_key;
 void on_size_prepared(GdkPixbufLoader *loader, gint width, gint height, struct image_info *info);
+void on_pixbuf_closed(GdkPixbufLoader *loader, gpointer data);
 
+static gboolean image_loaded;
 static gboolean size_determined;
 
-glui32
-glk_image_get_info(glui32 image, glui32 *width, glui32 *height)
+static struct image_info*
+load_image_in_cache(glui32 image, gint width, gint height)
 {
        ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
-       giblorb_result_t res;
        giblorb_err_t blorb_error = 0;
+       giblorb_result_t resource;
        GError *pixbuf_error = NULL;
-       struct image_info *info = g_new0(struct image_info, 1);
-       info->resource_number = image;
        guchar *buffer;
 
-       //printf("glk_image_get_info(%d)\n", image);
-
        /* Lookup the proper resource */
-       blorb_error = giblorb_load_resource(glk_data->resource_map, giblorb_method_FilePos, &res, giblorb_ID_Pict, image);
+       blorb_error = giblorb_load_resource(glk_data->resource_map, giblorb_method_FilePos, &resource, giblorb_ID_Pict, image);
        if(blorb_error != giblorb_err_None) {
                WARNING_S( "Error loading resource", giblorb_get_error_message(blorb_error) );
-               return FALSE;
+               return NULL;
        }
 
-       if(width == NULL && height == NULL) {
-               /* No size requested, don't bother loading the image */
-               giblorb_unload_chunk(glk_data->resource_map, image);
-               return TRUE;
-       }
+       struct image_info *info = g_new0(struct image_info, 1);
+       info->resource_number = image;
 
        /* Load the resource */
        GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
        g_signal_connect( loader, "size-prepared", G_CALLBACK(on_size_prepared), info ); 
-       glk_stream_set_position(glk_data->resource_file, res.data.startpos, seekmode_Start);
+       g_signal_connect( loader, "closed", G_CALLBACK(on_pixbuf_closed), NULL ); 
+
+       /* Scale image if necessary */
+       if(width > 0 && height > 0) {
+               gdk_pixbuf_loader_set_size(loader, width, height);
+               info->scaled = TRUE;
+       }
+
+       glk_stream_set_position(glk_data->resource_file, resource.data.startpos, seekmode_Start);
        buffer = g_malloc( BUFFER_SIZE * sizeof(guchar) );
 
        guint32 total_read = 0;
-       size_determined = FALSE;
-       while(total_read < res.length && !size_determined) {
+       image_loaded = FALSE;
+       while(total_read < resource.length && !image_loaded) {
                guint32 num_read = glk_get_buffer_stream(glk_data->resource_file, (char *) buffer, BUFFER_SIZE);
 
                if( !gdk_pixbuf_loader_write(loader, buffer, MIN(BUFFER_SIZE, num_read), &pixbuf_error) ) {
@@ -51,118 +54,232 @@ glk_image_get_info(glui32 image, glui32 *width, glui32 *height)
                        giblorb_unload_chunk(glk_data->resource_map, image);
                        gdk_pixbuf_loader_close(loader, &pixbuf_error);
                        g_free(buffer);
-                       return FALSE;
+                       return NULL;
                }
 
                total_read += num_read;
        }
-       giblorb_unload_chunk(glk_data->resource_map, image);
        gdk_pixbuf_loader_close(loader, &pixbuf_error);
+       giblorb_unload_chunk(glk_data->resource_map, resource.chunknum);
        g_free(buffer);
 
-       /* Determine the image dimensions */
-       if(!size_determined) {
-               WARNING("Cannot read image size: file data trimmed?");
-               g_free(info);
-               return FALSE;
+       /* Wait for the PixbufLoader to finish loading the image */
+       g_mutex_lock(glk_data->resource_lock);
+       while(!image_loaded) {
+               g_cond_wait(glk_data->resource_loaded, glk_data->resource_lock);
        }
-
-       if(width != NULL)
-               *width = info->width;
-       if(height != NULL)
-               *height =info->height;
-       g_free(info);
-
-       //printf("size loaded: %d x %d\n", (int) *width, (int) *height);
-
-       return TRUE;
+       g_mutex_unlock(glk_data->resource_lock);
+
+       /* Store the image in the cache */
+       if( g_slist_length(glk_data->image_cache) >= IMAGE_CACHE_MAX_NUM ) {
+               printf("Cache size exceeded\n");
+               struct image_info *head = (struct image_info*) glk_data->image_cache->data;
+               gdk_pixbuf_unref(head->pixbuf);
+               g_free(head);
+               glk_data->image_cache = g_slist_remove_link(glk_data->image_cache, glk_data->image_cache);
+       }
+       printf("Loading pixbuf\n");
+       info->pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
+       info->width = gdk_pixbuf_get_width(info->pixbuf);
+       info->height = gdk_pixbuf_get_height(info->pixbuf);
+       printf("Caching pixbuf\n");
+       glk_data->image_cache = g_slist_prepend(glk_data->image_cache, info);
+
+       g_object_unref(loader);
+       return info;
 }
 
 void
 on_size_prepared(GdkPixbufLoader *loader, gint width, gint height, struct image_info *info)
 {
+       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+
+       g_mutex_lock(glk_data->resource_lock);
        info->width = width;
        info->height = height;
        size_determined = TRUE;
+       g_cond_broadcast(glk_data->resource_info_available);
+       g_mutex_unlock(glk_data->resource_lock);
+}
+
+void
+on_pixbuf_closed(GdkPixbufLoader *loader, gpointer data)
+{
+       gdk_threads_enter();
+
+       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+
+       g_mutex_lock(glk_data->resource_lock);
+       image_loaded = TRUE;
+       g_cond_broadcast(glk_data->resource_loaded);
+       g_mutex_unlock(glk_data->resource_lock);
+
+       gdk_threads_leave();
 }
 
+
 void
-on_graphics_size_allocate(GtkWidget *widget, GtkAllocation *allocation, winid_t *win)
+clear_image_cache(struct image_info *data, gpointer user_data)
+{
+       gdk_pixbuf_unref(data->pixbuf);
+       g_free(data);
+}
+
+static struct image_info*
+image_cache_find(struct image_info* to_find)
+{
+       printf("Finding image %d\n", to_find->resource_number);
+       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+       GSList *link = glk_data->image_cache;
+
+       /* Empty cache */
+       if(link == NULL) {
+               printf("Cache is empty\n");
+               return NULL;
+       }
+
+       /* Iterate over the cache to find the correct image and size */
+       do {
+               struct image_info *info = (struct image_info*) link->data;
+               printf("Examining cache entry %d\n", info->resource_number);
+               if(info->resource_number == to_find->resource_number) {
+                       /* Check size: are we looking for a scaled version or the original one? */
+                       if(to_find->scaled) {
+                               if(info->width >= to_find->width && info->height >= to_find->height) {
+                                       return info; /* Found a good enough match */
+                               }
+                       } else {
+                               if(!info->scaled) {
+                                       printf("Cache hit\n");
+                                       return info; /* Found a match */
+                               }
+                       }
+               }
+       } while( (link = g_slist_next(link)) );
+
+       return NULL; /* No match found */
+}
+
+glui32
+glk_image_get_info(glui32 image, glui32 *width, glui32 *height)
 {
-       printf("size allocated: %dx%d\n", allocation->width, allocation->height);
+       printf("get_info(%d)\n", image);
+       struct image_info *to_find = g_new0(struct image_info, 1);
+       struct image_info *found;
+       to_find->resource_number = image;
+       to_find->scaled = FALSE; /* we want the original image size */
+
+       if( !(found = image_cache_find(to_find)) ) {
+               printf("Cache miss for %d\n", image);
+               found = load_image_in_cache(image, 0, 0);
+               if(found == NULL)
+                       return FALSE;
+       } else {
+               printf("Cache hit for %d\n", image);
+       }
+
+       if(width != NULL)
+               *width = found->width;
+       if(width != NULL)
+               *height = found->height;
+       return TRUE;
 }
 
 glui32
-glk_image_draw(winid_t win, glui32 image, glsi32 val1, glsi32 val2) {
+glk_image_draw(winid_t win, glui32 image, glsi32 val1, glsi32 val2)
+{
+       printf("image_draw(%d)\n", image);
        VALID_WINDOW(win, return FALSE);
        g_return_val_if_fail(win->type == wintype_Graphics, FALSE);
-       printf("Drawing image %d\n", (int)image);
 
-       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
-       giblorb_result_t res;
-       giblorb_err_t blorb_error = 0;
-       GError *pixbuf_error = NULL;
-       struct image_info *info = g_new0(struct image_info, 1);
-       info->resource_number = image;
-       guchar *buffer;
+       struct image_info *to_find = g_new0(struct image_info, 1);
+       struct image_info *info;
        GdkPixmap *canvas;
 
-       //printf("glk_image_get_info(%d)\n", image);
-
        /* Lookup the proper resource */
-       blorb_error = giblorb_load_resource(glk_data->resource_map, giblorb_method_FilePos, &res, giblorb_ID_Pict, image);
-       if(blorb_error != giblorb_err_None) {
-               WARNING_S( "Error loading resource", giblorb_get_error_message(blorb_error) );
+       to_find->resource_number = image;
+       to_find->scaled = FALSE; /* we want the original image size */
+
+       if( !(info = image_cache_find(to_find)) ) {
+               info = load_image_in_cache(image, 0, 0);
+               if(info == NULL)
+                       return FALSE;
+       }
+
+       gdk_threads_enter();
+
+       gtk_image_get_pixmap( GTK_IMAGE(win->widget), &canvas, NULL );
+       if(canvas == NULL) {
+               WARNING("Could not get pixmap");
                return FALSE;
        }
 
-       /* Load the resource */
-       GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
-       glk_stream_set_position(glk_data->resource_file, res.data.startpos, seekmode_Start);
-       buffer = g_malloc( BUFFER_SIZE * sizeof(guchar) );
+       printf("image info: %d x %d, scaled=%d, pixbufaddr=%d\n", info->width, info->height, (int)info->scaled, (int)info->pixbuf);
+       gdk_draw_pixbuf( GDK_DRAWABLE(canvas), NULL, info->pixbuf, 0, 0, val1, val2, -1, -1, GDK_RGB_DITHER_NONE, 0, 0 );
 
-       guint32 total_read = 0;
-       while(total_read < res.length) {
-               guint32 num_read = glk_get_buffer_stream(glk_data->resource_file, (char *) buffer, BUFFER_SIZE);
+       /* Update the screen */
+       gtk_widget_queue_draw(win->widget);
 
-               if( !gdk_pixbuf_loader_write(loader, buffer, MIN(BUFFER_SIZE, num_read), &pixbuf_error) ) {
-                       WARNING_S("Cannot read image", pixbuf_error->message);
-                       giblorb_unload_chunk(glk_data->resource_map, image);
-                       gdk_pixbuf_loader_close(loader, &pixbuf_error);
-                       g_free(buffer);
-                       return FALSE;
-               }
+       gdk_threads_leave();
 
-               total_read += num_read;
+       return TRUE;
+}
+
+
+glui32
+glk_image_draw_scaled(winid_t win, glui32 image, glsi32 val1, glsi32 val2, glui32 width, glui32 height)
+{
+       VALID_WINDOW(win, return FALSE);
+       g_return_val_if_fail(win->type == wintype_Graphics, FALSE);
+
+       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+       struct image_info *to_find = g_new0(struct image_info, 1);
+       struct image_info *info;
+       struct image_info *scaled_info;
+       GdkPixmap *canvas;
+
+       /* Lookup the proper resource */
+       to_find->resource_number = image;
+       to_find->scaled = TRUE; /* any image size equal or larger than requested will do */
+
+       if( !(info = image_cache_find(to_find)) ) {
+               info = load_image_in_cache(image, width, height);
+               if(info == NULL)
+                       return FALSE;
        }
-       giblorb_unload_chunk(glk_data->resource_map, image);
-       g_free(buffer);
+
+       gdk_threads_enter();
 
        gtk_image_get_pixmap( GTK_IMAGE(win->widget), &canvas, NULL );
        if(canvas == NULL) {
                WARNING("Could not get pixmap");
-               gdk_pixbuf_loader_close(loader, &pixbuf_error);
                return FALSE;
        }
 
-       GdkPixbuf *pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
-       if(pixbuf == NULL) {
-               WARNING("Could not read image");
-               gdk_pixbuf_loader_close(loader, &pixbuf_error);
-               return FALSE;
+       /* Scale the image if necessary */
+       if(info->width != width || info->height != height) {
+               GdkPixbuf *scaled = gdk_pixbuf_scale_simple(info->pixbuf, width, height, GDK_INTERP_BILINEAR);
+
+               /* Add the scaled image into the image cache */
+               scaled_info = g_new0(struct image_info, 1);
+               scaled_info->resource_number = info->resource_number;
+               scaled_info->width = gdk_pixbuf_get_width(scaled);
+               scaled_info->height = gdk_pixbuf_get_width(scaled);
+               scaled_info->pixbuf = scaled;
+               scaled_info->scaled = TRUE;
+               glk_data->image_cache = g_slist_prepend(glk_data->image_cache, scaled_info);
+
+               /* Continue working with the scaled version */
+               info = scaled_info;
        }
 
-       gdk_draw_pixbuf( GDK_DRAWABLE(canvas), NULL, pixbuf, 0, 0, val1, val2, -1, -1, GDK_RGB_DITHER_NONE, 0, 0 );
-       gdk_pixbuf_loader_close(loader, &pixbuf_error);
+       gdk_draw_pixbuf( GDK_DRAWABLE(canvas), NULL, info->pixbuf, 0, 0, val1, val2, -1, -1, GDK_RGB_DITHER_NONE, 0, 0 );
 
        /* Update the screen */
        gtk_widget_queue_draw(win->widget);
-       return TRUE;
-}
 
-glui32
-glk_image_draw_scaled(winid_t win, glui32 image, glsi32 val1, glsi32 val2, glui32 width, glui32 height)
-{
+       gdk_threads_leave();
+
        return TRUE;
 }
 
@@ -177,19 +294,58 @@ glk_window_fill_rect(winid_t win, glui32 color, glsi32 left, glsi32 top, glui32
        VALID_WINDOW(win, return);
        g_return_if_fail(win->type == wintype_Graphics);
 
+
+       gdk_threads_enter();
+
        GdkPixmap *map;
        gtk_image_get_pixmap( GTK_IMAGE(win->widget), &map, NULL );
        gdk_draw_rectangle( GDK_DRAWABLE(map), win->widget->style->white_gc, TRUE, left, top, width, height);
        gtk_widget_queue_draw(win->widget);
+
+       gdk_threads_leave();
 }
 
 void
 glk_window_erase_rect(winid_t win, glsi32 left, glsi32 top, glui32 width, glui32 height)
 {
-       printf("erasing rect: %d %d %d %d\n", left, top, width, height);
        glk_window_fill_rect(win, win->background_color, left, top, width, height);
 }
 
 void glk_window_flow_break(winid_t win)
 {
 }
+
+/*** Called when the graphics window is resized. Resize the backing pixmap if necessary ***/
+void
+on_graphics_size_allocate(GtkWidget *widget, GtkAllocation *allocation, winid_t win)
+{ 
+       GdkPixmap *oldmap;
+       gtk_image_get_pixmap( GTK_IMAGE(widget), &oldmap, NULL );
+       gint oldwidth = 0;
+       gint oldheight = 0;
+       /* Determine whether a pixmap exists with the correct size */
+       gboolean needs_resize = FALSE;
+       if(oldmap == NULL)
+               needs_resize = TRUE;
+       else {
+               gdk_drawable_get_size( GDK_DRAWABLE(oldmap), &oldwidth, &oldheight );
+               if(oldwidth != allocation->width || oldheight != allocation->height)
+                       needs_resize = TRUE;
+       }
+
+       if(needs_resize) {
+               /* Create a new pixmap */
+               GdkPixmap *newmap = gdk_pixmap_new(widget->window, allocation->width, allocation->height, -1);
+               gdk_draw_rectangle( GDK_DRAWABLE(newmap), widget->style->white_gc, TRUE, 0, 0, allocation->width, allocation->height);
+
+               /* Copy the contents of the old pixmap */
+               if(oldmap != NULL)
+                       gdk_draw_drawable( GDK_DRAWABLE(newmap), widget->style->white_gc, GDK_DRAWABLE(oldmap), 0, 0, 0, 0, oldwidth, oldheight);
+               
+               /* Use the new pixmap */
+               gtk_image_set_from_pixmap( GTK_IMAGE(widget), newmap, NULL );
+               g_object_unref(newmap);
+       }
+}
+