X-Git-Url: https://git.draconx.ca/gitweb/liblbx.git/blobdiff_plain/5ca921b28d5ca2964e19dad8f9b8614b2aebca4d..f9f3a47a274221c512a23a76dceb6b2b1ee1d6a6:/src/gui/lbxgui.c diff --git a/src/gui/lbxgui.c b/src/gui/lbxgui.c index a3212a6..e9726a6 100644 --- a/src/gui/lbxgui.c +++ b/src/gui/lbxgui.c @@ -1,7 +1,7 @@ /* * 2ooM: The Master of Orion II Reverse Engineering Project * Graphical tool for inspecting LBX archives. - * Copyright (C) 2010 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 @@ -17,29 +17,46 @@ * along with this program. If not, see . */ #include +#include #include #include +#include #include #include #include "lbx.h" #include "image.h" -#include "render.h" +#include "lbxgui.h" #include "bg.xbm" -extern char lbxgui_xml[]; - static GtkTreeStore *archives; static GtkBuilder *builder; static GtkWidget *canvas; -static GdkGC *bg_gc; + +static cairo_surface_t *framebuf; +static cairo_pattern_t *bg_pattern; +static bool bg_stipple; static struct lbx_image *image; +static struct lbx_colour palette[256]; + +/* We support stacking up to three palettes, with each superseding the last. */ +static struct lbx_colour palette_external[256]; +static struct lbx_colour palette_internal[256]; +static struct lbx_colour palette_override[256]; + +static void refresh_palette(void) +{ + memcpy(palette, palette_external, sizeof palette); + lbxgui_stack_palette(palette, palette_internal); + lbxgui_stack_palette(palette, palette_override); -static GdkPixbuf *framebuf; + if (framebuf) + lbxgui_render_restart(framebuf); +} void play_toggled(GtkToggleButton *button, gpointer data) { @@ -63,8 +80,8 @@ void set_frame(GtkSpinButton *spin, gpointer data) { unsigned frame = gtk_spin_button_get_value_as_int(spin); - if (image && framebuf) - render_to_pixbuf(image, framebuf, frame); + if (image) + lbxgui_render_argb(framebuf, image, frame, palette); gdk_window_invalidate_rect(canvas->window, NULL, FALSE); } @@ -72,6 +89,8 @@ static void redraw_image(void) { GtkSpinButton *spin; + refresh_palette(); + spin = GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "framespin")); set_frame(spin, NULL); } @@ -101,13 +120,13 @@ static void tick(void *p, double delta) while (elapsed > seconds_per_frame) { elapsed -= seconds_per_frame; - if (++newframe >= info.nframes) { - if (!info.looping) { + if (++newframe >= image->frames) { + if (image->leadin == image->frames - 1) { gtk_toggle_button_set_active(play, FALSE); break; } - newframe = info.loopstart; + newframe = image->leadin; } } @@ -137,39 +156,46 @@ static gboolean timeout(gpointer data) gboolean canvas_expose(GtkWidget *canvas, GdkEventExpose *event, gpointer data) { - if (!framebuf) - return FALSE; + cairo_t *cr; - 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); - 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); + 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); + } + + cairo_set_source_surface(cr, framebuf, 0, 0); + gdk_cairo_rectangle(cr, &event->area); + cairo_fill(cr); + + cairo_destroy(cr); return TRUE; } -static int alloc_framebuffer(struct lbx_image *image) +static int alloc_framebuffer(struct lbx_image *img) { - struct lbx_imginfo info; GtkSpinButton *spin; - if (framebuf) - g_object_unref(framebuf); - - lbx_img_getinfo(image, &info); - framebuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, - info.width, info.height); - g_return_val_if_fail(framebuf, -1); + cairo_surface_destroy(framebuf); + framebuf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + img->width, img->height); spin = GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "framespin")); - gtk_spin_button_set_range(spin, 0, info.nframes-1); + gtk_spin_button_set_range(spin, 0, img->frames-1); gtk_spin_button_set_value(spin, 0); - gtk_widget_set_size_request(canvas, info.width, info.height); + gtk_widget_set_size_request(canvas, img->width, img->height); return 0; } @@ -272,10 +298,7 @@ void set_image(GtkComboBox *combo) lbx_img_close(img); } - if (alloc_framebuffer(img) == -1) { - puts("crap"); - lbx_img_close(img); - } + alloc_framebuffer(img); } image = img; @@ -284,19 +307,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(); } @@ -448,18 +459,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); - bitmap = gdk_bitmap_create_from_data(drawable, (gchar *)bg_bits, - bg_width, bg_height); + return s; +} + +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); @@ -468,6 +533,7 @@ static void init_background(GdkDrawable *drawable) int main(int argc, char **argv) { + extern char lbxgui_glade[]; GtkWidget *window; GError *err = NULL; @@ -482,7 +548,7 @@ int main(int argc, char **argv) load_archive(*argv++); builder = gtk_builder_new(); - if (!gtk_builder_add_from_string(builder, lbxgui_xml, -1, &err)) { + if (!gtk_builder_add_from_string(builder, lbxgui_glade, -1, &err)) { fprintf(stderr, "%s\n", err->message); return EXIT_FAILURE; }