/*
* 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>
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;
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;
}
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();
}
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);