//
//  ImageFilter.m
//  ScaleAndPadImages
//
//  Created by Frank Mcpherson on 3/8/07.
//  Copyright 2007 Frank H. McPherson IV. All rights reserved.
//

#import "ImageFilter.h"


@implementation ImageFilter


// Set up for drawing the images off-screen by creating a bitmap of the target size
// and filling it with the specified background color
- (id)initWithTargetSize:(CGRect)rect backgroundColor:(NSColor *)color; {
	self = [super init];
	
	if (self) {
		
		targetRect = rect;
		
		// Create space to hold the image
		outBitmap = [[NSBitmapImageRep alloc]
			initWithBitmapDataPlanes:NULL pixelsWide:rect.size.width
						  pixelsHigh:rect.size.height
					   bitsPerSample:8 samplesPerPixel:4
							hasAlpha:YES isPlanar:NO 
					 colorSpaceName:NSDeviceRGBColorSpace
						 bytesPerRow:0 bitsPerPixel:0];
		
		// Save the current graphics state
		[NSGraphicsContext saveGraphicsState];
		
		// Grab an off-screen context backed by outBitmap
		NSGraphicsContext *nsContext = [NSGraphicsContext graphicsContextWithBitmapImageRep:outBitmap];
		
		// This should be set for higher quality interpolation. Time this versus both the fast way 
		// and versus the Core Image way
		// [nsGraphicsContext setImageInterpolation: NSImageInterpolationHigh];
		
		[NSGraphicsContext setCurrentContext:nsContext];
		ciContext = [nsContext CIContext];
		
		// Prepare a background color and crop to size since its extent is infinite
		CIFilter *bgColorFilter = [CIFilter filterWithName:@"CIConstantColorGenerator"];
		[bgColorFilter setDefaults];
		CIColor *ciColor = [[CIColor alloc] initWithColor:color];
		[bgColorFilter setValue:ciColor forKey:@"inputColor"];	
		[ciColor autorelease];
		
		CIFilter *cropFilter = [CIFilter filterWithName:@"CICrop"];
		[cropFilter setDefaults];
		[cropFilter setValue:[bgColorFilter valueForKey:@"outputImage"] forKey:@"inputImage"];
		[cropFilter setValue:[CIVector vectorWithX:0 Y:0 Z:rect.size.width W:rect.size.height] 
					  forKey:@"inputRectangle"];
		
		// Composite the transformed, scaled image over the square, colored background.
		// Retain this filter so we can re-use it in filterImage:
		compositeFilter = [CIFilter filterWithName:@"CISourceOverCompositing"];
		[compositeFilter setValue:[cropFilter valueForKey:@"outputImage"] forKey:@"inputBackgroundImage"];
		[compositeFilter retain];
	}
	return self;
}

-(void)dealloc {
	[NSGraphicsContext restoreGraphicsState];
	if (outBitmap) [outBitmap release];
	if (compositeFilter) [compositeFilter release];
	[super dealloc];
}


// Provides the same results as filterImage, but uses CG's affine transform to scale the image, instead of 
// using another CI filter
- (CIImage *)filterImage2:(CIImage *)inImage; {
	ScaleAndPadding to = [self scaleOfRect:[inImage extent] toSize:targetRect.size];
	CGAffineTransform transform = CGAffineTransformMakeTranslation(to.padding.x, to.padding.y);
	transform = CGAffineTransformScale(transform, to.scaleFactor, to.scaleFactor);

	[compositeFilter setValue:[inImage imageByApplyingTransform:transform]
					   forKey:@"inputImage"];
	return [compositeFilter valueForKey:@"outputImage"];
}

// Filter the image. Scale it to the targetRect's size, transform it to pad the short dimension
- (CIImage *)filterImage:(CIImage *)inImage; {
	
	CGRect inExtent = [inImage extent];
	// Figure out the target size, scale factor and padding for each dimension
	ScaleAndPadding to = [self scaleOfRect:inExtent toSize:targetRect.size];
	
	// Make a transform that translates the image for padding purposes
	CGAffineTransform transform = CGAffineTransformMakeTranslation(to.padding.x, to.padding.y);
	
	// Apply an Affine Clamp to the image 
	CIFilter *clamp = [CIFilter filterWithName:@"CIAffineClamp"];
	[clamp setValue:[NSAffineTransform transform]
			 forKey:@"inputTransform"];
	[clamp setValue:inImage forKey:@"inputImage"];
	
	// I tried scaling with the AffineTransform but wasn't as pleased with the 
	// results, even when I used [nsGraphicsContext setImageInterpolation: NSImageInterpolationHigh];
	//transform = CGAffineTransformScale(transform, to.scaleFactor, to.scaleFactor);
	
	// Put the option here to use fast, good, or great quality. That way they can use based on their system power.

	CIFilter *scaleFilter = [CIFilter filterWithName:@"CILanczosScaleTransform"];
	[scaleFilter setDefaults];
	[scaleFilter setValue:[clamp valueForKey:@"outputImage"] forKey:@"inputImage"];
	[scaleFilter setValue:[NSNumber numberWithFloat:to.scaleFactor] forKey:@"inputScale"];

	CIImage *image = [scaleFilter valueForKey:@"outputImage"];

	CIFilter *cropFilter = [CIFilter filterWithName:@"CICrop"];
	[cropFilter setValue:[CIVector vectorWithX:0 Y:0
											 Z:ceilf(inExtent.size.width*to.scaleFactor) 
											 W:ceilf(inExtent.size.height*to.scaleFactor)]
				  forKey:@"inputRectangle"];
	[cropFilter setValue:image forKey:@"inputImage"];
	
	[compositeFilter setValue:[[cropFilter valueForKey:@"outputImage"] imageByApplyingTransform:transform]
					   forKey:@"inputImage"];
	return [compositeFilter valueForKey:@"outputImage"];
}


// Use the  created in prepareForSavingImagesOfSize: withBackgroundColor: 
// to draw anImage and save the backing bitmap to outFile
- (void)saveImage:(CIImage *)outImage toFile:(NSString *)outFile {
	[ciContext drawImage:outImage inRect:targetRect fromRect:[outImage extent]];
	
	// Save the bitmap backing the context to outFile
	NSDictionary *outImageProps = [NSDictionary dictionaryWithObject:[NSDecimalNumber numberWithFloat:0.8] 
															  forKey:NSImageCompressionFactor];	
	[[outBitmap representationUsingType:NSJPEGFileType properties:outImageProps]
		writeToFile:outFile atomically:NO];
}


// I want to scale the image to a certain maximum dimension but keep the 
// current aspect ratio. I may also want to pad resulting image so it's square, 
// so I need to figure out the size of padding for the shorter dimension
- (ScaleAndPadding)scaleOfRect:(CGRect)rect toSize:(CGSize)size
{
	ScaleAndPadding result;
	if (rect.size.height > rect.size.width) {
		result.scaleFactor = size.height / rect.size.height;
		result.padding.y = 0;
		result.padding.x = (size.width - rect.size.width * result.scaleFactor) / 2;
	} else {
		result.scaleFactor = size.width / rect.size.width;
		result.padding.x = 0;
		result.padding.y = (size.height - rect.size.height * result.scaleFactor) / 2;
	}
	return result;
}

@end
