From http://www.jwz.org/xscreensaver/xscreensaver-5.31.tar.gz
[xscreensaver] / hacks / webcollage-helper-cocoa.m
1 /* webcollage-helper-cocoa --- scales and pastes one image into another
2  * xscreensaver, Copyright (c) 2002-2014 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 firstObject];
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 void
288 write_image (NSImage *img, const char *file)
289 {
290   float jpeg_quality = .85;
291
292   // Load the NSImage's contents into an NSBitmapImageRep:
293   NSBitmapImageRep *bit_rep = [NSBitmapImageRep
294                                 imageRepWithData:[img TIFFRepresentation]];
295
296   // Write the bitmapImageRep to a JPEG file.
297   if (bit_rep == nil)
298     {
299       fprintf (stderr, "%s: error converting image?\n", progname);
300       exit (1);
301     }
302
303   if (verbose_p)
304     fprintf (stderr, "%s: writing %s (q=%d%%) ", progname, file, 
305              (int) (jpeg_quality * 100));
306
307   NSDictionary *props = [NSDictionary
308                           dictionaryWithObject:
309                             [NSNumber numberWithFloat:jpeg_quality]
310                           forKey:NSImageCompressionFactor];
311   NSData *jpeg_data = [bit_rep representationUsingType:NSJPEGFileType
312                                properties:props];
313
314   [jpeg_data writeToFile:
315                [NSString stringWithCString:file
316                                   encoding:NSISOLatin1StringEncoding]
317              atomically:YES];
318
319   if (verbose_p)
320     {
321       struct stat st;
322       if (stat (file, &st))
323         {
324           char buf[255];
325           sprintf (buf, "%.100s: %.100s", progname, file);
326           perror (buf);
327           exit (1);
328         }
329       fprintf (stderr, " %luK\n", ((unsigned long) st.st_size + 1023) / 1024);
330     }
331 }
332
333
334 static void
335 usage (void)
336 {
337   fprintf (stderr, "usage: %s [-v] paste-file base-file\n"
338            "\t from-scale opacity\n"
339            "\t from-x from-y to-x to-y w h\n"
340            "\n"
341            "\t Pastes paste-file into base-file.\n"
342            "\t base-file will be overwritten (with JPEG data.)\n"
343            "\t scaling is applied first: coordinates apply to scaled image.\n",
344            progname);
345   exit (1);
346 }
347
348
349 int
350 main (int argc, char **argv)
351 {
352   int i;
353   char *paste_file, *base_file, *s, dummy;
354   double from_scale, opacity;
355   int from_x, from_y, to_x, to_y, w, h, bevel_pct;
356
357   i = 0;
358   progname = argv[i++];
359   s = strrchr (progname, '/');
360   if (s) progname = s+1;
361
362   if (argc != 11 && argc != 12) usage();
363
364   if (!strcmp(argv[i], "-v"))
365     verbose_p++, i++;
366
367   paste_file = argv[i++];
368   base_file = argv[i++];
369
370   if (*paste_file == '-') usage();
371   if (*base_file == '-') usage();
372
373   s = argv[i++];
374   if (1 != sscanf (s, " %lf %c", &from_scale, &dummy)) usage();
375   if (from_scale <= 0 || from_scale > 100) usage();
376
377   s = argv[i++];
378   if (1 != sscanf (s, " %lf %c", &opacity, &dummy)) usage();
379   if (opacity <= 0 || opacity > 1) usage();
380
381   s = argv[i++]; if (1 != sscanf (s, " %d %c", &from_x, &dummy)) usage();
382   s = argv[i++]; if (1 != sscanf (s, " %d %c", &from_y, &dummy)) usage();
383   s = argv[i++]; if (1 != sscanf (s, " %d %c", &to_x, &dummy)) usage();
384   s = argv[i++]; if (1 != sscanf (s, " %d %c", &to_y, &dummy)) usage();
385   s = argv[i++]; if (1 != sscanf (s, " %d %c", &w, &dummy)) usage();
386   s = argv[i];   if (1 != sscanf (s, " %d %c", &h, &dummy)) usage();
387
388   bevel_pct = 10; /* #### */
389
390   if (w < 0) usage();
391   if (h < 0) usage();
392
393   if (w == 0 || h == 0 || 
394       w > 10240 || h > 10240) {
395     fprintf (stderr, "%s: absurd size: %d x %d\n", progname, w, h);
396     exit (1);
397   }
398
399
400   // Much of Cocoa needs one of these to be available.
401   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
402
403   //Need an NSApp instance to make [NSImage TIFFRepresentation] work
404   NSApp = [NSApplication sharedApplication];
405   [NSApp autorelease];
406
407   paste (paste_file, base_file,
408          from_scale, opacity, bevel_pct,
409          from_x, from_y, to_x, to_y,
410          w, h);
411
412   [pool release];
413
414   exit (0);
415 }