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