From 4a19157ff85714553145578f5d05b99705cacb90 Mon Sep 17 00:00:00 2001 From: pascal-niklaus Date: Sun, 24 Mar 2024 11:04:22 +0100 Subject: [PATCH] main: add unlimited (100x) undo buffer with compression * main: compression * main: undo buffer lz4 compression * main: undo buffer lz4 compression * undo-unrelated code removed * undo-unrelated code removed, 2nd attempt * CMakelist fixed * --amend --------- Co-authored-by: Christian Beier --- CMakeLists.txt | 4 ++ src/main.c | 108 ++++++++++++++++++++++++++++++++++++------------- src/main.h | 18 ++++++--- 3 files changed, 97 insertions(+), 33 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a183cf0..d64d92e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,7 @@ if(NOT appindicator3_FOUND) pkg_check_modules(appindicator3 REQUIRED "appindicator3-0.1 >= 0.4.92") set(APPINDICATOR_IS_LEGACY 1) endif() +pkg_check_modules(lz4 REQUIRED liblz4) configure_file(build-config.h_cmake_in build-config.h) @@ -38,6 +39,7 @@ include_directories( ${appindicator3_INCLUDE_DIRS} ${xinput_INCLUDE_DIRS} ${x11_INCLUDE_DIRS} + ${lz4_INCLUDE_DIRS} ) link_directories( @@ -45,6 +47,7 @@ link_directories( ${appindicator3_LIBRARY_DIRS} ${xinput_LIBRARY_DIRS} ${x11_LIBRARY_DIRS} + ${lz4_LIBRARY_DIRS} ) set(sources @@ -71,6 +74,7 @@ target_link_libraries(${target_name} ${appindicator3_LIBRARIES} ${xinput_LIBRARIES} ${x11_LIBRARIES} + ${lz4_LIBRARIES} -lm ) diff --git a/src/main.c b/src/main.c index 76fe92c..f30cdbe 100644 --- a/src/main.c +++ b/src/main.c @@ -1,4 +1,4 @@ -/* +/* * Gromit-MPX -- a program for painting on the screen * * Gromit Copyright (C) 2000 Simon Budig @@ -23,6 +23,7 @@ #include #include +#include #include "callbacks.h" #include "config.h" @@ -430,7 +431,8 @@ void snap_undo_state (GromitData *data) if(data->debug) g_printerr ("DEBUG: Snapping undo buffer %d.\n", data->undo_head); - copy_surface(data->undobuffer[data->undo_head], data->backbuffer); + undo_compress(data, data->backbuffer); + undo_temp_buffer_to_slot(data, data->undo_head); // Increment head position data->undo_head++; @@ -456,23 +458,8 @@ void copy_surface (cairo_surface_t *dst, cairo_surface_t *src) } - -void swap_surfaces (cairo_surface_t *a, cairo_surface_t *b) -{ - int width = cairo_image_surface_get_width(a); - int height = cairo_image_surface_get_height(a); - cairo_surface_t *temp = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); - copy_surface(temp, a); - copy_surface(a, b); - copy_surface(b, temp); - cairo_surface_destroy(temp); -} - - - void undo_drawing (GromitData *data) { - /* Swap undobuffer[head-1]->backbuffer */ if(data->undo_depth <= 0) return; data->undo_depth--; @@ -483,7 +470,9 @@ void undo_drawing (GromitData *data) if(data->undo_head < 0) data->undo_head += GROMIT_MAX_UNDO; - swap_surfaces(data->backbuffer, data->undobuffer[data->undo_head]); + undo_compress(data, data->backbuffer); + undo_decompress(data, data->undo_head, data->backbuffer); + undo_temp_buffer_to_slot(data, data->undo_head); GdkRectangle rect = {0, 0, data->width, data->height}; gdk_window_invalidate_rect(gtk_widget_get_window(data->win), &rect, 0); @@ -501,7 +490,9 @@ void redo_drawing (GromitData *data) if(data->redo_depth <= 0) return; - swap_surfaces(data->backbuffer, data->undobuffer[data->undo_head]); + undo_compress(data, data->backbuffer); + undo_decompress(data, data->undo_head, data->backbuffer); + undo_temp_buffer_to_slot(data, data->undo_head); data->redo_depth--; data->undo_depth++; @@ -513,15 +504,74 @@ void redo_drawing (GromitData *data) gdk_window_invalidate_rect(gtk_widget_get_window(data->win), &rect, 0); data->modified = 1; - + if(data->debug) g_printerr("DEBUG: Redo drawing.\n"); } +/* + * compress image data and store it in undo_temp_buffer + * + * the undo_temp_buffer is successively grown in case it is too small + */ +void undo_compress(GromitData *data, cairo_surface_t *surface) +{ + char *raw_data = (char *)cairo_image_surface_get_data(surface); + guint bytes_per_row = cairo_image_surface_get_stride(surface); + guint rows = cairo_image_surface_get_height(surface); + size_t src_bytes = rows * bytes_per_row; + size_t dest_bytes; + for (;;) + { + dest_bytes = LZ4_compress_default(raw_data, data->undo_temp, src_bytes, data->undo_temp_size); + if (dest_bytes == 0) + { + data->undo_temp_size *= 2; + data->undo_temp = g_realloc(data->undo_temp, data->undo_temp_size); + } + else + { + data->undo_temp_used = dest_bytes; + break; + } + } +} +/* + * copy undo_temp to undo slot, growing undo buffer if required + */ +void undo_temp_buffer_to_slot(GromitData *data, gint undo_slot) +{ + const size_t required = data->undo_temp_used; + if (data->undo_buffer_size[undo_slot] < required) + { + data->undo_buffer[undo_slot] = g_realloc(data->undo_buffer[undo_slot], required); + data->undo_buffer_size[undo_slot] = required; + } + data->undo_buffer_used[undo_slot] = required; + memcpy(data->undo_buffer[undo_slot], data->undo_temp, required); +} + +/* + * decompress undo slot data and store it in cairo surface + */ +void undo_decompress(GromitData *data, gint undo_slot, cairo_surface_t *surface) +{ + char *dest_data = (char *)cairo_image_surface_get_data(surface); + guint bytes_per_row = cairo_image_surface_get_stride(surface); + guint rows = cairo_image_surface_get_height(surface); + size_t dest_bytes = rows * bytes_per_row; + + char *src_data = data->undo_buffer[undo_slot]; + size_t src_bytes = data->undo_buffer_used[undo_slot]; + if (LZ4_decompress_safe(src_data, dest_data, src_bytes, dest_bytes) < 0) { + g_printerr("Fatal error occurred decompressing image data\n"); + exit(1); + } +} /* * Functions for handling various (GTK+)-Events @@ -625,23 +675,25 @@ void setup_main_app (GromitData *data, int argc, char ** argv) cairo_surface_destroy(data->backbuffer); data->backbuffer = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, data->width, data->height); + // original state for LINE and RECT tool + cairo_surface_destroy(data->aux_backbuffer); + data->aux_backbuffer = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, data->width, data->height); + /* UNDO STATE */ data->undo_head = 0; data->undo_depth = 0; data->redo_depth = 0; - int i; - for (i = 0; i < GROMIT_MAX_UNDO; i++) + data->undo_temp_size = 0x10000; + data->undo_temp = g_malloc(data->undo_temp_size); + data->undo_temp_used = 0; + for (int i = 0; i < GROMIT_MAX_UNDO; i++) { - cairo_surface_destroy(data->undobuffer[i]); - data->undobuffer[i] = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, data->width, data->height); + data->undo_buffer_size[i] = 0; + data->undo_buffer[i] = NULL; } - // original state for LINE and RECT tool - cairo_surface_destroy(data->aux_backbuffer); - data->aux_backbuffer = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, data->width, data->height); - /* EVENTS */ gtk_widget_add_events (data->win, GROMIT_WINDOW_EVENTS); g_signal_connect (data->win, "draw", diff --git a/src/main.h b/src/main.h index 2265313..77ae382 100644 --- a/src/main.h +++ b/src/main.h @@ -59,7 +59,7 @@ #define GA_TOGGLEDATA gdk_atom_intern ("Gromit/toggledata", FALSE) #define GA_LINEDATA gdk_atom_intern ("Gromit/linedata", FALSE) -#define GROMIT_MAX_UNDO 4 +#define GROMIT_MAX_UNDO 100 typedef enum { @@ -160,8 +160,14 @@ typedef struct gchar *clientdata; - cairo_surface_t *undobuffer[GROMIT_MAX_UNDO]; - gint undo_head, undo_depth, redo_depth; + /* undo buffer */ + gchar *undo_buffer[GROMIT_MAX_UNDO]; + size_t undo_buffer_size[GROMIT_MAX_UNDO]; + size_t undo_buffer_used[GROMIT_MAX_UNDO]; + gchar *undo_temp; + size_t undo_temp_size; + size_t undo_temp_used; + gint undo_head, undo_depth, redo_depth; gboolean show_intro_on_startup; @@ -177,10 +183,12 @@ void parse_print_help (gpointer key, gpointer value, gpointer user_data); void select_tool (GromitData *data, GdkDevice *device, GdkDevice *slave_device, guint state); void copy_surface (cairo_surface_t *dst, cairo_surface_t *src); -void swap_surfaces (cairo_surface_t *a, cairo_surface_t *b); -void snap_undo_state (GromitData *data); +void snap_undo_state(GromitData *data); void undo_drawing (GromitData *data); void redo_drawing (GromitData *data); +void undo_compress(GromitData *data, cairo_surface_t *surface); +void undo_temp_buffer_to_slot(GromitData *data, gint undo_slot); +void undo_decompress(GromitData *data, gint undo_slot, cairo_surface_t *surface); void clear_screen (GromitData *data);