Center view rotations/flips

This commit is contained in:
Přemysl Eric Janouch 2022-10-03 22:57:39 +02:00
parent 7b88e89489
commit 9cdc641b0a
Signed by: p
GPG Key ID: A0420B94F92B9493
2 changed files with 96 additions and 36 deletions

View File

@ -165,7 +165,8 @@ typedef enum _FivIoOrientation {
FivIoOrientation270 = 8 FivIoOrientation270 = 8
} FivIoOrientation; } FivIoOrientation;
/// Returns a rendering matrix for a surface, and its target dimensions. /// Returns a rendering matrix for a surface (user space to pattern space),
/// and its target dimensions.
cairo_matrix_t fiv_io_orientation_apply(cairo_surface_t *surface, cairo_matrix_t fiv_io_orientation_apply(cairo_surface_t *surface,
FivIoOrientation orientation, double *width, double *height); FivIoOrientation orientation, double *width, double *height);
void fiv_io_orientation_dimensions(cairo_surface_t *surface, void fiv_io_orientation_dimensions(cairo_surface_t *surface,

View File

@ -191,20 +191,19 @@ get_display_dimensions(FivView *self, int *width, int *height)
static void static void
update_adjustments(FivView *self) update_adjustments(FivView *self)
{ {
int w = 0, h = 0; int dw = 0, dh = 0;
get_display_dimensions(self, &w, &h); get_display_dimensions(self, &dw, &dh);
GtkAllocation alloc; GtkAllocation alloc;
gtk_widget_get_allocation(GTK_WIDGET(self), &alloc); gtk_widget_get_allocation(GTK_WIDGET(self), &alloc);
if (self->hadjustment) { if (self->hadjustment) {
gtk_adjustment_configure(self->hadjustment, gtk_adjustment_configure(self->hadjustment,
gtk_adjustment_get_value(self->hadjustment), 0, w, gtk_adjustment_get_value(self->hadjustment), 0, dw,
alloc.width * 0.1, alloc.width * 0.9, alloc.width); alloc.width * 0.1, alloc.width * 0.9, alloc.width);
} }
if (self->vadjustment) { if (self->vadjustment) {
gtk_adjustment_configure(self->vadjustment, gtk_adjustment_configure(self->vadjustment,
gtk_adjustment_get_value(self->vadjustment), 0, h, gtk_adjustment_get_value(self->vadjustment), 0, dh,
alloc.height * 0.1, alloc.height * 0.9, alloc.height); alloc.height * 0.1, alloc.height * 0.9, alloc.height);
} }
} }
@ -576,8 +575,8 @@ fiv_view_draw(GtkWidget *widget, cairo_t *cr)
!gtk_cairo_should_draw_window(cr, gtk_widget_get_window(widget))) !gtk_cairo_should_draw_window(cr, gtk_widget_get_window(widget)))
return TRUE; return TRUE;
int w, h; int dw, dh;
get_display_dimensions(self, &w, &h); get_display_dimensions(self, &dw, &dh);
double x = 0; double x = 0;
double y = 0; double y = 0;
@ -585,11 +584,13 @@ fiv_view_draw(GtkWidget *widget, cairo_t *cr)
x = -floor(gtk_adjustment_get_value(self->hadjustment)); x = -floor(gtk_adjustment_get_value(self->hadjustment));
if (self->vadjustment) if (self->vadjustment)
y = -floor(gtk_adjustment_get_value(self->vadjustment)); y = -floor(gtk_adjustment_get_value(self->vadjustment));
if (w < allocation.width) if (dw < allocation.width)
x = round((allocation.width - w) / 2.); x = round((allocation.width - dw) / 2.);
if (h < allocation.height) if (dh < allocation.height)
y = round((allocation.height - h) / 2.); y = round((allocation.height - dh) / 2.);
// XXX: This naming is confusing, because it isn't actually for the surface,
// but rather for our possibly rotated rendition of it.
Dimensions surface_dimensions = {}; Dimensions surface_dimensions = {};
cairo_matrix_t matrix = fiv_io_orientation_apply( cairo_matrix_t matrix = fiv_io_orientation_apply(
self->page_scaled ? self->page_scaled : self->page, self->orientation, self->page_scaled ? self->page_scaled : self->page, self->orientation,
@ -599,7 +600,7 @@ fiv_view_draw(GtkWidget *widget, cairo_t *cr)
if (self->checkerboard) { if (self->checkerboard) {
gtk_style_context_save(style); gtk_style_context_save(style);
gtk_style_context_add_class(style, "checkerboard"); gtk_style_context_add_class(style, "checkerboard");
gtk_render_background(style, cr, 0, 0, w, h); gtk_render_background(style, cr, 0, 0, dw, dh);
gtk_style_context_restore(style); gtk_style_context_restore(style);
} }
@ -615,7 +616,7 @@ fiv_view_draw(GtkWidget *widget, cairo_t *cr)
// we always get a shitty pixmap, where transparency contains junk. // we always get a shitty pixmap, where transparency contains junk.
if (cairo_surface_get_type(self->frame) == CAIRO_SURFACE_TYPE_RECORDING) { if (cairo_surface_get_type(self->frame) == CAIRO_SURFACE_TYPE_RECORDING) {
cairo_surface_t *image = cairo_surface_t *image =
cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h); cairo_image_surface_create(CAIRO_FORMAT_ARGB32, dw, dh);
cairo_t *tcr = cairo_create(image); cairo_t *tcr = cairo_create(image);
cairo_scale(tcr, self->scale, self->scale); cairo_scale(tcr, self->scale, self->scale);
cairo_set_source_surface(tcr, self->frame, 0, 0); cairo_set_source_surface(tcr, self->frame, 0, 0);
@ -631,7 +632,7 @@ fiv_view_draw(GtkWidget *widget, cairo_t *cr)
// XXX: The rounding together with padding may result in up to // XXX: The rounding together with padding may result in up to
// a pixel's worth of made-up picture data. // a pixel's worth of made-up picture data.
cairo_rectangle(cr, 0, 0, w, h); cairo_rectangle(cr, 0, 0, dw, dh);
cairo_clip(cr); cairo_clip(cr);
cairo_scale(cr, self->scale, self->scale); cairo_scale(cr, self->scale, self->scale);
@ -689,6 +690,28 @@ set_scale_to_fit(FivView *self, bool scale_to_fit)
return TRUE; return TRUE;
} }
static void
widget_to_surface(FivView *self, double *x, double *y)
{
int dw, dh;
get_display_dimensions(self, &dw, &dh);
GtkAllocation allocation;
gtk_widget_get_allocation(GTK_WIDGET(self), &allocation);
// Unneeded, thus unimplemented: this means zero adjustment values.
if (!self->hadjustment || !self->vadjustment)
return;
*x = (*x + (dw < allocation.width
? -round((allocation.width - dw) / 2.)
: +floor(gtk_adjustment_get_value(self->hadjustment))))
/ self->scale;
*y = (*y + (dh < allocation.height
? -round((allocation.height - dh) / 2.)
: +floor(gtk_adjustment_get_value(self->vadjustment))))
/ self->scale;
}
static gboolean static gboolean
set_scale(FivView *self, double scale, const GdkEvent *event) set_scale(FivView *self, double scale, const GdkEvent *event)
{ {
@ -703,34 +726,25 @@ set_scale(FivView *self, double scale, const GdkEvent *event)
GtkAllocation allocation; GtkAllocation allocation;
gtk_widget_get_allocation(GTK_WIDGET(self), &allocation); gtk_widget_get_allocation(GTK_WIDGET(self), &allocation);
double focus_x = 0, focus_y = 0;
double focus_x = 0, focus_y = 0, surface_x = 0, surface_y = 0;
if (!event || !gdk_event_get_coords(event, &focus_x, &focus_y)) { if (!event || !gdk_event_get_coords(event, &focus_x, &focus_y)) {
focus_x = 0.5 * allocation.width; focus_x = 0.5 * allocation.width;
focus_y = 0.5 * allocation.height; focus_y = 0.5 * allocation.height;
} }
if (self->hadjustment && self->vadjustment) {
int w, h;
get_display_dimensions(self, &w, &h);
surface_x = (focus_x + (w < allocation.width double surface_x = focus_x;
? -round((allocation.width - w) / 2.) double surface_y = focus_y;
: +floor(gtk_adjustment_get_value(self->hadjustment)))) widget_to_surface(self, &surface_x, &surface_y);
/ self->scale;
surface_y = (focus_y + (h < allocation.height
? -round((allocation.height - h) / 2.)
: +floor(gtk_adjustment_get_value(self->vadjustment))))
/ self->scale;
}
self->scale = scale; self->scale = scale;
g_object_notify_by_pspec(G_OBJECT(self), view_properties[PROP_SCALE]); g_object_notify_by_pspec(G_OBJECT(self), view_properties[PROP_SCALE]);
prescale_page(self); prescale_page(self);
// Similar to set_orientation().
if (self->hadjustment && self->vadjustment) { if (self->hadjustment && self->vadjustment) {
Dimensions surface_dimensions = get_surface_dimensions(self);
update_adjustments(self); update_adjustments(self);
Dimensions surface_dimensions = get_surface_dimensions(self);
if (surface_dimensions.width * self->scale > allocation.width) if (surface_dimensions.width * self->scale > allocation.width)
gtk_adjustment_set_value( gtk_adjustment_set_value(
self->hadjustment, surface_x * self->scale - focus_x); self->hadjustment, surface_x * self->scale - focus_x);
@ -1036,6 +1050,7 @@ on_draw_page(G_GNUC_UNUSED GtkPrintOperation *operation,
// Any DPI will be wrong, unless we import that information from the image. // Any DPI will be wrong, unless we import that information from the image.
double scale = 1 / 96.; double scale = 1 / 96.;
Dimensions surface_dimensions = {}; Dimensions surface_dimensions = {};
// XXX: Perhaps use self->frame, even though their sizes should match.
cairo_matrix_t matrix = cairo_matrix_t matrix =
fiv_io_orientation_apply(self->page, self->orientation, fiv_io_orientation_apply(self->page, self->orientation,
&surface_dimensions.width, &surface_dimensions.height); &surface_dimensions.width, &surface_dimensions.height);
@ -1457,6 +1472,53 @@ swap_enhanced_image(FivView *self)
} }
} }
static void
transformed_to_real(FivView *self, double *x, double *y)
{
double sw = 0, sh = 0;
cairo_matrix_t matrix =
fiv_io_orientation_apply(self->page, self->orientation, &sw, &sh);
cairo_matrix_transform_point(&matrix, x, y);
}
static void
set_orientation(FivView *self, FivIoOrientation orientation)
{
GtkAllocation allocation;
gtk_widget_get_allocation(GTK_WIDGET(self), &allocation);
// In the future, rotating gestures can pick another centre point.
double focus_x = 0.5 * allocation.width;
double focus_y = 0.5 * allocation.height;
double surface_x = focus_x;
double surface_y = focus_y;
widget_to_surface(self, &surface_x, &surface_y);
transformed_to_real(self, &surface_x, &surface_y);
self->orientation = orientation;
// Similar to set_scale().
Dimensions surface_dimensions = {};
cairo_matrix_t matrix =
fiv_io_orientation_apply(self->page, self->orientation,
&surface_dimensions.width, &surface_dimensions.height);
if (self->hadjustment && self->vadjustment &&
cairo_matrix_invert(&matrix) == CAIRO_STATUS_SUCCESS) {
cairo_matrix_transform_point(&matrix, &surface_x, &surface_y);
update_adjustments(self);
if (surface_dimensions.width * self->scale > allocation.width)
gtk_adjustment_set_value(
self->hadjustment, surface_x * self->scale - focus_x);
if (surface_dimensions.height * self->scale > allocation.height)
gtk_adjustment_set_value(
self->vadjustment, surface_y * self->scale - focus_y);
}
gtk_widget_queue_resize(GTK_WIDGET(self));
}
void void
fiv_view_command(FivView *self, FivViewCommand command) fiv_view_command(FivView *self, FivViewCommand command)
{ {
@ -1471,14 +1533,11 @@ fiv_view_command(FivView *self, FivViewCommand command)
reload(self); reload(self);
break; case FIV_VIEW_COMMAND_ROTATE_LEFT: break; case FIV_VIEW_COMMAND_ROTATE_LEFT:
self->orientation = view_left[self->orientation]; set_orientation(self, view_left[self->orientation]);
gtk_widget_queue_resize(widget);
break; case FIV_VIEW_COMMAND_MIRROR: break; case FIV_VIEW_COMMAND_MIRROR:
self->orientation = view_mirror[self->orientation]; set_orientation(self, view_mirror[self->orientation]);
gtk_widget_queue_resize(widget);
break; case FIV_VIEW_COMMAND_ROTATE_RIGHT: break; case FIV_VIEW_COMMAND_ROTATE_RIGHT:
self->orientation = view_right[self->orientation]; set_orientation(self, view_right[self->orientation]);
gtk_widget_queue_resize(widget);
break; case FIV_VIEW_COMMAND_PAGE_FIRST: break; case FIV_VIEW_COMMAND_PAGE_FIRST:
switch_page(self, self->image); switch_page(self, self->image);