/* webcollage-helper-cocoa --- scales and pastes one image into another * xscreensaver, Copyright (c) 2002-2018 Jamie Zawinski * * 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 #include #include #include #include #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); }