]> git.draconx.ca Git - liblbx.git/commitdiff
lbxgui: Use Cairo for drawing.
authorNick Bowler <nbowler@draconx.ca>
Wed, 29 Jan 2014 00:34:17 +0000 (19:34 -0500)
committerNick Bowler <nbowler@draconx.ca>
Fri, 31 Jan 2014 05:39:01 +0000 (00:39 -0500)
The GDK stuff is deprecated so let's convert all the GDK drawing bits to
Cairo.  Unlike GDK, Cairo does not include an XBM loader so we have to
implement that from scratch.

Keep the GDK pixbuf bits around for now.

src/gui/lbxgui.c

index bb9e5e5664459c2ce9b06249f204adf55f80bb44..14da3787fc916cfe1b8c2a3a9f3b65dfae1b2ddd 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  2ooM: The Master of Orion II Reverse Engineering Project
  *  Graphical tool for inspecting LBX archives.
- *  Copyright (C) 2010, 2014 Nick Bowler
+ *  Copyright © 2010, 2014 Nick Bowler
  *
  *  This program is free software: you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 #include <config.h>
+#include <assert.h>
 #include <stdlib.h>
 #include <string.h>
+#include <stdbool.h>
 #include <math.h>
 
 #include <gtk/gtk.h>
@@ -33,7 +35,9 @@ static GtkTreeStore *archives;
 static GtkBuilder *builder;
 
 static GtkWidget *canvas;
-static GdkGC     *bg_gc;
+
+static cairo_pattern_t *bg_pattern;
+static bool bg_stipple;
 
 static struct lbx_image *image;
 
@@ -135,17 +139,32 @@ static gboolean timeout(gpointer data)
 
 gboolean canvas_expose(GtkWidget *canvas, GdkEventExpose *event, gpointer data)
 {
+       cairo_t *cr;
+
        if (!framebuf)
                return FALSE;
 
-       gdk_draw_rectangle(canvas->window, bg_gc, TRUE,
-               event->area.x, event->area.y,
-               event->area.width, event->area.height);
+       cr = gdk_cairo_create(canvas->window);
+
+       if (bg_stipple) {
+               cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
+               gdk_cairo_rectangle(cr, &event->area);
+               cairo_fill(cr);
+
+               cairo_set_source_rgb(cr, 0.3, 0.3, 0.3);
+               gdk_cairo_rectangle(cr, &event->area);
+               cairo_mask(cr, bg_pattern);
+       } else {
+               cairo_set_source_rgb(cr, 0, 0, 0);
+               gdk_cairo_rectangle(cr, &event->area);
+               cairo_fill(cr);
+       }
+
+       gdk_cairo_set_source_pixbuf(cr, framebuf, 0, 0);
+       gdk_cairo_rectangle(cr, &event->area);
+       cairo_fill(cr);
 
-       gdk_draw_pixbuf(canvas->window, NULL, framebuf,
-               event->area.x, event->area.y, event->area.x, event->area.y,
-               event->area.width, event->area.height,
-               GDK_RGB_DITHER_NORMAL, 0, 0);
+       cairo_destroy(cr);
 
        return TRUE;
 }
@@ -280,19 +299,7 @@ void set_image(GtkComboBox *combo)
 
 void set_background(GtkCheckMenuItem *item, gpointer data)
 {
-       GdkColor black = { .red = 0,      .green = 0,      .blue = 0      };
-       GdkColor dark  = { .red = 0x6666, .green = 0x6666, .blue = 0x6666 };
-       GdkColor light = { .red = 0x9999, .green = 0x9999, .blue = 0x9999 };
-
-       if (gtk_check_menu_item_get_active(item)) {
-               /* Enable stippled background. */
-               gdk_gc_set_rgb_fg_color(bg_gc, &dark);
-               gdk_gc_set_rgb_bg_color(bg_gc, &light);
-               gdk_gc_set_fill(bg_gc, GDK_OPAQUE_STIPPLED);
-       } else {
-               gdk_gc_set_rgb_fg_color(bg_gc, &black);
-               gdk_gc_set_fill(bg_gc, GDK_SOLID);
-       }
+       bg_stipple = gtk_check_menu_item_get_active(item);
        redraw_image();
 }
 
@@ -444,18 +451,72 @@ static void init_interface(void)
        init_combobox(builder, "overchooser");
 }
 
-static void init_background(GdkDrawable *drawable)
+/*
+ * Reverse the bits in each byte of a 32-bit word.
+ */
+static guint32 reverse4b(guint32 v)
 {
-       GtkWidget *check;
-       GdkBitmap *bitmap;
+       v = ((v >> 1) & 0x55555555) | ((v & 0x55555555) << 1);
+       v = ((v >> 2) & 0x33333333) | ((v & 0x33333333) << 2);
+       v = ((v >> 4) & 0x0f0f0f0f) | ((v & 0x0f0f0f0f) << 4);
+       return v;
+}
+
+static cairo_surface_t *
+xbm_to_cairo(const void *xbmdata, int width, int height)
+{
+       const unsigned char *data = xbmdata;
+       unsigned char *sdata;
+       cairo_surface_t *s;
+       int stride;
+
+       s = cairo_image_surface_create(CAIRO_FORMAT_A1, width, height);
+       if (cairo_surface_status(s) != CAIRO_STATUS_SUCCESS)
+               return s;
+
+       sdata = cairo_image_surface_get_data(s);
+       stride = cairo_image_surface_get_stride(s);
+       assert(stride % 4 == 0);
+       width = (width + 7) / 8;
+
+       cairo_surface_flush(s);
+       for (int y = 0; y < height; y++) {
+               /*
+                * Cairo is nuts.  The mapping of bits to pixels in the A1
+                * format actually depends on the host *byte* ordering, so
+                * we actually need these two separate cases.
+                */
+               if (G_BYTE_ORDER == G_LITTLE_ENDIAN) {
+                       /* Easy, XBM and Cairo match in this case */
+                       memcpy(sdata, data, width);
+               } else {
+                       /* No such luck, we have to convert manually */
+                       for (int x = 0; x < width; x += 4) {
+                               guint32 word = 0;
+
+                               memcpy(&word, data+x, MIN(4, width-x));
+                               word = reverse4b(word);
+                               memcpy(sdata+x, &word, 4);
+                       }
+               }
 
-       bg_gc = gdk_gc_new(canvas->window);
+               sdata += stride;
+               data += width;
+       }
+       cairo_surface_mark_dirty(s);
+
+       return s;
+}
 
-       bitmap = gdk_bitmap_create_from_data(drawable, (gchar *)bg_bits,
-               bg_width, bg_height);
+static void init_background(GdkDrawable *drawable)
+{
+       cairo_surface_t *bg;
+       GtkWidget *check;
 
-       gdk_gc_set_stipple(bg_gc, bitmap);
-       gdk_gc_set_ts_origin(bg_gc, 0, 0);
+       bg = xbm_to_cairo(bg_bits, bg_width, bg_height);
+       bg_pattern = cairo_pattern_create_for_surface(bg);
+       cairo_pattern_set_extend(bg_pattern, CAIRO_EXTEND_REPEAT);
+       cairo_surface_destroy(bg);
 
        check = GTK_WIDGET(gtk_builder_get_object(builder, "menu-background"));
        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(check), TRUE);