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 ****************************/
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.* */
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
)
64 static gint
wait_child(pid_t pid
)
66 const struct timespec req
= {.tv_sec
= 0,.tv_nsec
= 10000000 };
71 while (child_pid
!= 0) {
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
)
88 regex_t geometry_regex
, type_regex
;
99 regcomp(&geometry_regex
, "^ *Geometry: ([0-9]+)x([0-9]+)", REG_EXTENDED
);
100 regcomp(&type_regex
, "^ *Type:.*with transparency", REG_NOSUB
);
103 getline(&buffer
, &size
, f
);
105 if (!*has_alpha
&& strstr(buffer
, "with transparency") &&
106 !regexec(&type_regex
, buffer
, 0, NULL
, 0))
109 else if (strstr(buffer
, "Geometry: ") &&
110 !regexec(&geometry_regex
, buffer
, 3, match
, 0)) {
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
);
127 regfree(&geometry_regex
);
128 regfree(&type_regex
);
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
);
152 static void delete_png_tiles(const gchar
* basename
)
155 gchar
*filename
= NULL
;
159 filename
= g_strdup_printf("%s.%d", basename
, i
);
161 } while (remove(filename
) == 0);
166 static void place_tiles(GdkPixbuf
* pixbuf
, const gchar
* basename
, gint nb
,
167 gint nb_x
, gint tile_width
, gint tile_height
)
173 for (i
= 0; i
< nb
; i
++) {
176 filename
= g_strdup_printf("%s.%d", basename
, i
);
177 tile
= load_gdk_pixbuf(filename
);
182 gdk_pixbuf_copy_area(tile
, 0, 0,
183 gdk_pixbuf_get_width(tile
),
184 gdk_pixbuf_get_height(tile
),
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
;
201 /* How many tiles? */
202 for (nb
= 0;; nb
++) {
205 filename
= g_strdup_printf("%s.%d", basename
, nb
);
206 if (g_file_test(filename
, G_FILE_TEST_EXISTS
) == FALSE
)
213 if (get_format(basename
, nb
, &has_alpha
,
214 &tile_width
, &tile_height
, &nb_x
, &nb_y
) == FALSE
)
219 pixbuf
= gdk_pixbuf_new(GDK_COLORSPACE_RGB
, has_alpha
, 8,
220 nb_x
* tile_width
, nb_y
* tile_height
);
223 gdk_pixbuf_fill(pixbuf
, 0x00000000);
227 place_tiles(pixbuf
, basename
, nb
, nb_x
, tile_width
, tile_height
);
231 if (gdk_pixbuf_save(pixbuf
, basename
, "png", &err
, NULL
) == FALSE
) {
232 g_printerr("%s: %s\n", locale_to_utf8(basename
), err
->message
);
238 delete_png_tiles(basename
);
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
);
252 return system(cmd
) == 0;
255 G_GNUC_NORETURN
static void child_process(const gchar
* filename
,
258 gchar
*cmd
, *escaped_dir
, *escaped_png_name
, *escaped_filename
;
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
,
272 g_free(escaped_png_name
);
273 g_free(escaped_filename
);
276 failed
= (system(cmd
) != 0);
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. */
287 static GdkPixbuf
*convert_to_png(const gchar
* filename
, gchar
** png_name
)
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
));
302 child_process(filename
, *png_name
);
306 if (wait_child(pid
) != 0)
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
);
324 static void imagemagick_init(void)
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
);
340 mkdir(magick_wd
, S_IRWXU
);
342 putenv(g_strconcat("TMPDIR=", magick_wd
, NULL
));
343 putenv(g_strconcat("TEMP=", magick_wd
, NULL
));
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
)
358 if (have_imagemagick() == FALSE
) {
359 ERROR_MSG(_("%s: ImageMagick not found\n"), locale_to_utf8(filename
));
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
);
375 g_hash_table_insert(hash
, (gchar
*) filename
, png_name
);
378 /* This image has already been converted. */
379 pixbuf
= load_gdk_pixbuf(png_name
);
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
);
394 gboolean
have_imagemagick(void)
396 static gboolean checked
= FALSE
;
397 static gboolean have_it
;
399 if (checked
== FALSE
) {
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)");
413 static void suppress_file(gpointer unused
, gpointer 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
;
428 if (getppid() == gliv_pid
) {
429 ERROR_MSG(_("Warning: the ImageMagick process is still running\n"));
437 kill(child_pid
, SIGTERM
);
439 g_hash_table_foreach(hash
, (GHFunc
) suppress_file
, NULL
);
442 cmd
= g_strdup_printf("rm -fr %s", str_escape(magick_wd
));
445 tmp_dir
= g_path_get_dirname(magick_wd
);
446 res
= rmdir(tmp_dir
);
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
)));