/* * Copyright (C) 2005 Apple Computer, Inc. All rights reserved. * Copyright (C) 2005 Ben La Monica . All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import #import #import #import #import /* prototypes */ int main(int argc, const char *argv[]); CGImageRef createImageFromStdin(int imageSize); void compareImages(CGImageRef actualBitmap, CGImageRef baselineImage); NSBitmapImageRep *getDifferenceBitmap(CGImageRef actualBitmap, CGImageRef baselineImage); float computePercentageDifferent(NSBitmapImageRep *diffBitmap); int main(int argc, const char *argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; char buffer[2048]; CGImageRef actualImage = nil; CGImageRef baselineImage = nil; NSAutoreleasePool *innerPool = [[NSAutoreleasePool alloc] init]; while (fgets(buffer, sizeof(buffer), stdin)) { // remove the CR char *newLineCharacter = strchr(buffer, '\n'); if (newLineCharacter) { *newLineCharacter = '\0'; } if (strncmp("Content-length: ", buffer, 16) == 0) { strtok(buffer, " "); int imageSize = strtol(strtok(NULL, " "), NULL, 10); if(imageSize > 0 && actualImage == nil) actualImage = createImageFromStdin(imageSize); else if (imageSize > 0 && baselineImage == nil) baselineImage = createImageFromStdin(imageSize); else fputs("error, image size must be specified.\n", stdout); } if (actualImage != nil && baselineImage != nil) { compareImages(actualImage, baselineImage); CGImageRelease(actualImage); CGImageRelease(baselineImage); actualImage = nil; baselineImage = nil; [innerPool release]; innerPool = [[NSAutoreleasePool alloc] init]; } fflush(stdout); } [innerPool release]; [pool release]; return 0; } CGImageRef createImageFromStdin(int bytesRemaining) { unsigned char buffer[2048]; NSMutableData *data = [[NSMutableData alloc] initWithCapacity:bytesRemaining]; int bytesRead = 0; while (bytesRemaining > 0) { bytesRead = (bytesRemaining > 2048 ? 2048 : bytesRemaining); fread(buffer, bytesRead, 1, stdin); [data appendBytes:buffer length:bytesRead]; bytesRemaining -= bytesRead; } CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((CFDataRef)data); CGImageRef image = CGImageCreateWithPNGDataProvider(dataProvider, NULL, NO, kCGRenderingIntentDefault); [data release]; CGDataProviderRelease(dataProvider); return image; } void compareImages(CGImageRef actualBitmap, CGImageRef baselineBitmap) { // prepare the difference blend to check for pixel variations NSBitmapImageRep *diffBitmap = getDifferenceBitmap(actualBitmap, baselineBitmap); float percentage = computePercentageDifferent(diffBitmap); // send message to let them know if an image was wrong if (percentage > 0.0) { // since the diff might actually show something, send it to stdout NSData *diffPNGData = [diffBitmap representationUsingType:NSPNGFileType properties:nil]; fprintf(stdout, "Content-length: %d\n", [diffPNGData length]); fwrite([diffPNGData bytes], [diffPNGData length], 1, stdout); fprintf(stdout, "diff: %01.2f%% failed\n", percentage); } else fprintf(stdout, "diff: %01.2f%% passed\n", percentage); } NSBitmapImageRep *getDifferenceBitmap(CGImageRef testBitmap, CGImageRef referenceBitmap) { // we must have both images to take diff if (testBitmap == nil || referenceBitmap == nil) return nil; NSBitmapImageRep *diffBitmap = [NSBitmapImageRep alloc]; [diffBitmap initWithBitmapDataPlanes:NULL pixelsWide:CGImageGetWidth(testBitmap) pixelsHigh:CGImageGetHeight(testBitmap) bitsPerSample:CGImageGetBitsPerComponent(testBitmap) samplesPerPixel:CGImageGetBitsPerPixel(testBitmap) / CGImageGetBitsPerComponent(testBitmap) hasAlpha:YES isPlanar:NO colorSpaceName:NSCalibratedRGBColorSpace bitmapFormat:0 bytesPerRow:CGImageGetBytesPerRow(testBitmap) bitsPerPixel:CGImageGetBitsPerPixel(testBitmap) ]; NSGraphicsContext *nsContext = [NSGraphicsContext graphicsContextWithBitmapImageRep:diffBitmap]; CGContextRef cgContext = [nsContext graphicsPort]; CGContextSetBlendMode(cgContext, kCGBlendModeNormal); CGContextDrawImage(cgContext, CGRectMake(0, 0, CGImageGetWidth(testBitmap), CGImageGetHeight(testBitmap)), testBitmap); CGContextSetBlendMode(cgContext, kCGBlendModeDifference); CGContextDrawImage(cgContext, CGRectMake(0, 0, CGImageGetWidth(referenceBitmap), CGImageGetHeight(referenceBitmap)), referenceBitmap); return [diffBitmap autorelease]; } /** * Counts the number of non-black pixels, and returns the percentage * of non-black pixels to total pixels in the image. */ float computePercentageDifferent(NSBitmapImageRep *diffBitmap) { // if diffBiatmap is nil, then there was an error, and it didn't match. if (diffBitmap == nil) return 100.0; unsigned bitmapFormat = [diffBitmap bitmapFormat]; assert(!(bitmapFormat & NSAlphaFirstBitmapFormat)); assert(!(bitmapFormat & NSFloatingPointSamplesBitmapFormat)); unsigned pixelsHigh = [diffBitmap pixelsHigh]; unsigned pixelsWide = [diffBitmap pixelsWide]; unsigned bytesPerRow = [diffBitmap bytesPerRow]; unsigned char *pixelRowData = [diffBitmap bitmapData]; unsigned differences = 0; // NOTE: This may not be safe when switching between ENDIAN types for (unsigned row = 0; row < pixelsHigh; row++) { for (unsigned col = 0; col < (pixelsWide * 4); col += 4) { if (*(pixelRowData + col) != 0 || *(pixelRowData + col + 1) != 0 || *(pixelRowData + col + 2) != 0) differences++; } pixelRowData += bytesPerRow; } float totalPixels = pixelsHigh * pixelsWide; return (differences * 100.f) / totalPixels; }