From http://www.jwz.org/xscreensaver/xscreensaver-5.38.tar.gz
[xscreensaver] / hacks / webcollage-helper-cocoa.m
1 /* webcollage-helper-cocoa --- 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 Cocoa implementation.  See webcollage-helper.c for the
14    GDK + JPEGlib implementation.
15  */
16
17 #import <Cocoa/Cocoa.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <unistd.h>
21 #include <sys/stat.h>
22
23
24 #if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4
25  typedef int          NSInteger;
26  typedef unsigned int NSUInteger;
27 #endif
28
29
30 char *progname;
31 static int verbose_p = 0;
32
33 static void write_image (NSImage *img, const char *file);
34
35
36 /* NSImage can't load PPMs by default...
37  */
38 static NSImage *
39 load_ppm_image (const char *file)
40 {
41   FILE *in = fopen (file, "r");
42   if (! in) return 0;
43
44   char buf[255];
45
46   char *s = fgets (buf, sizeof(buf)-1, in);             /* P6 */
47   if (!s || !!strcmp (s, "P6\n")) 
48     return 0;
49
50   s = fgets (buf, sizeof(buf)-1, in);                   /* W H */
51   if (!s)
52     return 0;
53
54   int w = 0, h = 0, d = 0;
55   if (2 != sscanf (buf, " %d %d \n", &w, &h))
56     return 0;
57   if (w <= 0 || h <= 0)
58     return 0;
59
60   s = fgets (buf, sizeof(buf)-1, in);                   /* 255 */
61   if (!s)
62     return 0;
63
64   if (1 != sscanf (buf, " %d \n", &d))
65     return 0;
66   if (d != 255)
67     return 0;
68
69   int size = (w * (h+1) * 3);
70   unsigned char *bits = malloc (size);
71   if (!bits) return 0;
72
73   int n = read (fileno (in), (void *) bits, size);      /* body */
74   if (n < 20) return 0;
75
76   fclose (in);
77   
78   NSBitmapImageRep *rep =
79     [[NSBitmapImageRep alloc]
80       initWithBitmapDataPlanes: &bits
81                     pixelsWide: w
82                     pixelsHigh: h
83                  bitsPerSample: 8
84                samplesPerPixel: 3
85                       hasAlpha: NO
86                       isPlanar: NO
87                 colorSpaceName: NSDeviceRGBColorSpace
88                   bitmapFormat: NSAlphaFirstBitmapFormat
89                    bytesPerRow: w * 3
90                   bitsPerPixel: 8 * 3];
91
92   NSImage *image = [[NSImage alloc] initWithSize: NSMakeSize (w, h)];
93   [image addRepresentation: rep];
94   [rep release];
95
96   // #### 'bits' is leaked... the NSImageRep doesn't free it when freed.
97
98   return image;
99 }
100
101
102 static NSImage *
103 load_image (const char *file)
104 {
105   NSImage *image = [[NSImage alloc] 
106                      initWithContentsOfFile:
107                        [NSString stringWithCString: file
108                                           encoding: NSUTF8StringEncoding]];
109   if (! image)
110     image = load_ppm_image (file);
111
112   if (! image) {
113     fprintf (stderr, "%s: unable to load %s\n", progname, file);
114     exit (1);
115   }
116
117   
118   // [NSImage size] defaults to the image size in points instead of pixels,
119   // so if an image file specified "pixels per inch" we can end up with
120   // absurdly sized images.  Set it back to 1:1 pixel:point.
121   //
122   NSImageRep *rep = [image.representations objectAtIndex:0];
123   image.size = NSMakeSize (rep.pixelsWide, rep.pixelsHigh);
124
125   return image;
126 }
127
128
129 static void
130 bevel_image (NSImage *img, int bevel_pct,
131              int x, int y, int w, int h, double scale)
132 {
133   int small_size = (w > h ? h : w);
134
135   int bevel_size = small_size * (bevel_pct / 100.0);
136
137   bevel_size /= scale;
138
139   /* Use a proportionally larger bevel size for especially small images. */
140   if      (bevel_size < 20 && small_size > 40) bevel_size = 20;
141   else if (bevel_size < 10 && small_size > 20) bevel_size = 10;
142   else if (bevel_size < 5)    /* too small to bother bevelling */
143     return;
144
145
146   NSBitmapImageRep *rep =
147     [[NSBitmapImageRep alloc]
148       initWithBitmapDataPlanes: NULL
149                     pixelsWide: w
150                     pixelsHigh: h
151                  bitsPerSample: 8
152                samplesPerPixel: 4
153                       hasAlpha: YES
154                       isPlanar: NO
155                 colorSpaceName: NSDeviceRGBColorSpace
156                   bitmapFormat: NSAlphaFirstBitmapFormat
157                    bytesPerRow: 0
158                   bitsPerPixel: 0];
159
160   NSInteger xx, yy;
161   double *ramp = (double *) malloc (sizeof(*ramp) * (bevel_size + 1));
162
163   if (!ramp)
164     {
165       fprintf (stderr, "%s: out of memory (%d)\n", progname, bevel_size);
166       exit (1);
167     }
168
169   for (xx = 0; xx <= bevel_size; xx++)
170     {
171 # if 0  /* linear */
172       ramp[xx] = xx / (double) bevel_size;
173
174 # else /* sinusoidal */
175       double p = (xx / (double) bevel_size);
176       double s = sin (p * M_PI / 2);
177       ramp[xx] = s;
178 # endif
179     }
180
181   memset ([rep bitmapData], 0xFFFFFFFF,
182           [rep bytesPerRow] * h);
183
184   for (yy = 0; yy < h; yy++)
185     {
186       for (xx = 0; xx < w; xx++)
187         {
188           double rx, ry, r;
189
190           if (xx < bevel_size)           rx = ramp[xx];
191           else if (xx >= w - bevel_size) rx = ramp[w - xx - 1];
192           else rx = 1;
193
194           if (yy < bevel_size)           ry = ramp[yy];
195           else if (yy >= h - bevel_size) ry = ramp[h - yy - 1];
196           else ry = 1;
197
198           r = rx * ry;
199           if (r != 1)
200             {
201               NSUInteger p[4];
202               p[0] = 0xFF * r;
203               p[1] = p[2] = p[3] = 0xFF;
204               [rep setPixel:p atX:xx y:yy];
205             }
206         }
207     }
208
209   free (ramp);
210
211   NSImage *bevel_img = [[NSImage alloc]
212                          initWithData: [rep TIFFRepresentation]];
213
214   [img lockFocus];
215   y = [img size].height - (y + h);
216   [bevel_img drawAtPoint: NSMakePoint (x, y)
217                 fromRect: NSMakeRect (0, 0, w, h)
218                operation: NSCompositeDestinationIn /* Destination image
219                                                       wherever both images are
220                                                       opaque, transparent
221                                                       elsewhere. */
222                 fraction: 1.0];
223   [img unlockFocus];
224
225   [rep release];
226   [bevel_img release];
227
228   if (verbose_p)
229     fprintf (stderr, "%s: added %d%% bevel (%d px)\n", progname,
230              bevel_pct, bevel_size);
231 }
232
233
234 static void
235 paste (const char *paste_file,
236        const char *base_file,
237        double from_scale,
238        double opacity, int bevel_pct,
239        int from_x, int from_y, int to_x, int to_y,
240        int w, int h)
241 {
242   NSImage *paste_img = load_image (paste_file);
243   NSImage *base_img  = load_image (base_file);
244
245   int paste_w = [paste_img size].width;
246   int paste_h = [paste_img size].height;
247
248   int base_w  = [base_img size].width;
249   int base_h  = [base_img size].height;
250
251   if (verbose_p)
252     {
253       fprintf (stderr, "%s: loaded %s: %dx%d\n",
254                progname, base_file, base_w, base_h);
255       fprintf (stderr, "%s: loaded %s: %dx%d\n",
256                progname, paste_file, paste_w, paste_h);
257     }
258
259   if (bevel_pct > 0 && paste_w > 5 && paste_h > 5)
260     bevel_image (paste_img, bevel_pct,
261                  from_x, from_y, w, h, 
262                  from_scale);
263
264   int scaled_w = w * from_scale;
265   int scaled_h = h * from_scale;
266
267   from_y = paste_h - (from_y + h);  // Cocoa flipped coordinate system
268   to_y   = base_h  - (to_y + scaled_h);
269
270   [base_img lockFocus];
271   [paste_img drawInRect: NSMakeRect (to_x, to_y, scaled_w, scaled_h)
272                fromRect: NSMakeRect (from_x, from_y, w, h)
273               operation: NSCompositeSourceOver
274                fraction: opacity];
275   [base_img unlockFocus];
276
277   if (verbose_p)
278     fprintf (stderr, "%s: pasted %dx%d (%dx%d) from %d,%d to %d,%d\n",
279              progname, w, h, scaled_w, scaled_h, from_x, from_y, to_x, to_y);
280
281   [paste_img release];
282   write_image (base_img, base_file);
283   [base_img release];
284 }
285
286
287 static NSColor *
288 parse_color (const char *s)
289 {
290   static const char hex[128] =
291     {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
292      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
293      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
294      0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0,
295      0, 10,11,12,13,14,15,0, 0, 0, 0, 0, 0, 0, 0, 0,
296      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
297      0, 10,11,12,13,14,15,0, 0, 0, 0, 0, 0, 0, 0, 0,
298      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
299
300   unsigned char r=0, g=0, b=0;
301
302   if      (!strcasecmp (s, "black")) ;
303   else if (!strcasecmp (s, "white")) r = g = b = 0xFF;
304   else if (!strcasecmp (s, "red"))   r = 0xFF;
305   else if (!strcasecmp (s, "green")) g = 0xFF;
306   else if (!strcasecmp (s, "blue"))  b = 0xFF;
307   else
308     {
309       if (*s != '#' || strlen(s) != 7)
310         {
311           fprintf (stderr, "%s: unparsable color: \"%s\"\n", progname, s);
312           exit (1);
313         }
314       s++;
315       r = (hex[(int) s[0]] << 4) | hex[(int) s[1]], s += 2;
316       g = (hex[(int) s[0]] << 4) | hex[(int) s[1]], s += 2;
317       b = (hex[(int) s[0]] << 4) | hex[(int) s[1]], s += 2;
318     }
319
320   return [NSColor colorWithRed: r / 255.0
321                          green: g / 255.0
322                           blue: b / 255.0
323                          alpha: 1.0];
324 }
325
326
327 static void
328 create (const char *color,
329         int w, int h,
330         const char *file)
331 {
332   NSColor *c = parse_color (color);
333   NSImage *img = [[NSImage alloc] initWithSize:NSMakeSize(w, h)];
334   [img lockFocus];
335   [c drawSwatchInRect:NSMakeRect(0, 0, w, h)];
336   [img unlockFocus];
337   write_image (img, file);
338   [img release];
339 }
340
341
342 static void
343 write_image (NSImage *img, const char *file)
344 {
345   float jpeg_quality = .85;
346
347   // Load the NSImage's contents into an NSBitmapImageRep:
348   NSBitmapImageRep *bit_rep = [NSBitmapImageRep
349                                 imageRepWithData:[img TIFFRepresentation]];
350
351   // Write the bitmapImageRep to a JPEG file.
352   if (bit_rep == nil)
353     {
354       fprintf (stderr, "%s: error converting image?\n", progname);
355       exit (1);
356     }
357
358   if (verbose_p)
359     fprintf (stderr, "%s: writing %s (q=%d%%) ", progname, file, 
360              (int) (jpeg_quality * 100));
361
362   NSDictionary *props = [NSDictionary
363                           dictionaryWithObject:
364                             [NSNumber numberWithFloat:jpeg_quality]
365                           forKey:NSImageCompressionFactor];
366   NSData *jpeg_data = [bit_rep representationUsingType:NSJPEGFileType
367                                properties:props];
368
369   [jpeg_data writeToFile:
370                [NSString stringWithCString:file
371                                   encoding:NSISOLatin1StringEncoding]
372              atomically:YES];
373
374   if (verbose_p)
375     {
376       struct stat st;
377       if (stat (file, &st))
378         {
379           char buf[255];
380           sprintf (buf, "%.100s: %.100s", progname, file);
381           perror (buf);
382           exit (1);
383         }
384       fprintf (stderr, " %luK\n", ((unsigned long) st.st_size + 1023) / 1024);
385     }
386 }
387
388
389 static void
390 usage (void)
391 {
392   fprintf (stderr,
393            "\nusage: %s [-v] paste-file base-file\n"
394            "\t from-scale opacity\n"
395            "\t from-x from-y to-x to-y w h\n"
396            "\n"
397            "\t Pastes paste-file into base-file.\n"
398            "\t base-file will be overwritten (with JPEG data.)\n"
399            "\t scaling is applied first: coordinates apply to scaled image.\n"
400            "\n"
401            "usage: %s [-v] color width height output-file\n"
402            "\t Creates a new image of a solid color.\n\n",
403            progname, progname);
404   exit (1);
405 }
406
407
408 int
409 main (int argc, char **argv)
410 {
411   int i;
412   char *paste_file, *base_file, *s, dummy;
413   double from_scale, opacity;
414   int from_x, from_y, to_x, to_y, w, h, bevel_pct;
415
416   i = 0;
417   progname = argv[i++];
418   s = strrchr (progname, '/');
419   if (s) progname = s+1;
420
421   // Much of Cocoa needs one of these to be available.
422   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
423
424   //Need an NSApp instance to make [NSImage TIFFRepresentation] work
425   NSApp = [NSApplication sharedApplication];
426   [NSApp autorelease];
427
428   if (!strcmp(argv[i], "-v"))
429     verbose_p++, i++;
430
431   if (argc == 11 || argc == 12)
432     {
433       paste_file = argv[i++];
434       base_file = argv[i++];
435
436       if (*paste_file == '-') usage();
437       if (*base_file == '-') usage();
438
439       s = argv[i++];
440       if (1 != sscanf (s, " %lf %c", &from_scale, &dummy)) usage();
441       if (from_scale <= 0 || from_scale > 100) usage();
442
443       s = argv[i++];
444       if (1 != sscanf (s, " %lf %c", &opacity, &dummy)) usage();
445       if (opacity <= 0 || opacity > 1) usage();
446
447       s = argv[i++]; if (1 != sscanf (s, " %d %c", &from_x, &dummy)) usage();
448       s = argv[i++]; if (1 != sscanf (s, " %d %c", &from_y, &dummy)) usage();
449       s = argv[i++]; if (1 != sscanf (s, " %d %c", &to_x, &dummy)) usage();
450       s = argv[i++]; if (1 != sscanf (s, " %d %c", &to_y, &dummy)) usage();
451       s = argv[i++]; if (1 != sscanf (s, " %d %c", &w, &dummy)) usage();
452       s = argv[i];   if (1 != sscanf (s, " %d %c", &h, &dummy)) usage();
453
454       bevel_pct = 10; /* #### */
455
456       if (w < 0) usage();
457       if (h < 0) usage();
458
459       if (w == 0 || h == 0 || 
460           w > 10240 || h > 10240) {
461         fprintf (stderr, "%s: absurd size: %d x %d\n", progname, w, h);
462         exit (1);
463       }
464
465       paste (paste_file, base_file,
466              from_scale, opacity, bevel_pct,
467              from_x, from_y, to_x, to_y,
468              w, h);
469     }
470   else if (argc == 4 || argc == 5)
471     {
472       char *color = argv[i++];
473       s = argv[i++]; if (1 != sscanf (s, " %d %c", &w, &dummy)) usage();
474       s = argv[i++]; if (1 != sscanf (s, " %d %c", &h, &dummy)) usage();
475       paste_file = argv[i++];
476       if (*paste_file == '-') usage();
477
478       create (color, w, h, paste_file);
479     }
480   else
481     {
482       usage();
483     }
484
485   [pool release];
486
487   exit (0);
488 }