From 9b72571596f9313d4efd94a4b17da8b3733fd2de Mon Sep 17 00:00:00 2001 From: Jeremy Attali Date: Mon, 15 Feb 2021 15:01:25 -0500 Subject: [PATCH] fix(ui): adjust rendering surface with proper scaling Closes #54 --- include/swappy.h | 2 ++ src/application.c | 61 +++++++++++++++++++++++++++++++++-------------- src/paint.c | 8 ------- src/pixbuf.c | 35 +++++++++++++++++++-------- 4 files changed, 70 insertions(+), 36 deletions(-) diff --git a/include/swappy.h b/include/swappy.h index dec10bb..f766ad9 100644 --- a/include/swappy.h +++ b/include/swappy.h @@ -153,6 +153,8 @@ struct swappy_state { cairo_surface_t *scaled_image_surface; cairo_surface_t *rendered_surface; + gdouble scaling_factor; + enum swappy_paint_type mode; /* Options */ diff --git a/src/application.c b/src/application.c index 923be68..ac994de 100644 --- a/src/application.c +++ b/src/application.c @@ -199,6 +199,25 @@ static void maybe_save_output_file(struct swappy_state *state) { } } +static void screen_coordinates_to_image_coordinates(struct swappy_state *state, + gdouble screen_x, + gdouble screen_y, + gdouble *image_x, + gdouble *image_y) { + gdouble x, y; + + gint w = gdk_pixbuf_get_width(state->original_image); + gint h = gdk_pixbuf_get_height(state->original_image); + + // Clamp coordinates to original image properties to avoid side effects in + // rendering pipeline + x = CLAMP(screen_x / state->scaling_factor, 0, w); + y = CLAMP(screen_y / state->scaling_factor, 0, h); + + *image_x = x; + *image_y = y; +} + void on_destroy(GtkApplication *application, gpointer data) { struct swappy_state *state = (struct swappy_state *)data; maybe_save_output_file(state); @@ -383,6 +402,16 @@ void redo_clicked_handler(GtkWidget *widget, struct swappy_state *state) { gboolean draw_area_handler(GtkWidget *widget, cairo_t *cr, struct swappy_state *state) { + GtkAllocation *alloc = g_new(GtkAllocation, 1); + gtk_widget_get_allocation(widget, alloc); + + GdkPixbuf *image = state->original_image; + gint image_width = gdk_pixbuf_get_width(image); + gint image_height = gdk_pixbuf_get_height(image); + double scale_x = (double)alloc->width / image_width; + double scale_y = (double)alloc->height / image_height; + + cairo_scale(cr, scale_x, scale_y); cairo_set_source_surface(cr, state->rendered_surface, 0, 0); cairo_paint(cr); @@ -393,18 +422,6 @@ gboolean draw_area_configure_handler(GtkWidget *widget, GdkEventConfigure *event, struct swappy_state *state) { g_debug("received configure_event callback"); - cairo_surface_destroy(state->rendered_surface); - - cairo_surface_t *surface = gdk_window_create_similar_surface( - gtk_widget_get_window(widget), CAIRO_CONTENT_COLOR_ALPHA, - gtk_widget_get_allocated_width(widget), - gtk_widget_get_allocated_height(widget)); - - g_info("size of drawing area surface: %ux%u", - cairo_image_surface_get_width(surface), - cairo_image_surface_get_height(surface)); - - state->rendered_surface = surface; pixbuf_scale_surface_from_widget(state, widget); @@ -415,6 +432,10 @@ gboolean draw_area_configure_handler(GtkWidget *widget, void draw_area_button_press_handler(GtkWidget *widget, GdkEventButton *event, struct swappy_state *state) { + gdouble x, y; + + screen_coordinates_to_image_coordinates(state, event->x, event->y, &x, &y); + if (event->button == 1) { switch (state->mode) { case SWAPPY_PAINT_MODE_BLUR: @@ -423,7 +444,7 @@ void draw_area_button_press_handler(GtkWidget *widget, GdkEventButton *event, case SWAPPY_PAINT_MODE_ELLIPSE: case SWAPPY_PAINT_MODE_ARROW: case SWAPPY_PAINT_MODE_TEXT: - paint_add_temporary(state, event->x, event->y, state->mode); + paint_add_temporary(state, x, y, state->mode); render_state(state); update_ui_undo_redo(state); break; @@ -434,8 +455,10 @@ void draw_area_button_press_handler(GtkWidget *widget, GdkEventButton *event, } void draw_area_motion_notify_handler(GtkWidget *widget, GdkEventMotion *event, struct swappy_state *state) { - gdouble x = event->x; - gdouble y = event->y; + gdouble x, y; + + screen_coordinates_to_image_coordinates(state, event->x, event->y, &x, &y); + GdkDisplay *display = gdk_display_get_default(); GdkWindow *window = event->window; GdkCursor *crosshair = gdk_cursor_new_for_display(display, GDK_CROSSHAIR); @@ -539,7 +562,7 @@ void text_size_increase_handler(GtkWidget *widget, struct swappy_state *state) { action_text_size_increase(state); } -static void compute_window_size(struct swappy_state *state) { +static void compute_window_size_and_scaling_factor(struct swappy_state *state) { GdkRectangle workarea = {0}; GdkDisplay *display = gdk_display_get_default(); GdkWindow *window = gtk_widget_get_window(GTK_WIDGET(state->ui->window)); @@ -554,6 +577,7 @@ static void compute_window_size(struct swappy_state *state) { state->window->y = workarea.y; double threshold = 0.75; + double scaling_factor = 1.0; int image_width = gdk_pixbuf_get_width(state->original_image); int image_height = gdk_pixbuf_get_height(state->original_image); @@ -572,13 +596,14 @@ static void compute_window_size(struct swappy_state *state) { double scaling_factor_height = (double)max_height / image_height; if (scaling_factor_height < 1.0 || scaling_factor_width < 1.0) { - double scaling_factor = MIN(scaling_factor_width, scaling_factor_height); + scaling_factor = MIN(scaling_factor_width, scaling_factor_height); scaled_width = image_width * scaling_factor; scaled_height = image_height * scaling_factor; g_info("rendering area will be scaled by a factor of: %.2lf", scaling_factor); } + state->scaling_factor = scaling_factor; state->window->width = scaled_width; state->window->height = scaled_height; @@ -672,7 +697,7 @@ static bool load_layout(struct swappy_state *state) { state->ui->area = area; state->ui->window = window; - compute_window_size(state); + compute_window_size_and_scaling_factor(state); gtk_widget_set_size_request(area, state->window->width, state->window->height); action_toggle_painting_panel(state, &state->config->show_panel); diff --git a/src/paint.c b/src/paint.c index 0975c1f..fbbd2f2 100644 --- a/src/paint.c +++ b/src/paint.c @@ -154,14 +154,6 @@ void paint_update_temporary_shape(struct swappy_state *state, double x, return; } - int32_t width = state->window->width; - int32_t height = state->window->height; - - // Bounding x and y to the window dimensions to avoid side effects in - // rendering. - x = MIN(MAX(x, 0), width); - y = MIN(MAX(y, 0), height); - switch (paint->type) { case SWAPPY_PAINT_MODE_BLUR: paint->can_draw = true; diff --git a/src/pixbuf.c b/src/pixbuf.c index f06e172..3840e4c 100644 --- a/src/pixbuf.c +++ b/src/pixbuf.c @@ -96,32 +96,47 @@ void pixbuf_scale_surface_from_widget(struct swappy_state *state, cairo_format_t format = has_alpha ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24; gint image_width = gdk_pixbuf_get_width(image); gint image_height = gdk_pixbuf_get_height(image); - cairo_surface_t *surface = + + cairo_surface_t *scaled_surface = cairo_image_surface_create(format, image_width, image_height); - if (!surface) { + if (!scaled_surface) { g_error("unable to create cairo surface from pixbuf"); - goto cleanup; + goto finish; } else { cairo_t *cr; - cr = cairo_create(surface); - double scale_x = (double)alloc->width / image_width; - double scale_y = (double)alloc->height / image_height; - g_info("image scaled on x,y: %.2lf,%.2lf", scale_x, scale_y); - cairo_scale(cr, scale_x, scale_y); + cr = cairo_create(scaled_surface); + // double scale_x = (double)alloc->width / image_width; + // double scale_y = (double)alloc->height / image_height; + // g_info("image scaled on x,y: %.2lf,%.2lf", scale_x, scale_y); + // cairo_scale(cr, scale_x, scale_y); gdk_cairo_set_source_pixbuf(cr, image, 0, 0); cairo_paint(cr); cairo_destroy(cr); } + cairo_surface_t *rendered_surface = + cairo_image_surface_create(format, image_width, image_height); + + if (!rendered_surface) { + g_error("unable to create rendering surface"); + goto finish; + } + g_info("size of area to render: %ux%u", alloc->width, alloc->height); +finish: if (state->scaled_image_surface) { cairo_surface_destroy(state->scaled_image_surface); state->scaled_image_surface = NULL; } - state->scaled_image_surface = surface; + state->scaled_image_surface = scaled_surface; + + if (state->rendered_surface) { + cairo_surface_destroy(state->rendered_surface); + state->rendered_surface = NULL; + } + state->rendered_surface = rendered_surface; -cleanup: g_free(alloc); }