1 /*
2  * This file is part of Siril, an astronomy image processor.
3  * Copyright (C) 2005-2011 Francois Meyer (dulle at free.fr)
4  * Copyright (C) 2012-2021 team free-astro (see more in AUTHORS file)
5  * Reference site is https://free-astro.org/index.php/Siril
6  *
7  * Siril is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * Siril is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with Siril. If not, see <http://www.gnu.org/licenses/>.
19 */
20 
21 #include "core/siril.h"
22 #include "core/proto.h"
23 #include "io/sequence.h"
24 #include "gui/utils.h"
25 #include "gui/callbacks.h"
26 #include "gui/progress_and_log.h"
27 #include "gui/image_interactions.h"
28 #include "gui/sequence_list.h"
29 #include "registration/registration.h"
30 
redraw_preview(GtkWidget * widget,cairo_t * cr,gpointer data)31 gboolean redraw_preview(GtkWidget *widget, cairo_t *cr, gpointer data) {
32 	int current_preview, shiftx = 0, shifty = 0;
33 	static GtkToggleButton *check_display_ref = NULL;
34 	static GtkWidget *labelRegRef = NULL;
35 	guint area_width = gtk_widget_get_allocated_width (widget);
36 	guint area_height = gtk_widget_get_allocated_height (widget);
37 	gboolean display_ref_image;
38 
39 	if (check_display_ref == NULL) {
40 		check_display_ref = GTK_TOGGLE_BUTTON(lookup_widget("checkbutton_displayref"));
41 		labelRegRef = lookup_widget("labelRegRef");
42 	}
43 	display_ref_image = com.refimage_regbuffer && com.refimage_surface
44 			&& gtk_toggle_button_get_active(check_display_ref)
45 							&& !gtk_widget_get_visible(labelRegRef);
46 
47 	if (widget == com.preview_area[0]) current_preview = 0;
48 	else if (widget == com.preview_area[1]) current_preview = 1;
49 	else {
50 		fprintf(stderr, "Uninitialized com.preview_area or unknown drawing area!\n");
51 		return TRUE;
52 	}
53 	// update previewW and previewH in case the drawing area was resized
54 	com.seq.previewW[current_preview] = area_width;
55 	com.seq.previewH[current_preview] = area_height;
56 
57 	/* fill preview bacground */
58 	cairo_rectangle(cr, 0, 0, area_width, area_height);
59 	cairo_fill(cr);
60 
61 	/* display current image with shifts */
62 	if (!com.preview_surface[current_preview]) {
63 		GtkStyleContext *context = gtk_widget_get_style_context(widget);
64 		GtkStateFlags state = gtk_widget_get_state_flags(widget);
65 		PangoLayout *layout;
66 		gchar *msg;
67 		GtkAllocation allocation;
68 		gdouble scale;
69 		GdkRGBA color;
70 		gint w, h;
71 
72 		layout = gtk_widget_create_pango_layout(widget, NULL);
73 
74 		if (sequence_is_loaded()) {
75 			msg = g_strdup_printf(_("Preview %d"), current_preview + 1);
76 		} else {
77 			msg = g_strdup(_("Load\nsequences"));
78 		}
79 		pango_layout_set_markup(layout, msg, -1);
80 		g_free(msg);
81 		pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER);
82 
83 		pango_layout_get_pixel_size(layout, &w, &h);
84 		gtk_widget_get_allocation(widget, &allocation);
85 
86 		scale = MIN(((gdouble) allocation.width / 3.0) / (gdouble ) w,
87 				((gdouble) allocation.height / 3.0) / (gdouble ) h);
88 
89 		gtk_style_context_get_color(context, state, &color);
90 		gdk_cairo_set_source_rgba(cr, &color);
91 
92 		cairo_move_to(cr, (allocation.width - (w * scale)) / 2,
93 				(allocation.height - (h * scale)) / 2);
94 
95 		cairo_scale(cr, scale, scale);
96 
97 		pango_cairo_show_layout(cr, layout);
98 
99 		g_object_unref(layout);
100 		return TRUE;
101 	}
102 	cairo_translate(cr, area_width / 2.0 - com.seq.previewX[current_preview],
103 			area_height/2.0-com.seq.previewY[current_preview]);
104 	if (com.seq.regparam && com.seq.regparam[com.cvport]) {
105 		shiftx = roundf_to_int(com.seq.regparam[com.cvport][com.seq.current].shiftx);
106 		shifty = roundf_to_int(com.seq.regparam[com.cvport][com.seq.current].shifty);
107 	}
108 	if (shiftx || shifty)
109 		cairo_translate(cr, shiftx, -shifty);
110 	cairo_set_source_surface(cr, com.preview_surface[current_preview], 0, 0);
111 	cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_FAST);
112 	cairo_paint(cr);
113 
114 	/* display reference image */
115 	if (display_ref_image) {
116 		/* already translated above but undo the shift translate */
117 		if (shiftx || shifty)
118 			cairo_translate(cr, -shiftx, shifty);
119 		cairo_set_source_surface(cr, com.refimage_surface, 0, 0);
120 		cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_FAST);
121 		cairo_paint_with_alpha(cr, 0.5);
122 	}
123 
124 	return FALSE;
125 }
126 
127 /* vport can be -1 if the correct viewport should be tested */
test_and_allocate_reference_image(int vport)128 void test_and_allocate_reference_image(int vport) {
129 	static GtkComboBox *cbbt_layers = NULL;
130 	if (cbbt_layers == NULL) {
131 		cbbt_layers = GTK_COMBO_BOX(lookup_widget("comboboxreglayer"));
132 	}
133 	if (vport == -1)
134 		vport = gtk_combo_box_get_active(cbbt_layers);
135 
136 	if (sequence_is_loaded() && com.seq.current == com.seq.reference_image
137 			&& gtk_combo_box_get_active(cbbt_layers) == vport && vport < gfit.naxes[2]) {
138 		/* this is the registration layer and the reference frame,
139 		 * save the buffer for alignment preview */
140 		if (!com.refimage_regbuffer || !com.refimage_surface) {
141 			guchar *oldbuf = com.refimage_regbuffer;
142 			com.refimage_regbuffer = realloc(com.refimage_regbuffer,
143 					com.surface_stride[vport] * gfit.ry * sizeof(guchar));
144 			if (com.refimage_regbuffer == NULL) {
145 				PRINT_ALLOC_ERR;
146 				if (oldbuf)
147 					free(oldbuf);
148 				return;
149 			}
150 
151 			if (com.refimage_surface)
152 				cairo_surface_destroy(com.refimage_surface);
153 			com.refimage_surface = cairo_image_surface_create_for_data(
154 					com.refimage_regbuffer, CAIRO_FORMAT_RGB24, gfit.rx,
155 					gfit.ry, com.surface_stride[vport]);
156 			if (cairo_surface_status(com.refimage_surface)
157 					!= CAIRO_STATUS_SUCCESS) {
158 				fprintf(stderr,
159 						"Error creating the Cairo image surface for the reference image.\n");
160 				cairo_surface_destroy(com.refimage_surface);
161 				com.refimage_surface = NULL;
162 			} else {
163 				fprintf(stdout,
164 						"Saved the reference frame buffer for alignment preview.\n");
165 				enable_view_reference_checkbox(TRUE);
166 			}
167 		}
168 		memcpy(com.refimage_regbuffer, com.graybuf[vport],
169 				com.surface_stride[vport] * gfit.ry * sizeof(guchar));
170 		cairo_surface_flush(com.refimage_surface);
171 		cairo_surface_mark_dirty(com.refimage_surface);
172 	}
173 }
174 
175 
redraw_previews()176 void redraw_previews() {
177 	int i;
178 	if (com.script) return;
179 	for (i = 0; i < PREVIEW_NB; i++)
180 		gtk_widget_queue_draw(com.preview_area[i]);
181 }
182 
set_preview_area(int preview_area,int centerX,int centerY)183 void set_preview_area(int preview_area, int centerX, int centerY) {
184 	/* equivalent to remap() for full image visualization, called in the
185 	 * mouse click callback and image change.
186 	 * load the preview area from reference image, from current image,
187 	 * sets the cairo_surface_t */
188 	guint area_width = gtk_widget_get_allocated_width (com.preview_area[preview_area]);
189 	guint area_height = gtk_widget_get_allocated_height (com.preview_area[preview_area]);
190 
191 	com.seq.previewX[preview_area] = centerX;
192 	com.seq.previewY[preview_area] = centerY;
193 	com.seq.previewW[preview_area] = area_width;
194 	com.seq.previewH[preview_area] = area_height;
195 
196 	if (cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, gfit.rx) !=
197 			com.surface_stride[com.cvport] ||
198 			gfit.ry != com.surface_height[com.cvport] ||
199 			!com.preview_surface[preview_area]) {
200 		if (com.preview_surface[preview_area])
201 			cairo_surface_destroy(com.preview_surface[preview_area]);
202 		com.preview_surface[preview_area] =
203 			cairo_image_surface_create_for_data(com.graybuf[com.cvport],
204 					CAIRO_FORMAT_RGB24,
205 					gfit.rx, gfit.ry,
206 					com.surface_stride[com.cvport]);
207 		if (cairo_surface_status(com.preview_surface[preview_area]) != CAIRO_STATUS_SUCCESS) {
208 			fprintf(stderr, "Error creating the Cairo image surface for preview %d\n", preview_area);
209 			cairo_surface_destroy(com.preview_surface[preview_area]);
210 			com.preview_surface[preview_area] = NULL;
211 		}
212 	}
213 	// queue for redraw
214 	gtk_widget_queue_draw(com.preview_area[preview_area]);
215 	// flush to ensure all writing to the image was done and redraw the surface
216 	//cairo_surface_flush (com.preview_surface[preview_area]);
217 	//cairo_surface_mark_dirty (com.preview_surface[preview_area]);
218 }
219 
on_toggle_preview_toggled(GtkToggleButton * toggle,gpointer user_data)220 void on_toggle_preview_toggled(GtkToggleButton *toggle, gpointer user_data) {
221 	static GtkToggleButton *preview1 = NULL;
222 
223 	if (sequence_is_loaded()) {
224 
225 		if (preview1 == NULL)
226 			preview1 = GTK_TOGGLE_BUTTON(lookup_widget("togglebutton2"));
227 		if (gtk_toggle_button_get_active(toggle)) {
228 			if (toggle == preview1)
229 				mouse_status = MOUSE_ACTION_SELECT_PREVIEW1;
230 			else
231 				mouse_status = MOUSE_ACTION_SELECT_PREVIEW2;
232 		} else {
233 			/* deactivate preview */
234 			int preview_area;
235 			mouse_status = MOUSE_ACTION_SELECT_REG_AREA;
236 			if (toggle == preview1)
237 				preview_area = 0;
238 			else
239 				preview_area = 1;
240 			cairo_surface_destroy(com.preview_surface[preview_area]);
241 			com.preview_surface[preview_area] = NULL;
242 			com.seq.previewX[preview_area] = -1;
243 			com.seq.previewY[preview_area] = -1;
244 			com.seq.previewH[preview_area] = 0;
245 			com.seq.previewW[preview_area] = 0;
246 			// queue for redraw
247 			gtk_widget_queue_draw(com.preview_area[preview_area]);
248 		}
249 	}
250 }
251 
on_checkbutton_displayref_toggled(GtkToggleButton * togglebutton,gpointer user_data)252 void on_checkbutton_displayref_toggled(GtkToggleButton *togglebutton, gpointer user_data) {
253 	redraw_previews();
254 }
255 
init_mouse()256 void init_mouse() {
257 	mouse_status = MOUSE_ACTION_SELECT_REG_AREA;
258 }
259 
260 /* display registration data (shift{x|y} for now) in the manual adjustments */
adjust_reginfo()261 void adjust_reginfo() {
262 	GtkSpinButton *spin_shiftx, *spin_shifty;
263 	GtkComboBoxText *seqcombo;
264 	gboolean set_sensitive;
265 	gint cvport;
266 
267 	spin_shiftx = GTK_SPIN_BUTTON(lookup_widget("spinbut_shiftx"));
268 	spin_shifty = GTK_SPIN_BUTTON(lookup_widget("spinbut_shifty"));
269 	seqcombo = GTK_COMBO_BOX_TEXT(lookup_widget("seqlist_dialog_combo"));
270 
271 	cvport = gtk_combo_box_get_active(GTK_COMBO_BOX(seqcombo));
272 	if (cvport < 0) return;
273 
274 	g_signal_handlers_block_by_func(spin_shiftx, on_spinbut_shift_value_change, NULL);
275 	g_signal_handlers_block_by_func(spin_shifty, on_spinbut_shift_value_change, NULL);
276 	if (com.seq.regparam == NULL || com.seq.regparam[cvport] == NULL) {
277 		gtk_spin_button_set_value(spin_shiftx, 0.);
278 		gtk_spin_button_set_value(spin_shifty, 0.);
279 	} else {
280 		gtk_spin_button_set_value(spin_shiftx,
281 				roundf_to_int(com.seq.regparam[cvport][com.seq.current].shiftx));
282 		gtk_spin_button_set_value(spin_shifty,
283 				roundf_to_int(com.seq.regparam[cvport][com.seq.current].shifty));
284 	}
285 	g_signal_handlers_unblock_by_func(spin_shiftx, on_spinbut_shift_value_change, NULL);
286 	g_signal_handlers_unblock_by_func(spin_shifty, on_spinbut_shift_value_change, NULL);
287 	set_sensitive = (com.seq.current != com.seq.reference_image);
288 	gtk_widget_set_sensitive(GTK_WIDGET(spin_shiftx), set_sensitive);
289 	gtk_widget_set_sensitive(GTK_WIDGET(spin_shifty), set_sensitive);
290 }
291 
on_spinbut_shift_value_change(GtkSpinButton * spinbutton,gpointer user_data)292 void on_spinbut_shift_value_change(GtkSpinButton *spinbutton, gpointer user_data) {
293 	static GtkSpinButton *spin_shiftx = NULL;
294 	static GtkComboBox *cbbt_layers = NULL;
295 	int current_layer, new_value;
296 	if (spin_shiftx == NULL) {
297 		spin_shiftx = GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "spinbut_shiftx"));
298 		cbbt_layers = GTK_COMBO_BOX(gtk_builder_get_object(builder, "comboboxreglayer"));
299 	}
300 	if (com.seq.regparam == NULL) {
301 		/* allocated when the number of layers is loaded from the sequence,
302 		 * = shouldn't happen */
303 		fprintf(stderr, "regparam not allocated, sequence not loaded, never displayed or malformed\n");
304 		return;
305 	}
306 
307 	current_layer = gtk_combo_box_get_active(cbbt_layers);
308 	activate_tab(current_layer);
309 
310 	if (com.seq.regparam[current_layer] == NULL) {
311 		printf("Allocating registration data for this layer\n");
312 		com.seq.regparam[current_layer] = calloc(com.seq.number, sizeof(regdata));
313 		if (com.seq.regparam[current_layer] == NULL) {
314 			PRINT_ALLOC_ERR;
315 			return;
316 		}
317 	}
318 
319 	new_value = gtk_spin_button_get_value_as_int(spinbutton);
320 	if (spinbutton == spin_shiftx)
321 		com.seq.regparam[current_layer][com.seq.current].shiftx = (float) new_value;
322 	else com.seq.regparam[current_layer][com.seq.current].shifty = (float) new_value;
323 	writeseqfile(&com.seq);
324 	update_seqlist();
325 	fill_sequence_list(&com.seq, current_layer, FALSE);	// update list with new regparam
326 	redraw_previews();
327 }
328 
329 /* enables or disables the "display reference" checkbox in registration preview */
enable_view_reference_checkbox(gboolean status)330 void enable_view_reference_checkbox(gboolean status) {
331 	static GtkToggleButton *check_display_ref = NULL;
332 	static GtkWidget *widget = NULL, *labelRegRef = NULL;
333 	if (check_display_ref == NULL) {
334 		check_display_ref = GTK_TOGGLE_BUTTON(lookup_widget("checkbutton_displayref"));
335 		widget = GTK_WIDGET(check_display_ref);
336 		labelRegRef = lookup_widget("labelRegRef");
337 	}
338 	if (status && gtk_widget_get_sensitive(widget))
339 		return;	// may be already enabled but deactivated by user, don't force it again
340 	gtk_widget_set_sensitive(widget, status);
341 	gtk_widget_set_visible(labelRegRef, !status);
342 	gtk_toggle_button_set_active(check_display_ref, status);
343 }
344 
345