http://ftp.ksu.edu.tw/FTP/FreeBSD/distfiles/xscreensaver-4.23.tar.gz
[xscreensaver] / hacks / webcollage-helper.c
1 /* webcollage-helper --- scales and pastes one image into another
2  * xscreensaver, Copyright (c) 2002-2005 Jamie Zawinski <jwz@jwz.org>
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or 
10  * implied warranty.
11  */
12
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
16
17 #if defined(HAVE_GDK_PIXBUF) && defined(HAVE_JPEGLIB)  /* whole file */
18
19 #include <stdlib.h>
20 #include <stdio.h>
21 #include <math.h>
22 #include <string.h>
23 #include <time.h>
24 #include <sys/types.h>
25 #include <sys/stat.h>
26
27 #undef HAVE_STDLIB_H /* stupid jconfig.h! */
28 #include <jpeglib.h>
29 #include <gdk-pixbuf/gdk-pixbuf.h>
30
31
32 char *progname;
33 static int verbose_p = 0;
34
35 static void add_jpeg_comment (struct jpeg_compress_struct *cinfo);
36 static void write_pixbuf (GdkPixbuf *pb, const char *file);
37
38 static GdkPixbuf *
39 load_pixbuf (const char *file)
40 {
41   GdkPixbuf *pb;
42 #ifdef HAVE_GTK2
43   GError *err = NULL;
44
45   pb = gdk_pixbuf_new_from_file (file, &err);
46 #else  /* !HAVE_GTK2 */
47   pb = gdk_pixbuf_new_from_file (file);
48 #endif /* HAVE_GTK2 */
49
50   if (!pb)
51     {
52 #ifdef HAVE_GTK2
53       fprintf (stderr, "%s: %s\n", progname, err->message);
54       g_error_free (err);
55 #else  /* !HAVE_GTK2 */
56       fprintf (stderr, "%s: unable to load %s\n", progname, file);
57 #endif /* !HAVE_GTK2 */
58       exit (1);
59     }
60
61   return pb;
62 }
63
64
65 static void
66 bevel_image (GdkPixbuf **pbP, int bevel_pct,
67              int x, int y, int w, int h)
68 {
69   GdkPixbuf *pb = *pbP;
70   int small_size = (w > h ? h : w);
71
72   int bevel_size = small_size * (bevel_pct / 100.0);
73
74   /* Use a proportionally larger bevel size for especially small images. */
75   if      (bevel_size < 20 && small_size > 40) bevel_size = 20;
76   else if (bevel_size < 10 && small_size > 20) bevel_size = 10;
77   else if (bevel_size < 5)    /* too small to bother bevelling */
78     return;
79
80   /* Ensure the pixbuf has an alpha channel. */
81   if (! gdk_pixbuf_get_has_alpha (pb))
82     {
83       GdkPixbuf *pb2 = gdk_pixbuf_add_alpha (pb, FALSE, 0, 0, 0);
84       gdk_pixbuf_unref (pb);
85       pb = pb2;
86     }
87
88   {
89     guchar *data = gdk_pixbuf_get_pixels (pb);
90     guchar *line;
91     int rs = gdk_pixbuf_get_rowstride (pb);
92     int ch = gdk_pixbuf_get_n_channels (pb);
93     int xx, yy;
94     double *ramp = (double *) malloc (sizeof(*ramp) * (bevel_size + 1));
95
96     if (!ramp)
97       {
98         fprintf (stderr, "%s: out of memory (%d)\n", progname, bevel_size);
99         exit (1);
100       }
101
102     for (xx = 0; xx <= bevel_size; xx++)
103       {
104
105 # if 0  /* linear */
106         ramp[xx] = xx / (double) bevel_size;
107
108 # else /* sinusoidal */
109         double p = (xx / (double) bevel_size);
110         double s = sin (p * M_PI / 2);
111         ramp[xx] = s;
112 # endif
113       }
114
115     line = data + (rs * y);
116     for (yy = 0; yy < h; yy++)
117       {
118         guchar *p = line + (x * ch);
119         for (xx = 0; xx < w; xx++)
120           {
121             double rx, ry, r;
122
123             if (xx < bevel_size)           rx = ramp[xx];
124             else if (xx >= w - bevel_size) rx = ramp[w - xx - 1];
125             else rx = 1;
126
127             if (yy < bevel_size)           ry = ramp[yy];
128             else if (yy >= h - bevel_size) ry = ramp[h - yy - 1];
129             else ry = 1;
130
131             r = rx * ry;
132             if (r != 1)
133               p[ch-1] *= r;
134
135             p += ch;
136           }
137         line += rs;
138       }
139
140 #if 0  /* show the ramp */
141     line = data + (rs * y);
142     for (yy = 0; yy < h; yy++)
143       {
144         guchar *p = line + (x * ch);
145         for (xx = 0; xx < w; xx++)
146           {
147             int cc = 0;
148             for (cc = 0; cc < ch-1; cc++)
149               p[cc] = 255;
150             p += ch;
151           }
152         line += rs;
153       }
154 #endif
155
156     free (ramp);
157
158     if (verbose_p)
159       fprintf (stderr, "%s: added %d%% bevel (%d px)\n", progname,
160                bevel_pct, bevel_size);
161   }
162
163   *pbP = pb;
164 }
165
166
167 static void
168 paste (const char *paste_file,
169        const char *base_file,
170        double from_scale,
171        double opacity, int bevel_pct,
172        int from_x, int from_y, int to_x, int to_y,
173        int w, int h)
174 {
175   GdkPixbuf *paste_pb;
176   GdkPixbuf *base_pb;
177
178   int paste_w, paste_h;
179   int base_w, base_h;
180
181   paste_pb = load_pixbuf (paste_file);
182   base_pb  = load_pixbuf (base_file);
183
184   paste_w = gdk_pixbuf_get_width (paste_pb);
185   paste_h = gdk_pixbuf_get_height (paste_pb);
186
187   base_w = gdk_pixbuf_get_width (base_pb);
188   base_h = gdk_pixbuf_get_height (base_pb);
189
190   if (verbose_p)
191     {
192       fprintf (stderr, "%s: loaded %s: %dx%d\n",
193                progname, base_file, base_w, base_h);
194       fprintf (stderr, "%s: loaded %s: %dx%d\n",
195                progname, paste_file, paste_w, paste_h);
196     }
197
198   if (from_scale != 1.0)
199     {
200       int new_w = paste_w * from_scale;
201       int new_h = paste_h * from_scale;
202       GdkPixbuf *new_pb = gdk_pixbuf_scale_simple (paste_pb, new_w, new_h,
203                                                    GDK_INTERP_HYPER);
204       gdk_pixbuf_unref (paste_pb);
205       paste_pb = new_pb;
206       paste_w = gdk_pixbuf_get_width (paste_pb);
207       paste_h = gdk_pixbuf_get_height (paste_pb);
208
209       if (verbose_p)
210         fprintf (stderr, "%s: %s: scaled to %dx%d (%.2f)\n",
211                  progname, paste_file, paste_w, paste_h, from_scale);
212     }
213
214   if (w == 0) w = paste_w - from_x;
215   if (h == 0) h = paste_h - from_y;
216
217   {
218     int ofx = from_x;
219     int ofy = from_y;
220     int otx = to_x;
221     int oty = to_y;
222     int ow = w;
223     int oh = h;
224
225     int clipped = 0;
226
227     if (from_x < 0)             /* from left out of bounds */
228       {
229         w += from_x;
230         from_x = 0;
231         clipped = 1;
232       }
233
234     if (from_y < 0)             /* from top out of bounds */
235       {
236         h += from_y;
237         from_y = 0;
238         clipped = 1;
239       }
240
241     if (to_x < 0)               /* to left out of bounds */
242       {
243         w += to_x;
244         from_x -= to_x;
245         to_x = 0;
246         clipped = 1;
247       }
248
249     if (to_y < 0)               /* to top out of bounds */
250       {
251         h += to_y;
252         from_y -= to_y;
253         to_y = 0;
254         clipped = 1;
255       }
256
257     if (from_x + w > paste_w)   /* from right out of bounds */
258       {
259         w = paste_w - from_x;
260         clipped = 1;
261       }
262
263     if (from_y + h > paste_h)   /* from bottom out of bounds */
264       {
265         h = paste_h - from_y;
266         clipped = 1;
267       }
268
269     if (to_x + w > base_w)      /* to right out of bounds */
270       {
271         w = base_w - to_x;
272         clipped = 1;
273       }
274
275     if (to_y + h > base_h)      /* to bottom out of bounds */
276       {
277         h = base_h - to_y;
278         clipped = 1;
279       }
280
281
282     if (clipped && verbose_p)
283       {
284         fprintf (stderr, "clipped from: %dx%d %d,%d %d,%d\n",
285                  ow, oh, ofx, ofy, otx, oty);
286         fprintf (stderr, "clipped   to: %dx%d %d,%d %d,%d\n",
287                  w, h, from_x, from_y, to_x, to_y);
288       }
289   }
290
291   if (bevel_pct > 0)
292     bevel_image (&paste_pb, bevel_pct,
293                  from_x, from_y, w, h);
294
295   if (opacity == 1.0 && bevel_pct == 0)
296     gdk_pixbuf_copy_area (paste_pb,
297                           from_x, from_y, w, h,
298                           base_pb,
299                           to_x, to_y);
300   else
301     {
302       from_x++;  /* gdk_pixbuf_composite gets confused about the bevel: */
303       from_y++;  /* it leaves a stripe on the top and left if we try to */
304       to_x++;    /* start at 0,0, so pull it right and down by 1 pixel. */
305       to_y++;    /* (problem seen in gtk2-2.4.14-2.fc3) */
306       w--;
307       h--;
308
309       if (w > 0 && h > 0)
310         gdk_pixbuf_composite (paste_pb, base_pb,
311                               to_x, to_y, w, h,
312                               to_x - from_x, to_y - from_y,
313                               1.0, 1.0,
314                               GDK_INTERP_HYPER,
315                               opacity * 255);
316     }
317
318   if (verbose_p)
319     fprintf (stderr, "%s: pasted %dx%d from %d,%d to %d,%d\n",
320              progname, paste_w, paste_h, from_x, from_y, to_x, to_y);
321
322   gdk_pixbuf_unref (paste_pb);
323   write_pixbuf (base_pb, base_file);
324   gdk_pixbuf_unref (base_pb);
325 }
326
327
328 static void
329 write_pixbuf (GdkPixbuf *pb, const char *file)
330 {
331   int jpeg_quality = 85;
332
333   int w = gdk_pixbuf_get_width (pb);
334   int h = gdk_pixbuf_get_height (pb);
335   guchar *data = gdk_pixbuf_get_pixels (pb);
336   int ww = gdk_pixbuf_get_rowstride (pb);
337   int channels = gdk_pixbuf_get_n_channels (pb);
338
339   struct jpeg_compress_struct cinfo;
340   struct jpeg_error_mgr jerr;
341   FILE *out;
342
343   if (channels != 3)
344     {
345       fprintf (stderr, "%s: %d channels?\n", progname, channels);
346       exit (1);
347     }
348
349   cinfo.err = jpeg_std_error (&jerr);
350   jpeg_create_compress (&cinfo);
351
352   out = fopen (file, "wb");
353   if (!out)
354     {
355       char buf[255];
356       sprintf (buf, "%.100s: %.100s", progname, file);
357       perror (buf);
358       exit (1);
359     }
360   else if (verbose_p)
361     fprintf (stderr, "%s: writing %s...", progname, file);
362
363   jpeg_stdio_dest (&cinfo, out);
364
365   cinfo.image_width = w;
366   cinfo.image_height = h;
367   cinfo.input_components = channels;
368   cinfo.in_color_space = JCS_RGB;
369
370   jpeg_set_defaults (&cinfo);
371   jpeg_simple_progression (&cinfo);
372   jpeg_set_quality (&cinfo, jpeg_quality, TRUE);
373
374   jpeg_start_compress (&cinfo, TRUE);
375   add_jpeg_comment (&cinfo);
376
377   {
378     guchar *d = data;
379     guchar *end = d + (ww * h);
380     while (d < end)
381       {
382         jpeg_write_scanlines (&cinfo, &d, 1);
383         d += ww;
384       }
385   }
386
387   jpeg_finish_compress (&cinfo);
388   jpeg_destroy_compress (&cinfo);
389
390   if (verbose_p)
391     {
392       struct stat st;
393       fflush (out);
394       if (fstat (fileno (out), &st))
395         {
396           char buf[255];
397           sprintf (buf, "%.100s: %.100s", progname, file);
398           perror (buf);
399           exit (1);
400         }
401       fprintf (stderr, " %luK\n", ((unsigned long) st.st_size + 1023) / 1024);
402     }
403
404   fclose (out);
405 }
406
407
408 static void
409 add_jpeg_comment (struct jpeg_compress_struct *cinfo)
410 {
411   time_t now = time ((time_t *) 0);
412   struct tm *tm = localtime (&now);
413   const char fmt[] =
414     "\r\n"
415     "    Generated by WebCollage: Exterminate All Rational Thought. \r\n"
416     "    Copyright (c) 1999-%Y by Jamie Zawinski <jwz@jwz.org> \r\n"
417     "\r\n"
418     "        http://www.jwz.org/webcollage/ \r\n"
419     "\r\n"
420     "    This is what the web looked like on %d %b %Y at %I:%M:%S %p %Z. \r\n"
421     "\r\n";
422   char comment[sizeof(fmt) + 100];
423   strftime (comment, sizeof(comment)-1, fmt, tm);
424   jpeg_write_marker (cinfo, JPEG_COM,
425                      (unsigned char *) comment,
426                      strlen (comment));
427 }
428
429
430 static void
431 usage (void)
432 {
433   fprintf (stderr, "usage: %s [-v] paste-file base-file\n"
434            "\t from-scale opacity\n"
435            "\t from-x from-y to-x to-y w h\n"
436            "\n"
437            "\t Pastes paste-file into base-file.\n"
438            "\t base-file will be overwritten (with JPEG data.)\n"
439            "\t scaling is applied first: coordinates apply to scaled image.\n",
440            progname);
441   exit (1);
442 }
443
444
445 int
446 main (int argc, char **argv)
447 {
448   int i;
449   char *paste_file, *base_file, *s, dummy;
450   double from_scale, opacity;
451   int from_x, from_y, to_x, to_y, w, h, bevel_pct;
452
453   i = 0;
454   progname = argv[i++];
455   s = strrchr (progname, '/');
456   if (s) progname = s+1;
457
458   if (argc != 11 && argc != 12) usage();
459
460   if (!strcmp(argv[i], "-v"))
461     verbose_p++, i++;
462
463   paste_file = argv[i++];
464   base_file = argv[i++];
465
466   if (*paste_file == '-') usage();
467   if (*base_file == '-') usage();
468
469   s = argv[i++];
470   if (1 != sscanf (s, " %lf %c", &from_scale, &dummy)) usage();
471   if (from_scale <= 0 || from_scale > 100) usage();
472
473   s = argv[i++];
474   if (1 != sscanf (s, " %lf %c", &opacity, &dummy)) usage();
475   if (opacity <= 0 || opacity > 1) usage();
476
477   s = argv[i++]; if (1 != sscanf (s, " %d %c", &from_x, &dummy)) usage();
478   s = argv[i++]; if (1 != sscanf (s, " %d %c", &from_y, &dummy)) usage();
479   s = argv[i++]; if (1 != sscanf (s, " %d %c", &to_x, &dummy)) usage();
480   s = argv[i++]; if (1 != sscanf (s, " %d %c", &to_y, &dummy)) usage();
481   s = argv[i++]; if (1 != sscanf (s, " %d %c", &w, &dummy)) usage();
482   s = argv[i++]; if (1 != sscanf (s, " %d %c", &h, &dummy)) usage();
483
484   bevel_pct = 10; /* #### */
485
486   if (w < 0) usage();
487   if (h < 0) usage();
488
489 #ifdef HAVE_GTK2
490   g_type_init ();
491 #endif /* HAVE_GTK2 */
492
493   paste (paste_file, base_file,
494          from_scale, opacity, bevel_pct,
495          from_x, from_y, to_x, to_y,
496          w, h);
497   exit (0);
498 }
499
500 #endif /* HAVE_GDK_PIXBUF && HAVE_JPEGLIB -- whole file */