summaryrefslogtreecommitdiffstats
path: root/hacks/webcollage-helper-cocoa.m
diff options
context:
space:
mode:
Diffstat (limited to 'hacks/webcollage-helper-cocoa.m')
-rw-r--r--hacks/webcollage-helper-cocoa.m510
1 files changed, 510 insertions, 0 deletions
diff --git a/hacks/webcollage-helper-cocoa.m b/hacks/webcollage-helper-cocoa.m
new file mode 100644
index 0000000..fde1f0d
--- /dev/null
+++ b/hacks/webcollage-helper-cocoa.m
@@ -0,0 +1,510 @@
+/* webcollage-helper-cocoa --- scales and pastes one image into another
+ * xscreensaver, Copyright (c) 2002-2018 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ */
+
+/* This is the Cocoa implementation. See webcollage-helper.c for the
+ GDK + JPEGlib implementation.
+ */
+
+#import <Cocoa/Cocoa.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+
+#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4
+ typedef int NSInteger;
+ typedef unsigned int NSUInteger;
+#endif
+
+
+char *progname;
+static int verbose_p = 0;
+
+static void write_image (NSImage *img, const char *file);
+
+
+/* NSImage can't load PPMs by default...
+ */
+static NSImage *
+load_ppm_image (const char *file)
+{
+ FILE *in = fopen (file, "r");
+ if (! in) return 0;
+
+ char buf[255];
+
+ char *s = fgets (buf, sizeof(buf)-1, in); /* P6 */
+ if (!s || !!strcmp (s, "P6\n"))
+ return 0;
+
+ s = fgets (buf, sizeof(buf)-1, in); /* W H */
+ if (!s)
+ return 0;
+
+ int w = 0, h = 0, d = 0;
+ if (2 != sscanf (buf, " %d %d \n", &w, &h))
+ return 0;
+ if (w <= 0 || h <= 0)
+ return 0;
+
+ s = fgets (buf, sizeof(buf)-1, in); /* 255 */
+ if (!s)
+ return 0;
+
+ if (1 != sscanf (buf, " %d \n", &d))
+ return 0;
+ if (d != 255)
+ return 0;
+
+ int size = (w * (h+1) * 3);
+ unsigned char *bits = malloc (size);
+ if (!bits) return 0;
+
+ int n = read (fileno (in), (void *) bits, size); /* body */
+ if (n < 20) return 0;
+
+ fclose (in);
+
+ NSBitmapImageRep *rep =
+ [[NSBitmapImageRep alloc]
+ initWithBitmapDataPlanes: &bits
+ pixelsWide: w
+ pixelsHigh: h
+ bitsPerSample: 8
+ samplesPerPixel: 3
+ hasAlpha: NO
+ isPlanar: NO
+ colorSpaceName: NSDeviceRGBColorSpace
+ bitmapFormat: NSAlphaFirstBitmapFormat
+ bytesPerRow: w * 3
+ bitsPerPixel: 8 * 3];
+
+ NSImage *image = [[NSImage alloc] initWithSize: NSMakeSize (w, h)];
+ [image addRepresentation: rep];
+ [rep release];
+
+ // #### 'bits' is leaked... the NSImageRep doesn't free it when freed.
+
+ return image;
+}
+
+
+static NSImage *
+load_image (const char *file)
+{
+ NSImage *image = [[NSImage alloc]
+ initWithContentsOfFile:
+ [NSString stringWithCString: file
+ encoding: NSUTF8StringEncoding]];
+ if (! image)
+ image = load_ppm_image (file);
+
+ if (! image) {
+ fprintf (stderr, "%s: unable to load %s\n", progname, file);
+ exit (1);
+ }
+
+
+ // [NSImage size] defaults to the image size in points instead of pixels,
+ // so if an image file specified "pixels per inch" we can end up with
+ // absurdly sized images. Set it back to 1:1 pixel:point.
+ //
+ NSImageRep *rep = [image.representations objectAtIndex:0];
+ image.size = NSMakeSize (rep.pixelsWide, rep.pixelsHigh);
+
+ return image;
+}
+
+
+static void
+bevel_image (NSImage *img, int bevel_pct,
+ int x, int y, int w, int h, double scale)
+{
+ int small_size = (w > h ? h : w);
+
+ int bevel_size = small_size * (bevel_pct / 100.0);
+
+ bevel_size /= scale;
+
+ /* Use a proportionally larger bevel size for especially small images. */
+ if (bevel_size < 20 && small_size > 40) bevel_size = 20;
+ else if (bevel_size < 10 && small_size > 20) bevel_size = 10;
+ else if (bevel_size < 5) /* too small to bother bevelling */
+ return;
+
+
+ NSBitmapImageRep *rep =
+ [[NSBitmapImageRep alloc]
+ initWithBitmapDataPlanes: NULL
+ pixelsWide: w
+ pixelsHigh: h
+ bitsPerSample: 8
+ samplesPerPixel: 4
+ hasAlpha: YES
+ isPlanar: NO
+ colorSpaceName: NSDeviceRGBColorSpace
+ bitmapFormat: NSAlphaFirstBitmapFormat
+ bytesPerRow: 0
+ bitsPerPixel: 0];
+
+ NSInteger xx, yy;
+ double *ramp = (double *) malloc (sizeof(*ramp) * (bevel_size + 1));
+
+ if (!ramp)
+ {
+ fprintf (stderr, "%s: out of memory (%d)\n", progname, bevel_size);
+ exit (1);
+ }
+
+ for (xx = 0; xx <= bevel_size; xx++)
+ {
+# if 0 /* linear */
+ ramp[xx] = xx / (double) bevel_size;
+
+# else /* sinusoidal */
+ double p = (xx / (double) bevel_size);
+ double s = sin (p * M_PI / 2);
+ ramp[xx] = s;
+# endif
+ }
+
+ memset ([rep bitmapData], 0xFFFFFFFF,
+ [rep bytesPerRow] * h);
+
+ for (yy = 0; yy < h; yy++)
+ {
+ for (xx = 0; xx < w; xx++)
+ {
+ double rx, ry, r;
+
+ if (xx < bevel_size) rx = ramp[xx];
+ else if (xx >= w - bevel_size) rx = ramp[w - xx - 1];
+ else rx = 1;
+
+ if (yy < bevel_size) ry = ramp[yy];
+ else if (yy >= h - bevel_size) ry = ramp[h - yy - 1];
+ else ry = 1;
+
+ r = rx * ry;
+ if (r != 1)
+ {
+ NSUInteger p[4];
+ p[0] = 0xFF * r;
+ p[1] = p[2] = p[3] = 0xFF;
+ [rep setPixel:p atX:xx y:yy];
+ }
+ }
+ }
+
+ free (ramp);
+
+ NSImage *bevel_img = [[NSImage alloc]
+ initWithData: [rep TIFFRepresentation]];
+
+ [img lockFocus];
+ y = [img size].height - (y + h);
+ [bevel_img drawAtPoint: NSMakePoint (x, y)
+ fromRect: NSMakeRect (0, 0, w, h)
+ operation: NSCompositeDestinationIn /* Destination image
+ wherever both images are
+ opaque, transparent
+ elsewhere. */
+ fraction: 1.0];
+ [img unlockFocus];
+
+ [rep release];
+ [bevel_img release];
+
+ if (verbose_p)
+ fprintf (stderr, "%s: added %d%% bevel (%d px)\n", progname,
+ bevel_pct, bevel_size);
+}
+
+
+static void
+paste (const char *paste_file,
+ const char *base_file,
+ double from_scale,
+ double opacity, int bevel_pct,
+ int from_x, int from_y, int to_x, int to_y,
+ int w, int h)
+{
+ NSImage *paste_img = load_image (paste_file);
+ NSImage *base_img = load_image (base_file);
+
+ int paste_w = [paste_img size].width;
+ int paste_h = [paste_img size].height;
+
+ int base_w = [base_img size].width;
+ int base_h = [base_img size].height;
+
+ if (verbose_p)
+ {
+ fprintf (stderr, "%s: loaded %s: %dx%d\n",
+ progname, base_file, base_w, base_h);
+ fprintf (stderr, "%s: loaded %s: %dx%d\n",
+ progname, paste_file, paste_w, paste_h);
+ }
+
+ if (bevel_pct > 0 && paste_w > 5 && paste_h > 5)
+ bevel_image (paste_img, bevel_pct,
+ from_x, from_y, w, h,
+ from_scale);
+
+ int scaled_w = w * from_scale;
+ int scaled_h = h * from_scale;
+
+ from_y = paste_h - (from_y + h); // Cocoa flipped coordinate system
+ to_y = base_h - (to_y + scaled_h);
+
+ [base_img lockFocus];
+ [paste_img drawInRect: NSMakeRect (to_x, to_y, scaled_w, scaled_h)
+ fromRect: NSMakeRect (from_x, from_y, w, h)
+ operation: NSCompositeSourceOver
+ fraction: opacity];
+ [base_img unlockFocus];
+
+ if (verbose_p)
+ fprintf (stderr, "%s: pasted %dx%d (%dx%d) from %d,%d to %d,%d\n",
+ progname, w, h, scaled_w, scaled_h, from_x, from_y, to_x, to_y);
+
+ [paste_img release];
+ write_image (base_img, base_file);
+ [base_img release];
+}
+
+
+static NSColor *
+parse_color (const char *s)
+{
+ static const char hex[128] =
+ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0,
+ 0, 10,11,12,13,14,15,0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 10,11,12,13,14,15,0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+ unsigned char r=0, g=0, b=0;
+
+ if (!strcasecmp (s, "black")) ;
+ else if (!strcasecmp (s, "white")) r = g = b = 0xFF;
+ else if (!strcasecmp (s, "red")) r = 0xFF;
+ else if (!strcasecmp (s, "green")) g = 0xFF;
+ else if (!strcasecmp (s, "blue")) b = 0xFF;
+ else
+ {
+ if (*s != '#' || strlen(s) != 7)
+ {
+ fprintf (stderr, "%s: unparsable color: \"%s\"\n", progname, s);
+ exit (1);
+ }
+ s++;
+ r = (hex[(int) s[0]] << 4) | hex[(int) s[1]], s += 2;
+ g = (hex[(int) s[0]] << 4) | hex[(int) s[1]], s += 2;
+ b = (hex[(int) s[0]] << 4) | hex[(int) s[1]], s += 2;
+ }
+
+ return [NSColor colorWithRed: r / 255.0
+ green: g / 255.0
+ blue: b / 255.0
+ alpha: 1.0];
+}
+
+
+static void
+create (const char *color,
+ int w, int h,
+ const char *file)
+{
+ NSColor *c = parse_color (color);
+ NSImage *img = [[NSImage alloc] initWithSize:NSMakeSize(w, h)];
+ [img lockFocus];
+ [c drawSwatchInRect:NSMakeRect(0, 0, w, h)];
+ [img unlockFocus];
+ write_image (img, file);
+ [img release];
+}
+
+
+static void
+write_image (NSImage *img, const char *file)
+{
+ float jpeg_quality = .85;
+
+ // Load the NSImage's contents into an NSBitmapImageRep.
+
+#if 0
+ // If the local display is Retina, this doubles the size of the output JPEG.
+ NSBitmapImageRep *bit_rep = [NSBitmapImageRep
+ imageRepWithData:[img TIFFRepresentation]];
+#else
+ // Render the image into a rep using pixels instead of points.
+ NSBitmapImageRep *bit_rep = [[NSBitmapImageRep alloc]
+ initWithBitmapDataPlanes:NULL
+ pixelsWide:[img size].width
+ pixelsHigh:[img size].height
+ bitsPerSample:8
+ samplesPerPixel:4
+ hasAlpha:YES
+ isPlanar:NO
+ colorSpaceName:NSCalibratedRGBColorSpace
+ bytesPerRow:0
+ bitsPerPixel:0];
+ bit_rep.size = [img size];
+ [NSGraphicsContext saveGraphicsState];
+ [NSGraphicsContext setCurrentContext:
+ [NSGraphicsContext
+ graphicsContextWithBitmapImageRep:bit_rep]];
+ [img drawInRect:NSMakeRect(0, 0, [img size].width, [img size].height)
+ fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.0];
+ [NSGraphicsContext restoreGraphicsState];
+#endif
+
+ // Write the bitmapImageRep to a JPEG file.
+ if (bit_rep == nil)
+ {
+ fprintf (stderr, "%s: error converting image?\n", progname);
+ exit (1);
+ }
+
+ if (verbose_p)
+ fprintf (stderr, "%s: writing %s (q=%d%%) ", progname, file,
+ (int) (jpeg_quality * 100));
+
+ NSData *jpeg_data = [bit_rep representationUsingType:NSJPEGFileType
+ properties:@{ NSImageCompressionFactor:
+ [NSNumber numberWithFloat:
+ jpeg_quality] }];
+ [jpeg_data writeToFile:
+ [NSString stringWithCString:file
+ encoding:NSISOLatin1StringEncoding]
+ atomically:YES];
+
+ if (verbose_p)
+ {
+ struct stat st;
+ if (stat (file, &st))
+ {
+ char buf[255];
+ sprintf (buf, "%.100s: %.100s", progname, file);
+ perror (buf);
+ exit (1);
+ }
+ fprintf (stderr, " %luK\n", ((unsigned long) st.st_size + 1023) / 1024);
+ }
+}
+
+
+static void
+usage (void)
+{
+ fprintf (stderr,
+ "\nusage: %s [-v] paste-file base-file\n"
+ "\t from-scale opacity\n"
+ "\t from-x from-y to-x to-y w h\n"
+ "\n"
+ "\t Pastes paste-file into base-file.\n"
+ "\t base-file will be overwritten (with JPEG data.)\n"
+ "\t scaling is applied first: coordinates apply to scaled image.\n"
+ "\n"
+ "usage: %s [-v] color width height output-file\n"
+ "\t Creates a new image of a solid color.\n\n",
+ progname, progname);
+ exit (1);
+}
+
+
+int
+main (int argc, char **argv)
+{
+ int i;
+ char *paste_file, *base_file, *s, dummy;
+ double from_scale, opacity;
+ int from_x, from_y, to_x, to_y, w, h, bevel_pct;
+
+ i = 0;
+ progname = argv[i++];
+ s = strrchr (progname, '/');
+ if (s) progname = s+1;
+
+ // Much of Cocoa needs one of these to be available.
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ //Need an NSApp instance to make [NSImage TIFFRepresentation] work
+ NSApp = [NSApplication sharedApplication];
+ [NSApp autorelease];
+
+ if (!strcmp(argv[i], "-v"))
+ verbose_p++, i++;
+
+ if (argc == 11 || argc == 12)
+ {
+ paste_file = argv[i++];
+ base_file = argv[i++];
+
+ if (*paste_file == '-') usage();
+ if (*base_file == '-') usage();
+
+ s = argv[i++];
+ if (1 != sscanf (s, " %lf %c", &from_scale, &dummy)) usage();
+ if (from_scale <= 0 || from_scale > 100) usage();
+
+ s = argv[i++];
+ if (1 != sscanf (s, " %lf %c", &opacity, &dummy)) usage();
+ if (opacity <= 0 || opacity > 1) usage();
+
+ s = argv[i++]; if (1 != sscanf (s, " %d %c", &from_x, &dummy)) usage();
+ s = argv[i++]; if (1 != sscanf (s, " %d %c", &from_y, &dummy)) usage();
+ s = argv[i++]; if (1 != sscanf (s, " %d %c", &to_x, &dummy)) usage();
+ s = argv[i++]; if (1 != sscanf (s, " %d %c", &to_y, &dummy)) usage();
+ s = argv[i++]; if (1 != sscanf (s, " %d %c", &w, &dummy)) usage();
+ s = argv[i]; if (1 != sscanf (s, " %d %c", &h, &dummy)) usage();
+
+ bevel_pct = 10; /* #### */
+
+ if (w < 0) usage();
+ if (h < 0) usage();
+
+ if (w == 0 || h == 0 ||
+ w > 10240 || h > 10240) {
+ fprintf (stderr, "%s: absurd size: %d x %d\n", progname, w, h);
+ exit (1);
+ }
+
+ paste (paste_file, base_file,
+ from_scale, opacity, bevel_pct,
+ from_x, from_y, to_x, to_y,
+ w, h);
+ }
+ else if (argc == 4 || argc == 5)
+ {
+ char *color = argv[i++];
+ s = argv[i++]; if (1 != sscanf (s, " %d %c", &w, &dummy)) usage();
+ s = argv[i++]; if (1 != sscanf (s, " %d %c", &h, &dummy)) usage();
+ paste_file = argv[i++];
+ if (*paste_file == '-') usage();
+
+ create (color, w, h, paste_file);
+ }
+ else
+ {
+ usage();
+ }
+
+ [pool release];
+
+ exit (0);
+}