From http://www.jwz.org/xscreensaver/xscreensaver-5.38.tar.gz
[xscreensaver] / hacks / webcollage-helper.c
1 /* webcollage-helper --- scales and pastes one image into another
2  * xscreensaver, Copyright (c) 2002-2017 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 guint32
333 parse_color (const char *s)
334 {
335   static const char hex[128] =
336     {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
337      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
338      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
339      0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0,
340      0, 10,11,12,13,14,15,0, 0, 0, 0, 0, 0, 0, 0, 0,
341      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
342      0, 10,11,12,13,14,15,0, 0, 0, 0, 0, 0, 0, 0, 0,
343      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
344
345   unsigned char r=0, g=0, b=0;
346
347   if      (!strcasecmp (s, "black")) ;
348   else if (!strcasecmp (s, "white")) r = g = b = 0xFF;
349   else if (!strcasecmp (s, "red"))   r = 0xFF;
350   else if (!strcasecmp (s, "green")) g = 0xFF;
351   else if (!strcasecmp (s, "blue"))  b = 0xFF;
352   else
353     {
354       if (*s != '#' || strlen(s) != 7)
355         {
356           fprintf (stderr, "%s: unparsable color: \"%s\"\n", progname, s);
357           exit (1);
358         }
359       s++;
360       r = (hex[(int) s[0]] << 4) | hex[(int) s[1]], s += 2;
361       g = (hex[(int) s[0]] << 4) | hex[(int) s[1]], s += 2;
362       b = (hex[(int) s[0]] << 4) | hex[(int) s[1]], s += 2;
363     }
364
365   return (r << 16) | (g << 8) | b;
366 }
367
368
369 static void
370 create (const char *color,
371         int w, int h,
372         const char *file)
373 {
374   int i;
375   GdkPixbuf *pb;
376   guint32 pixel = parse_color (color);
377   unsigned char *bytes = malloc (w * h * 3);
378   if (!bytes) abort();
379   for (i = 0; i < w * h * 3; i += 3)
380     {
381       bytes[i]   = 0xFF & (pixel >> 16);
382       bytes[i+1] = 0xFF & (pixel >> 8);
383       bytes[i+2] = 0xFF & (pixel);
384     }
385
386   pb = gdk_pixbuf_new_from_data (bytes, GDK_COLORSPACE_RGB,
387                                  FALSE, 8, /* alpha, sample size */
388                                  w, h,
389                                  w * 3,   /* rowstride */
390                                  NULL, 0);
391   if (!pb) abort();
392   write_pixbuf (pb, file);
393   g_object_unref (pb);
394   free (bytes);
395 }
396
397
398 static void
399 write_pixbuf (GdkPixbuf *pb, const char *file)
400 {
401   int jpeg_quality = 85;
402
403   int w = gdk_pixbuf_get_width (pb);
404   int h = gdk_pixbuf_get_height (pb);
405   guchar *data = gdk_pixbuf_get_pixels (pb);
406   int ww = gdk_pixbuf_get_rowstride (pb);
407   int channels = gdk_pixbuf_get_n_channels (pb);
408
409   struct jpeg_compress_struct cinfo;
410   struct jpeg_error_mgr jerr;
411   FILE *out;
412
413   if (channels != 3)
414     {
415       fprintf (stderr, "%s: %d channels?\n", progname, channels);
416       exit (1);
417     }
418
419   cinfo.err = jpeg_std_error (&jerr);
420   jpeg_create_compress (&cinfo);
421
422   out = fopen (file, "wb");
423   if (!out)
424     {
425       char buf[255];
426       sprintf (buf, "%.100s: %.100s", progname, file);
427       perror (buf);
428       exit (1);
429     }
430   else if (verbose_p)
431     fprintf (stderr, "%s: writing %s...", progname, file);
432
433   jpeg_stdio_dest (&cinfo, out);
434
435   cinfo.image_width = w;
436   cinfo.image_height = h;
437   cinfo.input_components = channels;
438   cinfo.in_color_space = JCS_RGB;
439
440   jpeg_set_defaults (&cinfo);
441   jpeg_simple_progression (&cinfo);
442   jpeg_set_quality (&cinfo, jpeg_quality, TRUE);
443
444   jpeg_start_compress (&cinfo, TRUE);
445   add_jpeg_comment (&cinfo);
446
447   {
448     guchar *d = data;
449     guchar *end = d + (ww * h);
450     while (d < end)
451       {
452         jpeg_write_scanlines (&cinfo, &d, 1);
453         d += ww;
454       }
455   }
456
457   jpeg_finish_compress (&cinfo);
458   jpeg_destroy_compress (&cinfo);
459
460   if (verbose_p)
461     {
462       struct stat st;
463       fflush (out);
464       if (fstat (fileno (out), &st))
465         {
466           char buf[255];
467           sprintf (buf, "%.100s: %.100s", progname, file);
468           perror (buf);
469           exit (1);
470         }
471       fprintf (stderr, " %luK\n", ((unsigned long) st.st_size + 1023) / 1024);
472     }
473
474   fclose (out);
475 }
476
477
478 static void
479 add_jpeg_comment (struct jpeg_compress_struct *cinfo)
480 {
481   time_t now = time ((time_t *) 0);
482   struct tm *tm = localtime (&now);
483   const char fmt[] =
484     "\r\n"
485     "    Generated by WebCollage: Exterminate All Rational Thought. \r\n"
486     "    Copyright (c) 1999-%Y by Jamie Zawinski <jwz@jwz.org> \r\n"
487     "\r\n"
488     "        https://www.jwz.org/webcollage/ \r\n"
489     "\r\n"
490     "    This is what the web looked like on %d %b %Y at %I:%M:%S %p %Z. \r\n"
491     "\r\n";
492   char comment[sizeof(fmt) + 100];
493   strftime (comment, sizeof(comment)-1, fmt, tm);
494   jpeg_write_marker (cinfo, JPEG_COM,
495                      (unsigned char *) comment,
496                      strlen (comment));
497 }
498
499
500 static void
501 usage (void)
502 {
503   fprintf (stderr,
504            "\nusage: %s [-v] paste-file base-file\n"
505            "\t from-scale opacity\n"
506            "\t from-x from-y to-x to-y w h\n"
507            "\n"
508            "\t Pastes paste-file into base-file.\n"
509            "\t base-file will be overwritten (with JPEG data.)\n"
510            "\t scaling is applied first: coordinates apply to scaled image.\n"
511            "\n"
512            "usage: %s [-v] color width height output-file\n"
513            "\t Creates a new image of a solid color.\n\n",
514            progname, progname);
515   exit (1);
516 }
517
518
519 int
520 main (int argc, char **argv)
521 {
522   int i;
523   char *paste_file, *base_file, *s, dummy;
524   double from_scale, opacity;
525   int from_x, from_y, to_x, to_y, w, h, bevel_pct;
526
527   i = 0;
528   progname = argv[i++];
529   s = strrchr (progname, '/');
530   if (s) progname = s+1;
531
532   if (!strcmp(argv[i], "-v"))
533     verbose_p++, i++;
534
535   if (argc == 11 || argc == 12)
536     {
537       paste_file = argv[i++];
538       base_file = argv[i++];
539
540       if (*paste_file == '-') usage();
541       if (*base_file == '-') usage();
542
543       s = argv[i++];
544       if (1 != sscanf (s, " %lf %c", &from_scale, &dummy)) usage();
545       if (from_scale <= 0 || from_scale > 100) usage();
546
547       s = argv[i++];
548       if (1 != sscanf (s, " %lf %c", &opacity, &dummy)) usage();
549       if (opacity <= 0 || opacity > 1) usage();
550
551       s = argv[i++]; if (1 != sscanf (s, " %d %c", &from_x, &dummy)) usage();
552       s = argv[i++]; if (1 != sscanf (s, " %d %c", &from_y, &dummy)) usage();
553       s = argv[i++]; if (1 != sscanf (s, " %d %c", &to_x, &dummy)) usage();
554       s = argv[i++]; if (1 != sscanf (s, " %d %c", &to_y, &dummy)) usage();
555       s = argv[i++]; if (1 != sscanf (s, " %d %c", &w, &dummy)) usage();
556       s = argv[i];   if (1 != sscanf (s, " %d %c", &h, &dummy)) usage();
557
558       bevel_pct = 10; /* #### */
559
560       if (w < 0) usage();
561       if (h < 0) usage();
562
563 # ifdef HAVE_GTK2
564 #  if !GLIB_CHECK_VERSION(2, 36 ,0)
565       g_type_init ();
566 #  endif
567 # endif /* HAVE_GTK2 */
568
569       paste (paste_file, base_file,
570              from_scale, opacity, bevel_pct,
571              from_x, from_y, to_x, to_y,
572              w, h);
573     }
574   else if (argc == 4 || argc == 5)
575     {
576       char *color = argv[i++];
577       s = argv[i++]; if (1 != sscanf (s, " %d %c", &w, &dummy)) usage();
578       s = argv[i++]; if (1 != sscanf (s, " %d %c", &h, &dummy)) usage();
579       paste_file = argv[i++];
580       if (*paste_file == '-') usage();
581
582       create (color, w, h, paste_file);
583     }
584   else
585     {
586       usage();
587     }
588
589   exit (0);
590 }
591
592 #endif /* HAVE_GDK_PIXBUF && HAVE_JPEGLIB -- whole file */