gliv-1.7
[gliv.git] / src / imagemagick.c
blobc0213918f54835abe0106b0693121f5c5616d4fe
1 /*
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU General Public License
4 * as published by the Free Software Foundation; either version 2
5 * of the License, or (at your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
16 * See the COPYING file for license information.
18 * Guillaume Chazarain <gfc@altern.org>
21 /****************************
22 * Loading via ImageMagick. *
23 ****************************/
25 #include "gliv.h"
26 #include "math_floats.h" /* sqrtf(), ceilf() */
28 #include <sys/stat.h> /* S_IRWXU */
29 #include <sys/types.h> /* pid_t */
30 #include <sys/wait.h> /* waitpid() */
31 #include <stdio.h> /* tmpnam(), remove(), perror(), FILE */
32 #include <time.h> /* time(), nanosleep() */
33 #include <unistd.h> /* *dir(), getp*id(), fork(), _exit() */
34 #include <stdlib.h> /* system() */
35 #include <signal.h> /* raise(), signal(), SIG* */
36 #include <string.h> /* strstr() */
37 #include <regex.h> /* ^reg.* */
39 extern rt_struct *rt;
42 * Used to store the association between an unknown file to
43 * gdk-pixbuf and its PNG equivalent made by ImageMagick.
45 static GHashTable *hash = NULL;
48 * We chdir() before doing the ImageMagick stuff, because it can leave
49 * some garbage if interrupted. We will delete the dir at the end.
51 static const gchar *gliv_wd, *magick_wd;
53 /* When the parent receives a SIGCHLD it is set to 0. */
54 static volatile pid_t child_pid = 0;
56 /* Used to know if we are in the parent or the child. */
57 static pid_t gliv_pid;
59 static void sig_chld(gint unused)
61 child_pid = 0;
64 static gint wait_child(pid_t pid)
66 const struct timespec req = {.tv_sec = 0,.tv_nsec = 10000000 };
67 gint status;
69 child_pid = pid;
71 while (child_pid != 0) {
72 process_events();
73 nanosleep(&req, NULL);
76 /* The child process exited. */
78 waitpid(pid, &status, WNOHANG);
80 return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
83 static gboolean get_format(const char *basename, gint nb, gboolean * has_alpha,
84 gint * max_width, gint * max_height,
85 gint * nb_x, gint * nb_y)
87 FILE *f;
88 regex_t geometry_regex, type_regex;
89 gchar *buffer = NULL;
90 gint size = 0;
91 regmatch_t match[3];
93 *has_alpha = FALSE;
94 *max_width = 0;
95 *max_height = 0;
97 f = fopen("id", "r");
99 regcomp(&geometry_regex, "^ *Geometry: ([0-9]+)x([0-9]+)", REG_EXTENDED);
100 regcomp(&type_regex, "^ *Type:.*with transparency", REG_NOSUB);
102 while (!feof(f)) {
103 getline(&buffer, &size, f);
105 if (!*has_alpha && strstr(buffer, "with transparency") &&
106 !regexec(&type_regex, buffer, 0, NULL, 0))
107 *has_alpha = TRUE;
109 else if (strstr(buffer, "Geometry: ") &&
110 !regexec(&geometry_regex, buffer, 3, match, 0)) {
111 gint w, h;
113 buffer[match[1].rm_eo] = '\0';
114 buffer[match[2].rm_eo] = '\0';
116 w = atoi(buffer + match[1].rm_so);
117 h = atoi(buffer + match[2].rm_so);
119 if (w > *max_width)
120 *max_width = w;
122 if (h > *max_height)
123 *max_height = h;
127 regfree(&geometry_regex);
128 regfree(&type_regex);
130 fclose(f);
131 remove("id");
134 * Number of tiles in each direction.
135 * The logic is to have:
137 * nb_x * max_width rt->scr_width
138 * ----------------- ~ --------------
139 * nb_y * max_height rt->scr_height
141 * so that in maximize mode, the space usage and zoom ratio are maximal.
144 *nb_x = (gint) ceilf(sqrtf((gfloat) nb * *max_height * rt->scr_width /
145 (*max_width * rt->scr_height)));
147 *nb_y = (gint) ceilf((gfloat) nb / *nb_x);
149 return TRUE;
152 static void delete_png_tiles(const gchar * basename)
154 int i = 0;
155 gchar *filename = NULL;
157 do {
158 g_free(filename);
159 filename = g_strdup_printf("%s.%d", basename, i);
160 i++;
161 } while (remove(filename) == 0);
163 g_free(filename);
166 static void place_tiles(GdkPixbuf * pixbuf, const gchar * basename, gint nb,
167 gint nb_x, gint tile_width, gint tile_height)
169 gint i;
170 GdkPixbuf *tile;
171 gchar *filename;
173 for (i = 0; i < nb; i++) {
174 process_events();
176 filename = g_strdup_printf("%s.%d", basename, i);
177 tile = load_gdk_pixbuf(filename);
178 g_free(filename);
180 process_events();
182 gdk_pixbuf_copy_area(tile, 0, 0,
183 gdk_pixbuf_get_width(tile),
184 gdk_pixbuf_get_height(tile),
185 pixbuf,
186 (i % nb_x) * tile_width, (i / nb_x) * tile_height);
188 gdk_pixbuf_unref(tile);
192 /* We build the concatenation, save it and return it. */
193 static GdkPixbuf *png_concatenate(const gchar * basename)
195 gint nb, nb_x, nb_y, tile_width, tile_height;
196 gchar *filename = NULL;
197 GdkPixbuf *pixbuf;
198 GError *err = NULL;
199 gboolean has_alpha;
201 /* How many tiles? */
202 for (nb = 0;; nb++) {
203 g_free(filename);
205 filename = g_strdup_printf("%s.%d", basename, nb);
206 if (g_file_test(filename, G_FILE_TEST_EXISTS) == FALSE)
207 break;
209 g_free(filename);
211 process_events();
213 if (get_format(basename, nb, &has_alpha,
214 &tile_width, &tile_height, &nb_x, &nb_y) == FALSE)
215 return NULL;
217 process_events();
219 pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, has_alpha, 8,
220 nb_x * tile_width, nb_y * tile_height);
222 /* Blacken it. */
223 gdk_pixbuf_fill(pixbuf, 0x00000000);
225 process_events();
227 place_tiles(pixbuf, basename, nb, nb_x, tile_width, tile_height);
229 process_events();
231 if (gdk_pixbuf_save(pixbuf, basename, "png", &err, NULL) == FALSE) {
232 g_printerr("%s: %s\n", locale_to_utf8(basename), err->message);
233 g_error_free(err);
236 process_events();
238 delete_png_tiles(basename);
240 return pixbuf;
243 /* Return FALSE if failed. */
244 static gboolean make_id(const gchar * basename)
246 gchar *cmd, *escaped;
248 escaped = str_escape(basename);
249 cmd = g_strdup_printf("identify -verbose %s.* 2>/dev/null > id", escaped);
250 g_free(escaped);
252 return system(cmd) == 0;
255 G_GNUC_NORETURN static void child_process(const gchar * filename,
256 gchar * png_name)
258 gchar *cmd, *escaped_dir, *escaped_png_name, *escaped_filename;
259 const gchar *dir;
260 gboolean failed;
262 dir = g_path_is_absolute(filename) ? "" : gliv_wd;
264 /* The conversion. */
265 escaped_dir = str_escape(dir);
266 escaped_filename = str_escape(filename);
267 escaped_png_name = str_escape(png_name);
269 cmd = g_strdup_printf("convert %s/%s png:%s", escaped_dir, escaped_filename,
270 escaped_png_name);
272 g_free(escaped_png_name);
273 g_free(escaped_filename);
274 g_free(escaped_dir);
276 failed = (system(cmd) != 0);
277 g_free(cmd);
279 if (failed == FALSE && g_file_test(png_name, G_FILE_TEST_EXISTS) == FALSE) {
280 failed = !make_id(png_name);
283 /* We don't want the child process to run the atexit() function. */
284 _exit(failed);
287 static GdkPixbuf *convert_to_png(const gchar * filename, gchar ** png_name)
289 pid_t pid;
291 signal(SIGCHLD, sig_chld);
292 *png_name = g_strdup_printf("%u_%s.png", g_random_int_range(0, 1 << 30),
293 g_path_get_basename(filename));
295 pid = fork();
297 if (pid < 0) {
298 perror("fork");
299 return NULL;
301 } else if (pid == 0)
302 child_process(filename, *png_name);
304 else {
305 /* Parent. */
306 if (wait_child(pid) != 0)
307 return NULL;
309 if (g_file_test(*png_name, G_FILE_TEST_EXISTS) == FALSE)
310 /* Multipart image. */
311 return png_concatenate(*png_name);
314 return load_gdk_pixbuf(*png_name);
317 static void signal_finish(gint sig)
319 imagemagick_finish();
320 signal(sig, SIG_DFL);
321 raise(sig);
324 static void imagemagick_init(void)
326 gchar *tmp_dir;
328 hash = g_hash_table_new(g_str_hash, g_str_equal);
330 gliv_wd = g_get_current_dir();
332 magick_wd = g_strdup_printf("%s/gliv/%s_%ld_%d", g_get_tmp_dir(),
333 g_path_get_basename(tmpnam(NULL)),
334 time(NULL), getpid());
336 tmp_dir = g_path_get_dirname(magick_wd);
337 mkdir(tmp_dir, S_IRWXU);
338 g_free(tmp_dir);
340 mkdir(magick_wd, S_IRWXU);
342 putenv(g_strconcat("TMPDIR=", magick_wd, NULL));
343 putenv(g_strconcat("TEMP=", magick_wd, NULL));
345 gliv_pid = getpid();
347 signal(SIGHUP, signal_finish);
348 signal(SIGINT, signal_finish);
349 signal(SIGTERM, signal_finish);
350 g_atexit(imagemagick_finish);
353 GdkPixbuf *load_with_imagemagick(const gchar * filename)
355 gchar *png_name;
356 GdkPixbuf *pixbuf;
358 if (have_imagemagick() == FALSE) {
359 ERROR_MSG(_("%s: ImageMagick not found\n"), locale_to_utf8(filename));
360 return NULL;
363 if (hash == NULL)
364 imagemagick_init();
366 chdir(magick_wd);
368 png_name = g_hash_table_lookup(hash, filename);
370 if (png_name == NULL) {
371 /* First time this image is loaded. */
372 pixbuf = convert_to_png(filename, &png_name);
374 if (pixbuf != NULL)
375 g_hash_table_insert(hash, (gchar *) filename, png_name);
377 } else {
378 /* This image has already been converted. */
379 pixbuf = load_gdk_pixbuf(png_name);
381 if (pixbuf == NULL)
383 * It can happen if the png image could
384 * not be saved when it was created.
386 pixbuf = convert_to_png(filename, &png_name);
389 chdir(gliv_wd);
391 return pixbuf;
394 gboolean have_imagemagick(void)
396 static gboolean checked = FALSE;
397 static gboolean have_it;
399 if (checked == FALSE) {
400 /* First time. */
402 have_it =
403 !system("(convert 2>/dev/null | head -1 | grep -q ImageMagick) && \
404 (montage 2>/dev/null | head -1 | grep -q ImageMagick) && \
405 (identify 2>/dev/null | head -1 | grep -q ImageMagick)");
407 checked = TRUE;
410 return have_it;
413 static void suppress_file(gpointer unused, gpointer filename)
415 remove(filename);
419 * Delete the temporary directory we created.
420 * This function is called on exit so there is
421 * no need freeing memory.
423 void imagemagick_finish(void)
425 gchar *cmd, *tmp_dir, *msg;
426 gint res;
428 if (getppid() == gliv_pid) {
429 ERROR_MSG(_("Warning: the ImageMagick process is still running\n"));
430 return;
433 if (hash == NULL)
434 return;
436 if (child_pid != 0)
437 kill(child_pid, SIGTERM);
439 g_hash_table_foreach(hash, (GHFunc) suppress_file, NULL);
440 hash = NULL;
442 cmd = g_strdup_printf("rm -fr %s", str_escape(magick_wd));
443 system(cmd);
445 tmp_dir = g_path_get_dirname(magick_wd);
446 res = rmdir(tmp_dir);
447 if (res < 0) {
448 msg = _("This directory can be deleted if GLiv is not running");
450 system(g_strdup_printf("echo %s > '%s/README'", str_escape(msg),
451 str_escape(tmp_dir)));