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