When I took immunology I had the oportunity to spin the knobs on a SEM. Mostly just an observer as the professor set up the specimen and took the pictures. Did get to colorize a hybridoma picture for my lab notebook. A good experience.
It sounds like java works well for viewing the pictures. With GTK you should be able to translate and zoom without problem. Not sure about buffering the image into memory. Have a few ideas about it. If you have a 64 bit computer you should be able to read the image directly into memory. Wasteful but possible for testing. The cairo surface limit should allow a 25000 x 25000 picture.
Defined in cairo_image_surface.c
* Limit on the width / height of an image surface in pixels. This is
* mainly determined by coordinates of things sent to pixman at the
* moment being in 16.16 format.
#define MAX_IMAGE_SIZE 32767
Is the picture file in a “raw” format? If so you could seek and read just the bytes that you need. With a ssd this should be fast. Or buffer the bytes from the file. Is the image that you are using a buffered image in java or an image read entirely into memory? Are you working with raw files or compressed files?
I haven’t used GEGL but it looks like you can tune a few things there and it has everything that you would need.
You should be able to achieve the same functionality in GTK. Might have to look around a bit though. The following is big_surface version 2. It should be similar to the java drawImage() but without a buffered file read. Didn’t get that far with it.
Eric
/*
gcc -Wall big_surface2.c -o big_surface2 `pkg-config --cflags --libs gtk+-3.0`
Tested on Ubuntu18.04 and GTK3.22
*/
#include<gtk/gtk.h>
#include<stdio.h>
static gdouble translate_x=0.0;
static gdouble translate_y=0.0;
static gdouble scale=1.0;
//Store data from file.
static unsigned char *big_surface_data=NULL;
static void save_big_surface();
static cairo_surface_t* get_big_surface();
static void translate_x_spin_changed(GtkSpinButton *spin_button, gpointer data);
static void translate_y_spin_changed(GtkSpinButton *spin_button, gpointer data);
static void scale_spin_changed(GtkSpinButton *spin_button, gpointer data);
static gboolean da_drawing(GtkWidget *da, cairo_t *cr, cairo_surface_t *big_surface);
int main(int argc, char **argv)
{
gtk_init(&argc, &argv);
GtkWidget *window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), "Big Surface2");
gtk_window_set_default_size(GTK_WINDOW(window), 500, 500);
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
//Get a test surface.
save_big_surface();
cairo_surface_t *big_surface=get_big_surface();
GtkWidget *da=gtk_drawing_area_new();
gtk_widget_set_hexpand(da, TRUE);
gtk_widget_set_vexpand(da, TRUE);
g_signal_connect(da, "draw", G_CALLBACK(da_drawing), big_surface);
GtkAdjustment *translate_x_adj=gtk_adjustment_new(0.0, 0.0, 5000.0, 20.0, 0.0, 0.0);
GtkAdjustment *translate_y_adj=gtk_adjustment_new(0.0, 0.0, 5000.0, 20.0, 0.0, 0.0);
GtkAdjustment *scale_adj=gtk_adjustment_new(1.0, 1.0, 5.0, 0.1, 0.0, 0.0);
GtkWidget *translate_x_label=gtk_label_new("translate x");
GtkWidget *translate_x_spin=gtk_spin_button_new(translate_x_adj, 50.0, 1);
g_signal_connect(translate_x_spin, "value-changed", G_CALLBACK(translate_x_spin_changed), da);
GtkWidget *translate_y_label=gtk_label_new("translate y");
GtkWidget *translate_y_spin=gtk_spin_button_new(translate_y_adj, 50.0, 1);
g_signal_connect(translate_y_spin, "value-changed", G_CALLBACK(translate_y_spin_changed), da);
GtkWidget *scale_label=gtk_label_new("Scale");
GtkWidget *scale_spin=gtk_spin_button_new(scale_adj, 0.2, 1);
g_signal_connect(scale_spin, "value-changed", G_CALLBACK(scale_spin_changed), da);
GtkWidget *grid=gtk_grid_new();
gtk_grid_attach(GTK_GRID(grid), da, 0, 0, 3, 1);
gtk_grid_attach(GTK_GRID(grid), translate_x_label, 0, 1, 1, 1);
gtk_grid_attach(GTK_GRID(grid), translate_y_label, 1, 1, 1, 1);
gtk_grid_attach(GTK_GRID(grid), scale_label, 2, 1, 1, 1);
gtk_grid_attach(GTK_GRID(grid), translate_x_spin, 0, 2, 1, 1);
gtk_grid_attach(GTK_GRID(grid), translate_y_spin, 1, 2, 1, 1);
gtk_grid_attach(GTK_GRID(grid), scale_spin, 2, 2, 1, 1);
gtk_container_add(GTK_CONTAINER(window), grid);
gtk_widget_show_all(window);
gtk_main();
//Clean up.
g_print("ref count %i\n", cairo_surface_get_reference_count(big_surface));
cairo_surface_destroy(big_surface);
free(big_surface_data);
return 0;
}
static void translate_x_spin_changed(GtkSpinButton *spin_button, gpointer data)
{
translate_x=gtk_spin_button_get_value(spin_button);
gtk_widget_queue_draw(GTK_WIDGET(data));
}
static void translate_y_spin_changed(GtkSpinButton *spin_button, gpointer data)
{
translate_y=gtk_spin_button_get_value(spin_button);
gtk_widget_queue_draw(GTK_WIDGET(data));
}
static void scale_spin_changed(GtkSpinButton *spin_button, gpointer data)
{
scale=gtk_spin_button_get_value(spin_button);
gtk_widget_queue_draw(GTK_WIDGET(data));
}
static void save_big_surface()
{
gint i=0;
//Use gdk_cairo_surface_create_from_pixbuf() to read in a pixbuf. Try a test surface here.
cairo_surface_t *big_surface=cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 5000, 5000);
cairo_t *cr=cairo_create(big_surface);
//Paint the background.
cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0);
cairo_paint(cr);
//Draw a circle.
cairo_set_source_rgba(cr, 0.0, 0.0, 1.0, 1.0);
cairo_arc(cr, 250.0, 250.0, 50.0, 0.0, 2.0*G_PI);
cairo_fill(cr);
//Draw some test grid lines.
cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, 1.0);
for(i=0;i<50;i++)
{
cairo_move_to(cr, 0.0, (gdouble)i*100.0);
cairo_line_to(cr, 5000.0, (gdouble)i*100.0);
cairo_stroke(cr);
}
for(i=0;i<50;i++)
{
cairo_move_to(cr, (gdouble)i*100, 0.0);
cairo_line_to(cr, (gdouble)i*100, 5000.0);
cairo_stroke(cr);
}
cairo_set_source_rgba(cr, 0.0, 0.0, 1.0, 1.0);
cairo_set_line_width(cr, 10.0);
for(i=0;i<10;i++)
{
cairo_move_to(cr, 0.0, (gdouble)i*500.0);
cairo_line_to(cr, 5000.0, (gdouble)i*500.0);
cairo_stroke(cr);
}
for(i=0;i<10;i++)
{
cairo_move_to(cr, (gdouble)i*500.0, 0.0);
cairo_line_to(cr, (gdouble)i*500.0, 5000.0);
cairo_stroke(cr);
}
//Outside box.
cairo_set_line_width(cr, 20.0);
cairo_set_source_rgba(cr, 1.0, 0.0, 1.0, 1.0);
cairo_rectangle(cr, 0.0, 0.0, 5000.0, 5000.0);
cairo_stroke(cr);
//Save surface data to file.
size_t len=0;
FILE *f=fopen("big_surface.s", "wb");
unsigned char *p=cairo_image_surface_get_data(big_surface);
len=fwrite(p, sizeof(unsigned char), 4*25000000, f);
g_print("write %i\n", len);
fclose(f);
cairo_destroy(cr);
cairo_surface_destroy(big_surface);
}
static cairo_surface_t* get_big_surface()
{
size_t len=0;
cairo_status_t status;
FILE *f=fopen("big_surface.s", "rb");
fseek(f, 0, SEEK_SET);
big_surface_data=(unsigned char*)malloc(4*25000000*sizeof(unsigned char));
if(big_surface_data==NULL) g_print("Null returned\n");
len=fread(big_surface_data, sizeof(unsigned char), 4*25000000, f);
g_print("read %i\n", len);
fclose(f);
cairo_surface_t *big_surface=cairo_image_surface_create_for_data(big_surface_data, CAIRO_FORMAT_ARGB32, 5000, 5000, cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, 5000));
cairo_surface_set_user_data(big_surface, NULL, big_surface_data, NULL);
cairo_surface_flush(big_surface);
status=cairo_surface_status(big_surface);
printf("open %s\n", cairo_status_to_string(status));
return big_surface;
}
static gboolean da_drawing(GtkWidget *da, cairo_t *cr, cairo_surface_t *big_surface)
{
gdouble width=(gdouble)gtk_widget_get_allocated_width(da);
gdouble height=(gdouble)gtk_widget_get_allocated_height(da);
gdouble origin_x=translate_x;
gdouble origin_y=translate_y;
//Some constraints.
if(translate_x>5000.0-width) origin_x=5000.0-width*1.0/scale;
if(translate_y>5000.0-height) origin_y=5000.0-height*1.0/scale;
cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
cairo_paint(cr);
//Partition the big surface.
cairo_surface_t *little_surface=cairo_surface_create_for_rectangle(big_surface, origin_x, origin_y, width*1.0/scale, height*1.0/scale);
cairo_scale(cr, scale, scale);
cairo_set_source_surface(cr, little_surface, 0.0, 0.0);
cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BILINEAR);
cairo_paint(cr);
cairo_surface_destroy(little_surface);
return TRUE;
}