/* * pixbuf.c - operations on GdkPixbuf objects * * Some of the code in this module was copied from the GQview program. */ #include #include #include "lodju.h" GdkPixbuf *pixbuf_copy_rotate_90(GdkPixbuf *src, gint counter_clockwise) { GdkPixbuf *dest; gint has_alpha; gint sw, sh, srs; gint dw, dh, drs; guchar *s_pix; guchar *d_pix; guchar *sp; guchar *dp; gint i, j; gint a; if (!src) return NULL; sw = gdk_pixbuf_get_width(src); sh = gdk_pixbuf_get_height(src); has_alpha = gdk_pixbuf_get_has_alpha(src); srs = gdk_pixbuf_get_rowstride(src); s_pix = gdk_pixbuf_get_pixels(src); dw = sh; dh = sw; dest = gdk_pixbuf_new(GDK_COLORSPACE_RGB, has_alpha, 8, dw, dh); g_assert(dest != NULL); drs = gdk_pixbuf_get_rowstride(dest); d_pix = gdk_pixbuf_get_pixels(dest); a = (has_alpha ? 4 : 3); for (i = 0; i < sh; i++) { sp = s_pix + (i * srs); for (j = 0; j < sw; j++) { if (counter_clockwise) { dp = d_pix + ((dh - j - 1) * drs) + (i * a); } else { dp = d_pix + (j * drs) + ((dw - i - 1) * a); } *(dp++) = *(sp++); /* r */ *(dp++) = *(sp++); /* g */ *(dp++) = *(sp++); /* b */ if (has_alpha) *(dp) = *(sp++); /* a */ } } return dest; } GdkPixbuf *pixbuf_copy_mirror(GdkPixbuf *src, gint mirror, gint flip) { GdkPixbuf *dest; gint has_alpha; gint w, h, srs; gint drs; guchar *s_pix; guchar *d_pix; guchar *sp; guchar *dp; gint i, j; gint a; if (!src) return NULL; w = gdk_pixbuf_get_width(src); h = gdk_pixbuf_get_height(src); has_alpha = gdk_pixbuf_get_has_alpha(src); srs = gdk_pixbuf_get_rowstride(src); s_pix = gdk_pixbuf_get_pixels(src); dest = gdk_pixbuf_new(GDK_COLORSPACE_RGB, has_alpha, 8, w, h); drs = gdk_pixbuf_get_rowstride(dest); d_pix = gdk_pixbuf_get_pixels(dest); a = has_alpha ? 4 : 3; for (i = 0; i < h; i++) { sp = s_pix + (i * srs); if (flip) { dp = d_pix + ((h - i - 1) * drs); } else { dp = d_pix + (i * drs); } if (mirror) { dp += (w - 1) * a; for (j = 0; j < w; j++) { *(dp++) = *(sp++); /* r */ *(dp++) = *(sp++); /* g */ *(dp++) = *(sp++); /* b */ if (has_alpha) *(dp) = *(sp++); /* a */ dp -= (a + 3); } } else { for (j = 0; j < w; j++) { *(dp++) = *(sp++); /* r */ *(dp++) = *(sp++); /* g */ *(dp++) = *(sp++); /* b */ if (has_alpha) *(dp++) = *(sp++); /* a */ } } } return dest; } GdkPixbuf *pixbuf_scale(GdkPixbuf *orig, gint width, gint height) { gint orig_width; gint orig_height; double ratio; double width_ratio; double height_ratio; orig_width = gdk_pixbuf_get_width(orig); orig_height = gdk_pixbuf_get_height(orig); width_ratio = (double) width / orig_width; height_ratio = (double) height / orig_height; if (width_ratio < height_ratio) ratio = width_ratio; else ratio = height_ratio; return gdk_pixbuf_scale_simple(orig, orig_width * ratio, orig_height * ratio, GDK_INTERP_NEAREST); } /*********************************************************************** * Background loading of images. */ struct cached_pixbuf { GdkPixbuf *pixbuf; gchar *filename; gint width; gint height; gint angle; }; static GList *originals; static GList *scaled_ones; static void destroy_cached_pixbuf(struct cached_pixbuf *cp) { gdk_pixbuf_unref(cp->pixbuf); g_free(cp->filename); g_free(cp); } static glong memory_in_list(GList *list) { glong sum; struct cached_pixbuf *cp; for (sum = 0; list != NULL; list = list->next) { cp = list->data; sum += (glong) cp->width * cp->height; } return sum * 3; /* there are three bytes per pixel */ } static GList *remove_least_recently_used(GList *list) { GList *last; last = g_list_last(list); list = g_list_remove_link(list, last); destroy_cached_pixbuf(last->data); g_list_free(last); return list; } static void limit_cache_size(void) { glong sum; for (;;) { sum = memory_in_list(originals) + memory_in_list(scaled_ones); if (sum <= cfg_get_cache_size()) break; if (originals != NULL && originals->next != NULL) originals = remove_least_recently_used(originals); else if (scaled_ones != NULL && scaled_ones->next != NULL) scaled_ones = remove_least_recently_used(scaled_ones); else break; } } static struct cached_pixbuf *save_pixbuf(GdkPixbuf *pixbuf, const gchar *fn, gint angle) { struct cached_pixbuf *cp; cp = g_new(struct cached_pixbuf, 1); cp->pixbuf = pixbuf; cp->filename = g_strdup(fn); cp->width = gdk_pixbuf_get_width(pixbuf); cp->height = gdk_pixbuf_get_height(pixbuf); cp->angle = angle; return cp; } static void save_original_pixbuf(GdkPixbuf *pixbuf, const gchar *filename) { struct cached_pixbuf *cp; cp = save_pixbuf(gdk_pixbuf_copy(pixbuf), filename, 0); originals = g_list_prepend(originals, cp); limit_cache_size(); } static GList *move_to_front(GList *list, gpointer data) { list = g_list_remove(list, data); return g_list_prepend(list, data); } static struct cached_pixbuf *find_original(const gchar *fn) { GList *node; struct cached_pixbuf *cp; for (node = originals; node != NULL; node = node->next) { cp = node->data; if (strcmp(cp->filename, fn) == 0) { originals = move_to_front(originals, cp); return cp; } } return NULL; } static GdkPixbuf *scale_and_rotate(GdkPixbuf *o, gint angle, gint w, gint h) { GdkPixbuf *p; GdkPixbuf *scaled; scaled = NULL; switch (angle) { default: /* In case of bad rotation angle */ case 0: p = pixbuf_scale(o, w, h); break; case 90: scaled = pixbuf_scale(o, h, w); p = pixbuf_copy_rotate_90(scaled, FALSE); break; case 180: scaled = pixbuf_scale(o, w, h); p = pixbuf_copy_mirror(scaled, TRUE, TRUE); break; case 270: scaled = pixbuf_scale(o, h, w); p = pixbuf_copy_rotate_90(scaled, TRUE); break; } if (scaled != NULL) gdk_pixbuf_unref(scaled); return p; } static struct cached_pixbuf *save_scaled_one(struct cached_pixbuf *cp, gint angle, gint width, gint height) { struct cached_pixbuf *cp2; GdkPixbuf *pixbuf; pixbuf = scale_and_rotate(cp->pixbuf, angle, width, height); cp2 = save_pixbuf(pixbuf, cp->filename, angle); scaled_ones = g_list_prepend(scaled_ones, cp2); limit_cache_size(); return cp2; } static struct cached_pixbuf *get_scaled_one(const gchar *fn, gint angle, gint w, gint h) { GList *node; struct cached_pixbuf *match; struct cached_pixbuf *cp; gint cp_w, cp_h; match = NULL; for (node = scaled_ones; node != NULL; node = node->next) { cp = node->data; if (strcmp(cp->filename, fn) == 0) { cp_w = gdk_pixbuf_get_width(cp->pixbuf); cp_h = gdk_pixbuf_get_height(cp->pixbuf); if (cp->angle == angle && (cp_w == w || cp_h == h)) { scaled_ones = move_to_front(scaled_ones, cp); return cp; } } } return NULL; } GdkPixbuf *pixbuf_get(const gchar *fn, gint angle, gint width, gint height) { struct cached_pixbuf *cp; struct cached_pixbuf *original; cp = get_scaled_one(fn, angle, width, height); if (cp == NULL) { original = find_original(fn); if (original == NULL) return NULL; cp = save_scaled_one(original, angle, width, height); } return gdk_pixbuf_copy(cp->pixbuf); } GdkPixbuf *pixbuf_get_from_image(Image *image, gint width, gint height) { gint rotation_angle; const gchar *filename; rotation_angle = atoi(image_get_attribute(image, "rotation")); filename = image_get_attribute(image, "filename"); return pixbuf_get(filename, rotation_angle, width, height); } static GdkPixbufLoader *loader; static guint loader_idle_id; static FILE *file; static gchar *filename; static void (*load_done_callback)(void); static void (*set_progress_bar)(gfloat); static gfloat total_size; static gfloat total_read; void pixbuf_load_stop(void) { if (file != NULL) { (void) fclose(file); file = NULL; } g_free(filename); filename = NULL; if (loader != NULL) { gtk_idle_remove(loader_idle_id); gdk_pixbuf_loader_close(loader); loader = NULL; } load_done_callback = NULL; if (set_progress_bar != NULL) set_progress_bar(0.0); set_progress_bar = NULL; } static gint load_image(gpointer data) { guchar buf[64*1024]; size_t n; FILE *file; GdkPixbuf *pixbuf; file = data; if (file == NULL) goto error; n = fread(buf, 1, sizeof(buf), file); if (n > 0) { total_read += n; if (set_progress_bar != NULL) set_progress_bar(total_read / total_size); if (!gdk_pixbuf_loader_write(loader, buf, n)) goto error; } else { (void) fclose(file); if (set_progress_bar != NULL) set_progress_bar(1.0); pixbuf = gdk_pixbuf_loader_get_pixbuf(loader); save_original_pixbuf(pixbuf, filename); gdk_pixbuf_unref(pixbuf); load_done_callback(); pixbuf_load_stop(); return FALSE; } return TRUE; error: g_message(_("Could not load image")); /* XXX error reporting... */ pixbuf_load_stop(); return FALSE; } void pixbuf_load_start(const gchar *fn, void (*done)(void), void (*set_bar)(gfloat)) { FILE *f; pixbuf_load_stop(); filename = g_strdup(fn); f = fopen(filename, "r"); if (f == NULL) { g_message(_("Couldn't open image file")); return; /* XXX error dialog */ } if (fseek(f, 0, SEEK_END) == -1) total_size = 1024.0 * 1024.0 * 1024.0; else total_size = ftell(f); total_read = 0.0; rewind(f); g_assert(loader == NULL); loader = gdk_pixbuf_loader_new(); loader_idle_id = gtk_idle_add(load_image, f); load_done_callback = done; set_progress_bar = set_bar; } /*********************************************************************** * Unit tests. */ /* * Width and height of the test pixmaps we generate. */ #define WIDTH 105 #define HEIGHT 69 static GdkPixbuf *create(void) { GdkPixbuf *pixbuf; guchar *pixels; gint x; gint y; gint rowstride; pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, WIDTH, HEIGHT); pixels = gdk_pixbuf_get_pixels(pixbuf); rowstride = gdk_pixbuf_get_rowstride(pixbuf); for (y = 0; y < HEIGHT; ++y) { for (x = 0; x < WIDTH; ++x) { pixels[y * rowstride + x * 3 + 0] = 'r'; pixels[y * rowstride + x * 3 + 1] = 'g'; pixels[y * rowstride + x * 3 + 2] = 'b'; } } return pixbuf; } static gboolean equal(GdkPixbuf *p1, GdkPixbuf *p2) { gint w1, w2; gint h1, h2; gint rs1, rs2; guchar *pixels1, *pixels2; gint x, y; if (p1 == NULL || p2 == NULL) return FALSE; if (gdk_pixbuf_get_colorspace(p1) != gdk_pixbuf_get_colorspace(p2)) return FALSE; if (gdk_pixbuf_get_n_channels(p1) != gdk_pixbuf_get_n_channels(p2)) return FALSE; if (gdk_pixbuf_get_has_alpha(p1) != gdk_pixbuf_get_has_alpha(p2)) return FALSE; if (gdk_pixbuf_get_bits_per_sample(p1) != 8) return FALSE; if (gdk_pixbuf_get_bits_per_sample(p2) != 8) return FALSE; w1 = gdk_pixbuf_get_width(p1); h1 = gdk_pixbuf_get_height(p1); rs1 = gdk_pixbuf_get_rowstride(p1); w2 = gdk_pixbuf_get_width(p2); h2 = gdk_pixbuf_get_height(p2); rs2 = gdk_pixbuf_get_rowstride(p2); if (w1 != w2 || h1 != h2 || rs1 != rs2) return FALSE; pixels1 = gdk_pixbuf_get_pixels(p1); pixels2 = gdk_pixbuf_get_pixels(p2); for (y = 0; y < h1; ++y) { for (x = 0; x < w1; ++x) { if (memcmp(pixels1 + y*rs1 + x*3, pixels2 + y*rs1 + x*3, 3) != 0) return FALSE; } } return TRUE; } static GdkPixbuf *pixbuf1; static GdkPixbuf *pixbuf2; static GdkPixbuf *pixbuf3; static void setup(void) { pixbuf1 = NULL; pixbuf2 = NULL; pixbuf3 = NULL; } static void teardown(void) { if (pixbuf1 != NULL) gdk_pixbuf_unref(pixbuf1); if (pixbuf2 != NULL) gdk_pixbuf_unref(pixbuf2); if (pixbuf3 != NULL) gdk_pixbuf_unref(pixbuf3); pixbuf1 = NULL; pixbuf2 = NULL; pixbuf3 = NULL; } gint pixbuf_unit_test(void) { TEST("rotate90", { pixbuf1 = create(); ASSERT(pixbuf1 != NULL); ASSERT(gdk_pixbuf_get_width(pixbuf1) == WIDTH); ASSERT(gdk_pixbuf_get_height(pixbuf1) == HEIGHT); pixbuf2 = pixbuf_copy_rotate_90(pixbuf1, TRUE); ASSERT(pixbuf2 != NULL); ASSERT(gdk_pixbuf_get_width(pixbuf2) == HEIGHT); ASSERT(gdk_pixbuf_get_height(pixbuf2) == WIDTH); pixbuf3 = pixbuf_copy_rotate_90(pixbuf2, FALSE); ASSERT(equal(pixbuf1, pixbuf3)); }); TEST("mirror and flip", { pixbuf1 = create(); pixbuf2 = pixbuf_copy_mirror(pixbuf1, TRUE, FALSE); pixbuf3 = pixbuf_copy_mirror(pixbuf2, TRUE, FALSE); ASSERT(equal(pixbuf1, pixbuf3)); gdk_pixbuf_unref(pixbuf2); gdk_pixbuf_unref(pixbuf3); pixbuf2 = pixbuf_copy_mirror(pixbuf1, FALSE, TRUE); pixbuf3 = pixbuf_copy_mirror(pixbuf2, FALSE, TRUE); ASSERT(equal(pixbuf1, pixbuf3)); gdk_pixbuf_unref(pixbuf2); gdk_pixbuf_unref(pixbuf3); pixbuf2 = pixbuf_copy_mirror(pixbuf1, TRUE, TRUE); pixbuf3 = pixbuf_copy_mirror(pixbuf2, TRUE, TRUE); ASSERT(equal(pixbuf1, pixbuf3)); }); TEST("scale", { pixbuf1 = create(); pixbuf2 = pixbuf_scale(pixbuf1, WIDTH*2, HEIGHT*2); pixbuf3 = pixbuf_scale(pixbuf2, WIDTH, HEIGHT); ASSERT(equal(pixbuf1, pixbuf3)); }); return 0; }