diff options
Diffstat (limited to 'hacks/webcollage-helper-cocoa.m')
-rw-r--r-- | hacks/webcollage-helper-cocoa.m | 510 |
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); +} |