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 <stdlib.h>
22 #include <stdio.h>
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <string.h>
26
27 #include "core/siril.h"
28 #include "gui/utils.h"
29 #include "gui/callbacks.h"
30 #include "gui/image_display.h"
31 #include "gui/histogram.h"
32 #include "gui/progress_and_log.h"
33 #include "io/single_image.h"
34 #include "io/image_format_fits.h"
35 #include "core/undo.h"
36 #include "core/proto.h"
37 #include "algos/statistics.h"
38
39 #ifndef O_BINARY
40 #define O_BINARY 0
41 #endif
42
43 /* *filename must be freed */
undo_build_swapfile(fits * fit,char ** filename)44 static int undo_build_swapfile(fits *fit, char **filename) {
45 gchar *nameBuff;
46 char name[] = "siril_swp-XXXXXX";
47 gchar *tmpdir;
48 int fd;
49
50 tmpdir = com.pref.swap_dir;
51 nameBuff = g_build_filename(tmpdir, name, NULL);
52 fd = g_mkstemp(nameBuff);
53 if (fd < 1) {
54 siril_log_message(_("File I/O Error: Unable to create swap file in %s: [%s]\n"),
55 tmpdir, strerror(errno));
56 g_free(nameBuff);
57 return 1;
58 }
59
60 size_t size = fit->naxes[0] * fit->naxes[1] * fit->naxes[2];
61
62 errno = 0;
63 // Write some data to the temporary file
64 if (fit->type == DATA_USHORT) {
65 if (-1 == write(fd, fit->data, size * sizeof(WORD))) {
66 siril_log_message(_("File I/O Error: Unable to write swap file in %s: [%s]\n"),
67 tmpdir, strerror(errno));
68 g_free(nameBuff);
69 g_close(fd, NULL);
70 return 1;
71 }
72 } else if (fit->type == DATA_FLOAT) {
73 if (-1 == write(fd, fit->fdata, size * sizeof(float))) {
74 siril_log_message(_("File I/O Error: Unable to write swap file in %s: [%s]\n"),
75 tmpdir, strerror(errno));
76 g_free(nameBuff);
77 g_close(fd, NULL);
78 return 1;
79 }
80 }
81 *filename = nameBuff;
82 g_close(fd, NULL);
83
84 return 0;
85 }
86
undo_remove_item(historic * histo,int index)87 static int undo_remove_item(historic *histo, int index) {
88 if (histo[index].filename) {
89 g_unlink(histo[index].filename);
90 g_free(histo[index].filename);
91 histo[index].filename = NULL;
92 }
93 memset(histo[index].history, 0, FLEN_VALUE);
94 return 0;
95 }
96
undo_add_item(fits * fit,char * filename,char * histo)97 static void undo_add_item(fits *fit, char *filename, char *histo) {
98
99 if (!com.history) {
100 com.hist_size = HISTORY_SIZE;
101 com.history = calloc(com.hist_size, sizeof(historic));
102 com.hist_current = 0;
103 com.hist_display = 0;
104 }
105 /* when undo, we remove all further items being after */
106 while (com.hist_display < com.hist_current) {
107 com.hist_current--;
108 undo_remove_item(com.history, com.hist_current);
109 }
110 com.history[com.hist_current].filename = filename;
111 com.history[com.hist_current].rx = fit->rx;
112 com.history[com.hist_current].ry = fit->ry;
113 com.history[com.hist_current].type = fit->type;
114 snprintf(com.history[com.hist_current].history, FLEN_VALUE, "%s", histo);
115
116 if (com.hist_current == com.hist_size - 1) {
117 /* we must shift all elements except 0 that must always match with the original file
118 * 0 1 2 3 4 5 6 7 8 9 10 become
119 * 0 2 3 4 5 6 7 8 9 10 11 and
120 * 0 3 4 5 6 7 8 9 10 11 12 and so on
121 */
122 undo_remove_item(com.history, 1);
123 memmove(&com.history[1], &com.history[2],
124 (com.hist_size - 2) * sizeof(*com.history));
125 com.hist_current = com.hist_size - 2;
126 }
127 com.hist_current++;
128 com.hist_display = com.hist_current;
129 }
130
undo_get_data_ushort(fits * fit,historic hist)131 static int undo_get_data_ushort(fits *fit, historic hist) {
132 int fd;
133
134 if ((fd = g_open(hist.filename, O_RDONLY | O_BINARY, 0)) == -1) {
135 printf("Error opening swap file : %s\n", hist.filename);
136 return 1;
137 }
138
139 errno = 0;
140 fit->rx = fit->naxes[0] = hist.rx;
141 fit->ry = fit->naxes[1] = hist.ry;
142 /* TODO: fit->naxes[0] = fit->rx ? what about naxes[2] ? */
143
144 size_t n = fit->naxes[0] * fit->naxes[1];
145 size_t size = n * fit->naxes[2] * sizeof(WORD);
146 WORD *buf = calloc(1, size);
147 // read the data from temporary file
148 if ((read(fd, buf, size)) < size) {
149 printf("Undo Read of [%s], failed with error [%s]\n", hist.filename, strerror(errno));
150 free(buf);
151 g_close(fd, NULL);
152 return 1;
153 }
154 /* need to reallocate data as size may have changed */
155 WORD *newdata = (WORD*) realloc(fit->data, size);
156 if (!newdata) {
157 PRINT_ALLOC_ERR;
158 free(newdata);
159 free(buf);
160 g_close(fd, NULL);
161 return 1;
162 }
163 fit->data = newdata;
164 memcpy(fit->data, buf, size);
165 fit->pdata[RLAYER] = fit->data;
166 if (fit->naxes[2] > 1) {
167 fit->pdata[GLAYER] = fit->data + n;
168 fit->pdata[BLAYER] = fit->data + n * 2;
169 }
170 full_stats_invalidation_from_fit(fit);
171 free(buf);
172 g_close(fd, NULL);
173 return 0;
174 }
175
undo_get_data_float(fits * fit,historic hist)176 static int undo_get_data_float(fits *fit, historic hist) {
177 int fd;
178
179 if ((fd = g_open(hist.filename, O_RDONLY | O_BINARY, 0)) == -1) {
180 printf("Error opening swap file : %s\n", hist.filename);
181 return 1;
182 }
183
184 errno = 0;
185 fit->rx = fit->naxes[0] = hist.rx;
186 fit->ry = fit->naxes[1] = hist.ry;
187
188 size_t n = fit->naxes[0] * fit->naxes[1];
189 size_t size = n * fit->naxes[2] * sizeof(float);
190 float *buf = calloc(1, size);
191 // read the data from temporary file
192 if ((read(fd, buf, size) < size)) {
193 printf("Undo Read of [%s], failed with error [%s]\n", hist.filename, strerror(errno));
194 free(buf);
195 g_close(fd, NULL);
196 return 1;
197 }
198 /* need to reallocate data as size may have changed */
199 float *newdata = (float*) realloc(fit->data, size);
200 if (!newdata) {
201 PRINT_ALLOC_ERR;
202 free(newdata);
203 free(buf);
204 g_close(fd, NULL);
205 return 1;
206 }
207 fit->fdata = newdata;
208 memcpy(fit->fdata, buf, size);
209 fit->fpdata[RLAYER] = fit->fdata;
210 if (fit->naxes[2] > 1) {
211 fit->fpdata[GLAYER] = fit->fdata + n;
212 fit->fpdata[BLAYER] = fit->fdata + n * 2;
213 }
214 full_stats_invalidation_from_fit(fit);
215 free(buf);
216 g_close(fd, NULL);
217 return 0;
218 }
219
undo_get_data(fits * fit,historic hist)220 static int undo_get_data(fits *fit, historic hist) {
221 if (hist.type == DATA_USHORT) {
222 if (gfit.type != DATA_USHORT) {
223 size_t ndata = fit->naxes[0] * fit->naxes[1] * fit->naxes[2];
224 fit_replace_buffer(fit, float_buffer_to_ushort(fit->fdata, ndata), DATA_USHORT);
225 set_precision_switch();
226 }
227 return undo_get_data_ushort(fit, hist);
228 } else if (hist.type == DATA_FLOAT) {
229 if (gfit.type != DATA_FLOAT) {
230 size_t ndata = fit->naxes[0] * fit->naxes[1] * fit->naxes[2];
231 fit_replace_buffer(fit, ushort_buffer_to_float(fit->data, ndata), DATA_FLOAT);
232 set_precision_switch();
233 }
234 return undo_get_data_float(fit, hist);
235 }
236 return 1;
237 }
238
is_undo_available()239 gboolean is_undo_available() {
240 return (com.history && com.hist_display > 0);
241 }
242
is_redo_available()243 gboolean is_redo_available() {
244 return (com.history && (com.hist_display < com.hist_current - 1));
245 }
246
undo_save_state(fits * fit,char * message,...)247 int undo_save_state(fits *fit, char *message, ...) {
248 gchar *filename;
249 char histo[FLEN_VALUE] = { 0 };
250 va_list args;
251 va_start(args, message);
252
253 if (single_image_is_loaded()) {
254 if (message != NULL) {
255 vsnprintf(histo, FLEN_VALUE, message, args);
256 }
257
258 if (undo_build_swapfile(fit, &filename)) {
259 va_end(args);
260 return 1;
261 }
262
263 undo_add_item(fit, filename, histo);
264
265 /* update menus */
266 update_MenuItem();
267 }
268 va_end(args);
269 return 0;
270 }
271
undo_display_data(int dir)272 int undo_display_data(int dir) {
273 if (!com.history) {
274 return 1;
275 }
276 switch (dir) {
277 case UNDO:
278 if (is_undo_available()) {
279 if (com.hist_current == com.hist_display) {
280 undo_save_state(&gfit, NULL);
281 com.hist_display--;
282 }
283 com.hist_display--;
284 siril_log_message(_("Undo: %s\n"), com.history[com.hist_display].history);
285 undo_get_data(&gfit, com.history[com.hist_display]);
286 invalidate_gfit_histogram();
287 invalidate_stats_from_fit(&gfit);
288 update_gfit_histogram_if_needed();
289 update_MenuItem();
290 redraw(com.cvport, REMAP_ALL);
291 }
292 break;
293 case REDO:
294 if (is_redo_available()) {
295 siril_log_message(_("Redo: %s\n"), com.history[com.hist_display].history);
296 com.hist_display++;
297 undo_get_data(&gfit, com.history[com.hist_display]);
298 invalidate_gfit_histogram();
299 invalidate_stats_from_fit(&gfit);
300 update_gfit_histogram_if_needed();
301 update_MenuItem();
302 redraw(com.cvport, REMAP_ALL);
303 }
304 break;
305 default:
306 printf("ERROR\n");
307 return -1;
308 }
309 return 0;
310 }
311
undo_flush()312 int undo_flush() {
313 if (!com.history) {
314 return 1;
315 }
316 for (int i = 0; i < com.hist_current; i++) {
317 undo_remove_item(com.history, i);
318 }
319 free(com.history);
320 com.history = NULL;
321 com.hist_current = 0;
322 com.hist_display = 0;
323 return 0;
324 }
325
set_undo_redo_tooltip()326 void set_undo_redo_tooltip() {
327 if (is_undo_available()) {
328 gchar *str = g_strdup_printf(_("Undo: \"%s\""), com.history[com.hist_display - 1].history);
329 gtk_widget_set_tooltip_text(lookup_widget("header_undo_button"), str);
330 g_free(str);
331 }
332 else gtk_widget_set_tooltip_text(lookup_widget("header_undo_button"), _("Nothing to undo"));
333 if (is_redo_available()) {
334 gchar *str = g_strdup_printf(_("Redo: \"%s\""), com.history[com.hist_display].history);
335 gtk_widget_set_tooltip_text(lookup_widget("header_redo_button"), str);
336 g_free(str);
337 }
338 else gtk_widget_set_tooltip_text(lookup_widget("header_redo_button"), _("Nothing to redo"));
339 }
340