Rotate Texture by 90 degrees

From AwkwardTV
Jump to: navigation, search

<Google>WIKI</Google>

Because I needed a Vertical Progressbar, I had to get images of the stock Progress graphics rotate by 90 degrees. With some help from alan, this is the result.



Texture Cache

Since we don't want to have to do this stuff everytime our plugin loads, we'll use the Texture cache.

- (BRTexture*)loadImageAndRotate:(NSString*)path
{
  NSString* cacheName = [NSString stringWithFormat:@"%iROT_%@", 
                                                 90, 
                                                 [ [path lastPathComponent] 
                                                        stringByDeletingPathExtension] ]; 
  BRTexture* texture = [[self scene] cachedTextureForKey: cacheName];

The first line generates a cache name, by appending the filename ( [path lastPathComponent] ) without its extension (stringByDeletingPathExtension) to the string "90ROT_". The scene is then asked for a cached Texture with that name. If we do this the first time it will return nil, so we know we have to generate it.

if(texture == nil)
{
	NSURL * imageURL = [NSURL fileURLWithPath: path];
	CGImageRef image = CreateImageForURL( imageURL );
	CIImage* img = [CIImage imageWithCGImage:image];

The first step is to get a URL from the path and load an CGImageRef image by using the Backrow function CreateImageForURL. After that, an CIImage is created from that CGImageRef for using the actual transformation.

CIFilter

For the rotation I used the CIFilter class from the QuartzCore.Framework, so you'll have to include that into your project. CIFilters are created by name, so you have to know that. For the rotation I used the CIAffineTransformation Filter, which can scale / rotate and translate an Image.

CIFilter *transform = [CIFilter filterWithName:@"CIAffineTransform"];
[transform setValue:img forKey:@"inputImage"];
NSAffineTransform *affineTransform = [NSAffineTransform transform];
[affineTransform rotateByDegrees:90];
[transform setValue:affineTransform forKey:@"inputTransform"];
CIImage * result = [transform valueForKey:@"outputImage"];

The Parameter for the Filter are supplied via key-value pairs, the affinetransform filter takes an input image and the transformation itself, which is an NSAffineTransform. The transform itself consists of a mere 90 degrees rotation.

Back Translation

Because the rotation is not around 0,0 we have to reset the translation.

CGRect extent = [result extent];
transform = [CIFilter filterWithName:@"CIAffineTransform"];
affineTransform = [NSAffineTransform transform];
[affineTransform translateXBy:-extent.origin.x
			  yBy:-extent.origin.y];
[transform setValue:affineTransform forKey:@"inputTransform"];
[transform setValue:result forKey:@"inputImage"];
result = [transform valueForKey:@"outputImage"];

First, we take the current extents of the image, resupply these into another affineTransformation and transform the image again.

After these steps the origin of the image is back to 0,0 but because of the precision of float, the image is now slightly larger then we would expect. If for instance the original image was 28 by 15 pixels, we might now have a size of 28 by 16 pixels, which can be quite ugly if we depend on the size.

Cropping

Therefor we have to crop the image to the correct size which would be size.width=org.height and size.y=org.width

transform = [CIFilter filterWithName:@"CICrop"];
[transform setValue:[CIVector vectorWithX:0.0 
                                        Y:0.0 
                                        Z:orgImgSize.size.height 
                                        W:orgImgSize.size.width] forKey:@"inputRectangle"];
[transform setValue:result forKey:@"inputImage"];
result = [transform valueForKey:@"outputImage"];

( Please note: Without the Back Transformation this cropping would fail ! )

With the crop filter, we simply crop the image back to the size it should be. The only thing left now is to create a Texture out of it and clean up.

texture = [BRBitmapTexture textureWithCIImage:result context:[[self scene]resourceContext] mipmap:NO];
[[self scene] setCachedTexture:texture forKey: cacheName];
CFRelease( image );

In the second last line, we save the newly created texture into the texture cache so that subsequent calls will retrieve the cached version.

The Code

All in one:

- (BRTexture*)loadImageAndRotate:(NSString*)path
{
	NSString* cacheName = [NSString stringWithFormat:@"%iROT_%@", 90, [[path lastPathComponent] stringByDeletingPathExtension] ];
	BRTexture* texture = [[self scene] cachedTextureForKey: cacheName];
	
	if(texture == nil)
	{
		NSURL * imageURL = [NSURL fileURLWithPath: path];
		CGImageRef image = CreateImageForURL( imageURL );

		CIImage* img = [CIImage imageWithCGImage:image];

		CGRect orgImgSize = [img extent];

		CIFilter *transform = [CIFilter filterWithName:@"CIAffineTransform"];
		[transform setValue:img forKey:@"inputImage"];

		NSAffineTransform *affineTransform = [NSAffineTransform transform];
		[affineTransform rotateByDegrees:90];
		[transform setValue:affineTransform forKey:@"inputTransform"];
		CIImage * result = [transform valueForKey:@"outputImage"];
		
		CGRect extent = [result extent];
		
		transform = [CIFilter filterWithName:@"CIAffineTransform"];
		affineTransform = [NSAffineTransform transform];
		[affineTransform translateXBy:-extent.origin.x
								  yBy:-extent.origin.y];
		[transform setValue:affineTransform forKey:@"inputTransform"];
		[transform setValue:result forKey:@"inputImage"];
		result = [transform valueForKey:@"outputImage"];
		
		transform = [CIFilter filterWithName:@"CICrop"];
		[transform setValue:[CIVector vectorWithX:0.0 Y:0.0 Z:orgImgSize.size.height W:orgImgSize.size.width] forKey:@"inputRectangle"];
		[transform setValue:result forKey:@"inputImage"];
		result = [transform valueForKey:@"outputImage"];
		
		extent = [result extent];
		texture = [BRBitmapTexture textureWithCIImage:result context:[[self scene]resourceContext] mipmap:NO];
	
		[[self scene] setCachedTexture:texture forKey: cacheName];
		
		CFRelease( image );
	}
	return ( texture );
}