From http://www.jwz.org/xscreensaver/xscreensaver-5.39.tar.gz
[xscreensaver] / hacks / webcollage-helper-cocoa.m
1 /* webcollage-helper-cocoa --- scales and pastes one image into another
2  * xscreensaver, Copyright (c) 2002-2018 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
349 #if 0
350   // If the local display is Retina, this doubles the size of the output JPEG.
351   NSBitmapImageRep *bit_rep = [NSBitmapImageRep
352                                 imageRepWithData:[img TIFFRepresentation]];
353 #else
354   // Render the image into a rep using pixels instead of points.
355   NSBitmapImageRep *bit_rep = [[NSBitmapImageRep alloc]
356                                 initWithBitmapDataPlanes:NULL
357                                 pixelsWide:[img size].width
358                                 pixelsHigh:[img size].height
359                                 bitsPerSample:8
360                                 samplesPerPixel:4
361                                 hasAlpha:YES
362                                 isPlanar:NO
363                                 colorSpaceName:NSCalibratedRGBColorSpace
364                                 bytesPerRow:0
365                                 bitsPerPixel:0];
366     bit_rep.size = [img size];
367     [NSGraphicsContext saveGraphicsState];
368     [NSGraphicsContext setCurrentContext:
369                          [NSGraphicsContext
370                            graphicsContextWithBitmapImageRep:bit_rep]];
371     [img drawInRect:NSMakeRect(0, 0, [img size].width, [img size].height)
372          fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.0];
373     [NSGraphicsContext restoreGraphicsState];
374 #endif
375
376   // Write the bitmapImageRep to a JPEG file.
377   if (bit_rep == nil)
378     {
379       fprintf (stderr, "%s: error converting image?\n", progname);
380       exit (1);
381     }
382
383   if (verbose_p)
384     fprintf (stderr, "%s: writing %s (q=%d%%) ", progname, file, 
385              (int) (jpeg_quality * 100));
386
387   NSData *jpeg_data = [bit_rep representationUsingType:NSJPEGFileType
388                                properties:@{ NSImageCompressionFactor: 
389                                              [NSNumber numberWithFloat:
390                                                          jpeg_quality] }];
391   [jpeg_data writeToFile:
392                [NSString stringWithCString:file
393                                   encoding:NSISOLatin1StringEncoding]
394              atomically:YES];
395
396   if (verbose_p)
397     {
398       struct stat st;
399       if (stat (file, &st))
400         {
401           char buf[255];
402           sprintf (buf, "%.100s: %.100s", progname, file);
403           perror (buf);
404           exit (1);
405         }
406       fprintf (stderr, " %luK\n", ((unsigned long) st.st_size + 1023) / 1024);
407     }
408 }
409
410
411 static void
412 usage (void)
413 {
414   fprintf (stderr,
415            "\nusage: %s [-v] paste-file base-file\n"
416            "\t from-scale opacity\n"
417            "\t from-x from-y to-x to-y w h\n"
418            "\n"
419            "\t Pastes paste-file into base-file.\n"
420            "\t base-file will be overwritten (with JPEG data.)\n"
421            "\t scaling is applied first: coordinates apply to scaled image.\n"
422            "\n"
423            "usage: %s [-v] color width height output-file\n"
424            "\t Creates a new image of a solid color.\n\n",
425            progname, progname);
426   exit (1);
427 }
428
429
430 int
431 main (int argc, char **argv)
432 {
433   int i;
434   char *paste_file, *base_file, *s, dummy;
435   double from_scale, opacity;
436   int from_x, from_y, to_x, to_y, w, h, bevel_pct;
437
438   i = 0;
439   progname = argv[i++];
440   s = strrchr (progname, '/');
441   if (s) progname = s+1;
442
443   // Much of Cocoa needs one of these to be available.
444   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
445
446   //Need an NSApp instance to make [NSImage TIFFRepresentation] work
447   NSApp = [NSApplication sharedApplication];
448   [NSApp autorelease];
449
450   if (!strcmp(argv[i], "-v"))
451     verbose_p++, i++;
452
453   if (argc == 11 || argc == 12)
454     {
455       paste_file = argv[i++];
456       base_file = argv[i++];
457
458       if (*paste_file == '-') usage();
459       if (*base_file == '-') usage();
460
461       s = argv[i++];
462       if (1 != sscanf (s, " %lf %c", &from_scale, &dummy)) usage();
463       if (from_scale <= 0 || from_scale > 100) usage();
464
465       s = argv[i++];
466       if (1 != sscanf (s, " %lf %c", &opacity, &dummy)) usage();
467       if (opacity <= 0 || opacity > 1) usage();
468
469       s = argv[i++]; if (1 != sscanf (s, " %d %c", &from_x, &dummy)) usage();
470       s = argv[i++]; if (1 != sscanf (s, " %d %c", &from_y, &dummy)) usage();
471       s = argv[i++]; if (1 != sscanf (s, " %d %c", &to_x, &dummy)) usage();
472       s = argv[i++]; if (1 != sscanf (s, " %d %c", &to_y, &dummy)) usage();
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
476       bevel_pct = 10; /* #### */
477
478       if (w < 0) usage();
479       if (h < 0) usage();
480
481       if (w == 0 || h == 0 || 
482           w > 10240 || h > 10240) {
483         fprintf (stderr, "%s: absurd size: %d x %d\n", progname, w, h);
484         exit (1);
485       }
486
487       paste (paste_file, base_file,
488              from_scale, opacity, bevel_pct,
489              from_x, from_y, to_x, to_y,
490              w, h);
491     }
492   else if (argc == 4 || argc == 5)
493     {
494       char *color = argv[i++];
495       s = argv[i++]; if (1 != sscanf (s, " %d %c", &w, &dummy)) usage();
496       s = argv[i++]; if (1 != sscanf (s, " %d %c", &h, &dummy)) usage();
497       paste_file = argv[i++];
498       if (*paste_file == '-') usage();
499
500       create (color, w, h, paste_file);
501     }
502   else
503     {
504       usage();
505     }
506
507   [pool release];
508
509   exit (0);
510 }