blob: b3bb6808af872b32dd27c3c24bb52f856a9d1fa8 [file] [log] [blame]
/*
* Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
* Copyright (C) 2005 Ben La Monica <ben.lamonica@gmail.com>. 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 <Foundation/Foundation.h>
#import <QuartzCore/CoreImage.h>
#import <AppKit/NSBitmapImageRep.h>
#import <AppKit/NSGraphicsContext.h>
#import <AppKit/NSCIImageRep.h>
/* 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;
}