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 "core/processing.h"
24 #include "core/exif.h"
25 #include "gui/utils.h"
26 #include "gui/histogram.h"
27 #include "io/ser.h"
28 #include "io/image_format_fits.h"
29 
30 #ifdef HAVE_LIBHEIF
31 #include <libheif/heif.h>
32 #endif
33 
34 #include "dialog_preview.h"
35 
36 static gboolean preview_allocated = FALSE; // flag needed when user load image before preview was displayed.
37 
38 struct _updta_preview_data {
39 	GtkFileChooser *file_chooser;
40 	gchar *filename;
41 	gchar *description;
42 	GdkPixbuf *pixbuf;
43 	GFileInfo *file_info;
44 	fileChooserPreview *preview;
45 };
46 
47 struct _fileChooserPreview {
48 	GtkWidget *image;
49 	GtkWidget *name_label;
50 	GtkWidget *dim_label;
51 	GtkWidget *size_label;
52 };
53 
new_preview_object()54 static fileChooserPreview *new_preview_object() {
55 	fileChooserPreview *object = g_new(fileChooserPreview, 1);
56 	object->image = gtk_image_new();
57 	object->name_label = gtk_label_new(NULL);
58 	object->dim_label = gtk_label_new(NULL);
59 	object->size_label = gtk_label_new(NULL);
60 	return object;
61 }
62 
end_update_preview_cb(gpointer p)63 static gboolean end_update_preview_cb(gpointer p) {
64 	struct _updta_preview_data *args = (struct _updta_preview_data *) p;
65 
66 	stop_processing_thread();
67 
68 	int bytes;
69 	const char *bytes_str;
70 	char *size_str = NULL;
71 	char *name_str = NULL;
72 	char *info_str = NULL;
73 	GFileType type;
74 
75 	fileChooserPreview *preview = args->preview;
76 
77 	if (!preview_allocated || !preview || !(GTK_IS_IMAGE((preview->image)))) {
78 		return FALSE;
79 	}
80 
81 	name_str = g_path_get_basename(args->filename);
82 
83 	if (!args->file_info) {
84 		return FALSE;
85 	}
86 	type = g_file_info_get_file_type(args->file_info);
87 
88 	/* try to read file size */
89 	if (args->pixbuf && (bytes_str = gdk_pixbuf_get_option(args->pixbuf, "tEXt::Thumb::Size")) != NULL) {
90 		bytes = g_ascii_strtoll(bytes_str, NULL, 10);
91 		size_str = g_format_size(bytes);
92 	} else {
93 		if (type == G_FILE_TYPE_REGULAR) {
94 			size_str = g_format_size(g_file_info_get_size(args->file_info));
95 		}
96 	}
97 
98 	/* load icon */
99 	if (type == G_FILE_TYPE_REGULAR && args->pixbuf) {
100 		gtk_image_set_from_pixbuf(GTK_IMAGE(preview->image), args->pixbuf);
101 		info_str = args->description;
102 	} else if (type == G_FILE_TYPE_DIRECTORY) {
103 		gtk_image_set_from_icon_name(GTK_IMAGE(preview->image), "folder", GTK_ICON_SIZE_DIALOG);
104 		gtk_image_set_pixel_size(GTK_IMAGE(preview->image), com.pref.thumbnail_size);
105 		info_str = g_strdup(_("Folder"));
106 	} else {
107 		image_type im_type = get_type_from_filename(args->filename);
108 		if (im_type == TYPEAVI || im_type == TYPESER ||
109 				(im_type == TYPEFITS && fitseq_is_fitseq(args->filename, NULL)))
110 			gtk_image_set_from_icon_name(GTK_IMAGE(preview->image), "video", GTK_ICON_SIZE_DIALOG);
111 		else gtk_image_set_from_icon_name(GTK_IMAGE(preview->image), "image", GTK_ICON_SIZE_DIALOG);
112 		gtk_image_set_pixel_size(GTK_IMAGE(preview->image), com.pref.thumbnail_size);
113 	}
114 
115 	/* information strings */
116 	const char *format = "<span style=\"italic\">%s</span>";
117 	char *markup = g_markup_printf_escaped(format, name_str);
118 	gtk_label_set_markup(GTK_LABEL(preview->name_label), markup);
119 	gtk_label_set_ellipsize(GTK_LABEL(preview->name_label), PANGO_ELLIPSIZE_MIDDLE);
120 	gtk_label_set_width_chars(GTK_LABEL(preview->name_label), 25);
121 	gtk_label_set_max_width_chars(GTK_LABEL(preview->name_label), 25);
122 
123 	gtk_label_set_text(GTK_LABEL(preview->dim_label), info_str);
124 	gtk_label_set_text(GTK_LABEL(preview->size_label), size_str);
125 
126 	if (args->pixbuf)
127 		g_object_unref(args->pixbuf);
128 	g_free(markup);
129 	g_free(name_str);
130 	g_free(info_str);
131 	g_free(size_str);
132 
133 	g_object_unref(args->file_info);
134 	g_free(args->filename);
135 
136 	free(args);
137 	args = NULL;
138 	return FALSE;
139 }
140 
update_preview_cb_idle(gpointer p)141 static gpointer update_preview_cb_idle(gpointer p) {
142 	uint8_t *buffer = NULL;
143 	size_t size;
144 	char *mime_type = NULL;
145 	GdkPixbuf *pixbuf = NULL;
146 	image_type im_type;
147 	gboolean libheif_is_ok = FALSE;
148 
149 	struct _updta_preview_data *args = (struct _updta_preview_data *) p;
150 
151 	args->description = NULL;
152 
153 	im_type = get_type_from_filename(args->filename);
154 
155 	if (im_type == TYPEFITS) {
156 		/* try FITS file */
157 		pixbuf = get_thumbnail_from_fits(args->filename, &args->description);
158 	} else if (im_type == TYPESER) {
159 		pixbuf = get_thumbnail_from_ser(args->filename, &args->description);
160 	} else {
161 		if (im_type != TYPEUNDEF && !siril_get_thumbnail_exiv(args->filename, &buffer, &size,
162 				&mime_type)) {
163 			// Scale the image to the correct size
164 			GdkPixbuf *tmp;
165 			GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
166 			if (!gdk_pixbuf_loader_write(loader, buffer, size, NULL))
167 				goto cleanup;
168 			// Calling gdk_pixbuf_loader_close forces the data to be parsed by the
169 			// loader. We must do this before calling gdk_pixbuf_loader_get_pixbuf.
170 			if (!gdk_pixbuf_loader_close(loader, NULL))
171 				goto cleanup;
172 			if (!(tmp = gdk_pixbuf_loader_get_pixbuf(loader)))
173 				goto cleanup;
174 			float ratio = 1.0 * gdk_pixbuf_get_height(tmp) / gdk_pixbuf_get_width(tmp);
175 			int width = com.pref.thumbnail_size, height = com.pref.thumbnail_size * ratio;
176 			pixbuf = gdk_pixbuf_scale_simple(tmp, width, height, GDK_INTERP_BILINEAR);
177 			args->description = siril_get_file_info(args->filename, pixbuf);
178 
179 			cleanup: gdk_pixbuf_loader_close(loader, NULL);
180 			free(mime_type);
181 			free(buffer);
182 			g_object_unref(loader); // This should clean up tmp as well
183 		}
184 
185 		/* if no pixbuf created try to directly read the file */
186 		/* libheif < 1.6.2 has a bug, therefore we can't open preview if libheif is too old
187 		 * bug fixed in https://github.com/strukturag/libheif/commit/fbd6d28e8604ecb53a2eb33b522a664b6bcabd0b*/
188 #ifdef HAVE_LIBHEIF
189 		libheif_is_ok = LIBHEIF_HAVE_VERSION(1, 6, 2);
190 #endif
191 
192 		if (!pixbuf && (im_type != TYPEHEIF || libheif_is_ok)) {
193 			pixbuf = gdk_pixbuf_new_from_file_at_size(args->filename,
194 					com.pref.thumbnail_size, com.pref.thumbnail_size, NULL);
195 			args->description = siril_get_file_info(args->filename, pixbuf);
196 		}
197 	}
198 
199 	args->pixbuf = pixbuf;
200 	siril_add_idle(end_update_preview_cb, args);
201 	return GINT_TO_POINTER(0);
202 }
203 
update_preview_cb(GtkFileChooser * file_chooser,gpointer p)204 static void update_preview_cb(GtkFileChooser *file_chooser, gpointer p) {
205 	gchar *uri;
206 	GFile *file;
207 	GFileInfo *file_info;
208 	fileChooserPreview *preview = (fileChooserPreview *)p;
209 
210 	uri = gtk_file_chooser_get_preview_uri(file_chooser);
211 	if (uri == NULL) {
212 		gtk_file_chooser_set_preview_widget_active(file_chooser, FALSE);
213 		return;
214 	}
215 
216 	file = g_file_new_for_uri(uri);
217 	file_info = g_file_query_info(file,
218 				       G_FILE_ATTRIBUTE_TIME_MODIFIED ","
219 				       G_FILE_ATTRIBUTE_STANDARD_TYPE ","
220 				       G_FILE_ATTRIBUTE_STANDARD_SIZE ","
221 				       G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
222 				       0, NULL, NULL);
223 
224 	gtk_file_chooser_set_preview_widget_active(file_chooser, TRUE);
225 
226 	struct _updta_preview_data *data = malloc(sizeof(struct _updta_preview_data));
227 	data->filename = g_file_get_path(file);
228 	data->file_info = file_info;
229 	data->file_chooser = file_chooser;
230 	data->preview = preview;
231 
232 	g_free(uri);
233 	g_object_unref(file);
234 
235 	start_in_new_thread(update_preview_cb_idle, data);
236 }
237 
siril_preview_free(fileChooserPreview * preview)238 void siril_preview_free(fileChooserPreview *preview) {
239 	g_free(preview);
240 	preview_allocated = FALSE;
241 }
242 
siril_file_chooser_add_preview(GtkFileChooser * dialog,fileChooserPreview * preview)243 void siril_file_chooser_add_preview(GtkFileChooser *dialog, fileChooserPreview *preview) {
244 	if (com.pref.show_thumbnails) {
245 		GtkWidget *vbox;
246 
247 		vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
248 		gtk_container_set_border_width(GTK_CONTAINER(vbox), 12);
249 
250 		preview = new_preview_object();
251 		preview_allocated = TRUE;
252 
253 		gtk_label_set_justify(GTK_LABEL(preview->name_label), GTK_JUSTIFY_CENTER);
254 		gtk_label_set_justify(GTK_LABEL(preview->dim_label), GTK_JUSTIFY_CENTER);
255 		gtk_label_set_justify(GTK_LABEL(preview->dim_label), GTK_JUSTIFY_CENTER);
256 
257 		gtk_widget_set_size_request(preview->image, com.pref.thumbnail_size, com.pref.thumbnail_size);
258 
259 		gtk_box_pack_start(GTK_BOX(vbox), preview->image, FALSE, TRUE, 0);
260 		gtk_box_pack_start(GTK_BOX(vbox), preview->name_label, FALSE, TRUE, 10);
261 		gtk_box_pack_start(GTK_BOX(vbox), preview->size_label, FALSE, TRUE, 0);
262 		gtk_box_pack_start(GTK_BOX(vbox), preview->dim_label, FALSE, TRUE, 0);
263 
264 		gtk_widget_show_all(vbox);
265 
266 		gtk_file_chooser_set_preview_widget(dialog, vbox);
267 		gtk_file_chooser_set_use_preview_label(dialog, FALSE);
268 		gtk_file_chooser_set_preview_widget_active(dialog, FALSE);
269 
270 		g_signal_connect(dialog, "update-preview", G_CALLBACK(update_preview_cb), (gpointer)preview);
271 	}
272 }
273