/* * imagestore.c * * Copyright © 2013-2018 Thomas White * * This file is part of Colloquium. * * Colloquium is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include "imagestore.h" #define MAX_SIZES (32) struct image_record { char *filename; cairo_surface_t *surf[MAX_SIZES]; int w[MAX_SIZES]; int last_used[MAX_SIZES]; int n_sizes; int h_last_used; int full_width; int full_height; }; struct _imagestore { int n_images; struct image_record *images; int max_images; GFile *pparent; GFile *iparent; const char *storename; }; static int alloc_images(ImageStore *is, int new_max_images) { struct image_record *images_new; images_new = realloc(is->images, sizeof(struct image_record)*new_max_images); if ( images_new == NULL ) return 1; is->images = images_new; is->max_images = new_max_images; return 0; } ImageStore *imagestore_new(const char *storename) { ImageStore *is; is = calloc(1, sizeof(ImageStore)); if ( is == NULL ) return NULL; is->images = NULL; is->pparent = NULL; if ( storename != NULL ) { is->iparent = g_file_new_for_uri(storename); } else { is->iparent = NULL; } is->n_images = 0; is->max_images = 0; if ( alloc_images(is, 32) ) { free(is); return NULL; } return is; } void imagestore_set_parent(ImageStore *is, GFile *parent) { if ( is->pparent != NULL ) { g_object_unref(is->pparent); } is->pparent = parent; } void imagestore_destroy(ImageStore *is) { int i; for ( i=0; in_images; i++ ) { int j; free(is->images[i].filename); for ( j=0; jimages[i].n_sizes; j++ ) { if ( is->images[i].surf[j] != NULL ) { g_object_unref(is->images[i].surf[j]); } } } g_object_unref(is->pparent); g_object_unref(is->iparent); free(is->images); free(is); } static cairo_surface_t *pixbuf_to_surface(GdkPixbuf *t) { cairo_surface_t *surf; cairo_t *cr; int w, h; if ( t == NULL ) return NULL; w = gdk_pixbuf_get_width(t); h = gdk_pixbuf_get_height(t); surf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h); if ( surf == NULL ) return NULL; cr = cairo_create(surf); gdk_cairo_set_source_pixbuf(cr, t, 0, 0); cairo_pattern_t *patt = cairo_get_source(cr); cairo_pattern_set_extend(patt, CAIRO_EXTEND_PAD); cairo_pattern_set_filter(patt, CAIRO_FILTER_BEST); cairo_paint(cr); cairo_destroy(cr); return surf; } static int try_load(GFile *file, GdkPixbuf **pixbuf, gint w, gint h) { GFileInputStream *stream; GError *error = NULL; stream = g_file_read(file, NULL, &error); if ( stream != NULL ) { GError *pberr = NULL; *pixbuf = gdk_pixbuf_new_from_stream_at_scale(G_INPUT_STREAM(stream), w, h, TRUE, NULL, &pberr); g_object_unref(stream); g_object_unref(file); return 1; } return 0; } static GdkPixbuf *load_image(const char *uri, GFile *pparent, GFile *iparent, gint w, gint h) { GFile *file; GdkPixbuf *pixbuf; /* Literal pathname */ file = g_file_new_for_path(uri); if ( try_load(file, &pixbuf, w, h) ) return pixbuf; /* Try the file prefixed with the directory the presentation is in */ if ( pparent != NULL ) { file = g_file_get_child(pparent, uri); if ( try_load(file, &pixbuf, w, h) ) return pixbuf; } /* Try prefixing with imagestore folder */ if ( iparent != NULL ) { file = g_file_get_child(iparent, uri); if ( try_load(file, &pixbuf, w, h) ) return pixbuf; } return NULL; } static struct image_record *add_image_record(ImageStore *is, const char *filename) { int idx; if ( is->n_images == is->max_images ) { if ( alloc_images(is, is->max_images+32) ) { fprintf(stderr, "Couldn't allocate memory for image " "records.\n"); return NULL; } } idx = is->n_images++; is->images[idx].n_sizes = 0; is->images[idx].filename = strdup(filename); is->images[idx].h_last_used = 0; return &is->images[idx]; } int imagestore_get_size(ImageStore *is, const char *filename, int *w, int *h) { GdkPixbuf *pixbuf; struct image_record *ir; int i; for ( i=0; in_images; i++ ) { if ( strcmp(is->images[i].filename, filename) == 0 ) { *w = is->images[i].full_width; *h = is->images[i].full_height; return 0; } } pixbuf = load_image(filename, is->pparent, is->iparent, -1, -1); if ( pixbuf == NULL ) return 1; *w = gdk_pixbuf_get_width(pixbuf); *h = gdk_pixbuf_get_height(pixbuf); ir = add_image_record(is, filename); if ( ir != NULL ) { ir->full_width = *w; ir->full_height = *h; } /* otherwise can't cache, too bad */ g_object_unref(pixbuf); return 0; } static int find_earliest(struct image_record *im) { int i, earliest, l; earliest = im->h_last_used; l = im->n_sizes; for ( i=0; in_sizes; i++ ) { if ( im->last_used[i] < earliest ) { earliest = im->last_used[i]; l = i; } } assert(l != im->n_sizes); cairo_surface_destroy(im->surf[l]); return l; } static cairo_surface_t *add_image_size(struct image_record *im, const char *filename, GFile *pparent, GFile *iparent, int w) { cairo_surface_t *surf; GdkPixbuf *t; int pos; t = load_image(filename, pparent, iparent, w, -1); if ( t == NULL ) return NULL; surf = pixbuf_to_surface(t); g_object_unref(t); /* Add surface to list */ if ( im->n_sizes == MAX_SIZES ) { pos = find_earliest(im); } else { pos = im->n_sizes++; } im->w[pos] = w; im->surf[pos] = surf; im->last_used[pos] = im->h_last_used++; return surf; } static cairo_surface_t *add_new_image(ImageStore *is, const char *filename, int w) { struct image_record *ir; ir = add_image_record(is, filename); if ( ir == NULL ) return NULL; return add_image_size(ir, filename, is->pparent, is->iparent, w); } void show_imagestore(ImageStore *is) { int i; printf("Store %p contains %i records.\n", is, is->n_images); for ( i=0; in_images; i++ ) { printf("%s :\n", is->images[i].filename); printf("\n"); } } cairo_surface_t *lookup_image(ImageStore *is, const char *filename, int w) { int i, j; int found = 0; cairo_surface_t *surf; for ( i=0; in_images; i++ ) { if ( strcmp(is->images[i].filename, filename) == 0 ) { found = 1; break; } } if ( !found ) { return add_new_image(is, filename, w); } for ( j=0; jimages[i].n_sizes; j++ ) { if ( is->images[i].w[j] == w ) { is->images[i].last_used[j] = is->images[i].h_last_used++; return is->images[i].surf[j]; } } /* We don't have this size yet */ surf = add_image_size(&is->images[i], filename, is->pparent, is->iparent, w); return surf; }