http://www.tienza.es/crux/src/www.jwz.org/xscreensaver/xscreensaver-5.05.tar.gz
[xscreensaver] / hacks / webcollage-helper-cocoa.m
1 /* webcollage-helper-cocoa --- scales and pastes one image into another
2  * xscreensaver, Copyright (c) 2002-2008 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 char *progname;
24 static int verbose_p = 0;
25
26 static void write_image (NSImage *img, const char *file);
27
28
29 /* NSImage can't load PPMs by default...
30  */
31 static NSImage *
32 load_ppm_image (const char *file)
33 {
34   FILE *in = fopen (file, "r");
35   if (! in) return 0;
36
37   char buf[255];
38
39   char *s = fgets (buf, sizeof(buf)-1, in);             /* P6 */
40   if (!s || !!strcmp (s, "P6\n")) 
41     return 0;
42
43   s = fgets (buf, sizeof(buf)-1, in);                   /* W H */
44   if (!s)
45     return 0;
46
47   int w = 0, h = 0, d = 0;
48   if (2 != sscanf (buf, " %d %d \n", &w, &h))
49     return 0;
50   if (w <= 0 || h <= 0)
51     return 0;
52
53   s = fgets (buf, sizeof(buf)-1, in);                   /* 255 */
54   if (!s)
55     return 0;
56
57   if (1 != sscanf (buf, " %d \n", &d))
58     return 0;
59   if (d != 255)
60     return 0;
61
62   int size = (w * (h+1) * 3);
63   unsigned char *bits = malloc (size);
64   if (!bits) return 0;
65
66   int n = read (fileno (in), (void *) bits, size);      /* body */
67   if (n < 20) return 0;
68
69   fclose (in);
70   
71   NSBitmapImageRep *rep =
72     [[NSBitmapImageRep alloc]
73       initWithBitmapDataPlanes: &bits
74                     pixelsWide: w
75                     pixelsHigh: h
76                  bitsPerSample: 8
77                samplesPerPixel: 3
78                       hasAlpha: NO
79                       isPlanar: NO
80                 colorSpaceName: NSDeviceRGBColorSpace
81                   bitmapFormat: NSAlphaFirstBitmapFormat
82                    bytesPerRow: w * 3
83                   bitsPerPixel: 8 * 3];
84
85   NSImage *image = [[NSImage alloc] initWithSize: NSMakeSize (w, h)];
86   [image addRepresentation: rep];
87   [rep release];
88
89   // #### 'bits' is leaked... the NSImageRep doesn't free it when freed.
90
91   return image;
92 }
93
94
95 static NSImage *
96 load_image (const char *file)
97 {
98   NSImage *image = [[NSImage alloc] 
99                      initWithContentsOfFile:
100                        [NSString stringWithCString: file
101                                           encoding: kCFStringEncodingUTF8]];
102   if (! image)
103     image = load_ppm_image (file);
104
105   if (! image) {
106     fprintf (stderr, "%s: unable to load %s\n", progname, file);
107     exit (1);
108   }
109
110   return image;
111 }
112
113
114 static void
115 bevel_image (NSImage *img, int bevel_pct,
116              int x, int y, int w, int h, double scale)
117 {
118   int small_size = (w > h ? h : w);
119
120   int bevel_size = small_size * (bevel_pct / 100.0);
121
122   bevel_size /= scale;
123
124   /* Use a proportionally larger bevel size for especially small images. */
125   if      (bevel_size < 20 && small_size > 40) bevel_size = 20;
126   else if (bevel_size < 10 && small_size > 20) bevel_size = 10;
127   else if (bevel_size < 5)    /* too small to bother bevelling */
128     return;
129
130
131   NSBitmapImageRep *rep =
132     [[NSBitmapImageRep alloc]
133       initWithBitmapDataPlanes: NULL
134                     pixelsWide: w
135                     pixelsHigh: h
136                  bitsPerSample: 8
137                samplesPerPixel: 4
138                       hasAlpha: YES
139                       isPlanar: NO
140                 colorSpaceName: NSDeviceRGBColorSpace
141                   bitmapFormat: NSAlphaFirstBitmapFormat
142                    bytesPerRow: 0
143                   bitsPerPixel: 0];
144
145   int xx, yy;
146   double *ramp = (double *) malloc (sizeof(*ramp) * (bevel_size + 1));
147
148   if (!ramp)
149     {
150       fprintf (stderr, "%s: out of memory (%d)\n", progname, bevel_size);
151       exit (1);
152     }
153
154   for (xx = 0; xx <= bevel_size; xx++)
155     {
156 # if 0  /* linear */
157       ramp[xx] = xx / (double) bevel_size;
158
159 # else /* sinusoidal */
160       double p = (xx / (double) bevel_size);
161       double s = sin (p * M_PI / 2);
162       ramp[xx] = s;
163 # endif
164     }
165
166   memset ([rep bitmapData], 0xFFFFFFFF,
167           [rep bytesPerRow] * h);
168
169   for (yy = 0; yy < h; yy++)
170     {
171       for (xx = 0; xx < w; xx++)
172         {
173           double rx, ry, r;
174
175           if (xx < bevel_size)           rx = ramp[xx];
176           else if (xx >= w - bevel_size) rx = ramp[w - xx - 1];
177           else rx = 1;
178
179           if (yy < bevel_size)           ry = ramp[yy];
180           else if (yy >= h - bevel_size) ry = ramp[h - yy - 1];
181           else ry = 1;
182
183           r = rx * ry;
184           if (r != 1)
185             {
186               unsigned int p[4];
187               p[0] = 0xFF * r;
188               p[1] = p[2] = p[3] = 0xFF;
189               [rep setPixel:p atX:xx y:yy];
190             }
191         }
192     }
193
194   free (ramp);
195
196   NSImage *bevel_img = [[NSImage alloc]
197                          initWithData: [rep TIFFRepresentation]];
198
199   [img lockFocus];
200   y = [img size].height - (y + h);
201   [bevel_img drawAtPoint: NSMakePoint (x, y)
202                 fromRect: NSMakeRect (0, 0, w, h)
203                operation: NSCompositeDestinationIn /* Destination image
204                                                       wherever both images are
205                                                       opaque, transparent
206                                                       elsewhere. */
207                 fraction: 1.0];
208   [img unlockFocus];
209
210   [bevel_img release];
211
212   if (verbose_p)
213     fprintf (stderr, "%s: added %d%% bevel (%d px)\n", progname,
214              bevel_pct, bevel_size);
215 }
216
217
218 static void
219 paste (const char *paste_file,
220        const char *base_file,
221        double from_scale,
222        double opacity, int bevel_pct,
223        int from_x, int from_y, int to_x, int to_y,
224        int w, int h)
225 {
226   NSImage *paste_img = load_image (paste_file);
227   NSImage *base_img  = load_image (base_file);
228
229   int paste_w = [paste_img size].width;
230   int paste_h = [paste_img size].height;
231
232   int base_w  = [base_img size].width;
233   int base_h  = [base_img size].height;
234
235   if (verbose_p)
236     {
237       fprintf (stderr, "%s: loaded %s: %dx%d\n",
238                progname, base_file, base_w, base_h);
239       fprintf (stderr, "%s: loaded %s: %dx%d\n",
240                progname, paste_file, paste_w, paste_h);
241     }
242
243   if (bevel_pct > 0 && paste_w > 5 && paste_h > 5)
244     bevel_image (paste_img, bevel_pct,
245                  from_x, from_y, w, h, 
246                  from_scale);
247
248   int scaled_w = w * from_scale;
249   int scaled_h = h * from_scale;
250
251   from_y = paste_h - (from_y + h);  // Cocoa flipped coordinate system
252   to_y   = base_h  - (to_y + scaled_h);
253
254   [base_img lockFocus];
255   [paste_img drawInRect: NSMakeRect (to_x, to_y, scaled_w, scaled_h)
256                fromRect: NSMakeRect (from_x, from_y, w, h)
257               operation: NSCompositeSourceOver
258                fraction: opacity];
259   [base_img unlockFocus];
260
261   if (verbose_p)
262     fprintf (stderr, "%s: pasted %dx%d (%dx%d) from %d,%d to %d,%d\n",
263              progname, w, h, scaled_w, scaled_h, from_x, from_y, to_x, to_y);
264
265   [paste_img release];
266   write_image (base_img, base_file);
267   [base_img release];
268 }
269
270
271 static void
272 write_image (NSImage *img, const char *file)
273 {
274   float jpeg_quality = .85;
275
276   // Load the NSImage's contents into an NSBitmapImageRep:
277   NSBitmapImageRep *bit_rep = [NSBitmapImageRep
278                                 imageRepWithData:[img TIFFRepresentation]];
279
280   // Write the bitmapImageRep to a JPEG file.
281   if (bit_rep == nil)
282     {
283       fprintf (stderr, "%s: error converting image?\n", progname);
284       exit (1);
285     }
286
287   if (verbose_p)
288     fprintf (stderr, "%s: writing %s (q=%d%%) ", progname, file, 
289              (int) (jpeg_quality * 100));
290
291   NSDictionary *props = [NSDictionary
292                           dictionaryWithObject:
293                             [NSNumber numberWithFloat:jpeg_quality]
294                           forKey:NSImageCompressionFactor];
295   NSData *jpeg_data = [bit_rep representationUsingType:NSJPEGFileType
296                                properties:props];
297
298   [jpeg_data writeToFile:
299                [NSString stringWithCString:file]
300              atomically:YES];
301
302   if (verbose_p)
303     {
304       struct stat st;
305       if (stat (file, &st))
306         {
307           char buf[255];
308           sprintf (buf, "%.100s: %.100s", progname, file);
309           perror (buf);
310           exit (1);
311         }
312       fprintf (stderr, " %luK\n", ((unsigned long) st.st_size + 1023) / 1024);
313     }
314 }
315
316
317 static void
318 usage (void)
319 {
320   fprintf (stderr, "usage: %s [-v] paste-file base-file\n"
321            "\t from-scale opacity\n"
322            "\t from-x from-y to-x to-y w h\n"
323            "\n"
324            "\t Pastes paste-file into base-file.\n"
325            "\t base-file will be overwritten (with JPEG data.)\n"
326            "\t scaling is applied first: coordinates apply to scaled image.\n",
327            progname);
328   exit (1);
329 }
330
331
332 int
333 main (int argc, char **argv)
334 {
335   int i;
336   char *paste_file, *base_file, *s, dummy;
337   double from_scale, opacity;
338   int from_x, from_y, to_x, to_y, w, h, bevel_pct;
339
340   i = 0;
341   progname = argv[i++];
342   s = strrchr (progname, '/');
343   if (s) progname = s+1;
344
345   if (argc != 11 && argc != 12) usage();
346
347   if (!strcmp(argv[i], "-v"))
348     verbose_p++, i++;
349
350   paste_file = argv[i++];
351   base_file = argv[i++];
352
353   if (*paste_file == '-') usage();
354   if (*base_file == '-') usage();
355
356   s = argv[i++];
357   if (1 != sscanf (s, " %lf %c", &from_scale, &dummy)) usage();
358   if (from_scale <= 0 || from_scale > 100) usage();
359
360   s = argv[i++];
361   if (1 != sscanf (s, " %lf %c", &opacity, &dummy)) usage();
362   if (opacity <= 0 || opacity > 1) usage();
363
364   s = argv[i++]; if (1 != sscanf (s, " %d %c", &from_x, &dummy)) usage();
365   s = argv[i++]; if (1 != sscanf (s, " %d %c", &from_y, &dummy)) usage();
366   s = argv[i++]; if (1 != sscanf (s, " %d %c", &to_x, &dummy)) usage();
367   s = argv[i++]; if (1 != sscanf (s, " %d %c", &to_y, &dummy)) usage();
368   s = argv[i++]; if (1 != sscanf (s, " %d %c", &w, &dummy)) usage();
369   s = argv[i++]; if (1 != sscanf (s, " %d %c", &h, &dummy)) usage();
370
371   bevel_pct = 10; /* #### */
372
373   if (w < 0) usage();
374   if (h < 0) usage();
375
376
377   // Much of Cocoa needs one of these to be available.
378   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
379
380   //Need an NSApp instance to make [NSImage TIFFRepresentation] work
381   NSApp = [NSApplication sharedApplication];
382   [NSApp autorelease];
383
384   paste (paste_file, base_file,
385          from_scale, opacity, bevel_pct,
386          from_x, from_y, to_x, to_y,
387          w, h);
388
389   [pool release];
390
391   exit (0);
392 }