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