Affine Transforms + Demo App - ios

I have a demo app here https://github.com/rdetert/image-transform-test
After importing an image, you can pinch, zoom, rotate the image. What I want to do is save out a 640x480 image (landscape mode) that looks identical to the live preview. So if there are 100px bars of empty space on the sides, I need the same empty bars in the final output (scaled appropriately).
This is proving to be more difficult than I thought it would be. I can't quite get it to come out right after days of working on it.
The magic method that generates the final image is called -(void)generateFinalImage
Good luck! ;)
EDIT
The green rectangle represents the actual area the imported image can be pinched, zoomed and rotated. The resolution on the iPhone 4S is 852x640, for example.
The blue rectangle is just a live preview for debugging and it's aspect ratio is the same as 640x480. The live preview could get very slow due to Core Image being very slow.
What I want to do is convert whatever is in the green rectangle to a 640x480 image. Notice the 852x640 is a slightly different aspect ratio than 640x480 too, but that isn't a huge problem.

Is your goal is to obtain just the exact copy of what you are editing, but with the size of the original image?
I guess it could be obtained by something like this:
- (UIImage *)padImage:(UIImage *)img to:(CGSize)size
{
if (size.width < img.size.width && size.height < img.size.height) return img;
size.width = MAX(size.width, img.size.width);
size.height = MAX(size.height, img.size.height);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, size.width, size.height, 8, 0, colorSpace, kCGImageAlphaPremultipliedLast);
CGRect centeredRect = CGRectMake((size.width - img.size.width)/2.0, (size.height - img.size.height)/2.0, img.size.width, img.size.height);
CGContextDrawImage(context, centeredRect, [img CGImage]);
CGImageRef imageRef = CGBitmapContextCreateImage(context);
CGColorSpaceRelease(colorSpace);
CGContextRelease(context);
UIImage *paddedImage = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
return paddedImage;
}
// final image size must be 640x480
- (void)generateFinalImage
{
float rotatableCanvasWidth = 495.0;
float rotatableCanvasHeight = 320.0;
UIImage *tmp = self.importedRawImage;
CGSize size = self.importedRawImage.size;
NSLog(NSStringFromCGSize(size));
tmp = [self padImage:tmp to:CGSizeMake(rotatableCanvasWidth, rotatableCanvasHeight)];
CIImage *ciImage = [[CIImage alloc] initWithImage:[tmp imageWithTransform:self.importedImageView.transform]];
CGPoint center = CGPointMake(size.width / 2.0, size.height / 2.0);
CIContext *context = [CIContext contextWithOptions:nil];
CGRect r = ciImage.extent;
r.origin.x = (r.size.width - rotatableCanvasHeight) / 2.0;
r.origin.y = (r.size.height - rotatableCanvasWidth) / 2.0;
r.size.width = rotatableCanvasHeight;
r.size.height = rotatableCanvasWidth;
self.finalImage = [UIImage imageWithCGImage:[context createCGImage:ciImage fromRect:r] scale:1.0 orientation:UIImageOrientationUp];
self.finalImage = [self.finalImage resizedImage:CGSizeMake(100.0f, 134.0f) interpolationQuality:kCGInterpolationHigh];
self.previewImageView.image = self.finalImage;
}

Related

How to get a masked UIImage without bad resolution?

I have an iOS application. I take a picture from my camera and I save this then crop this with a mask. The first image from the camera is saved correctly, but when I apply the mask it is saved with a low resolution and a stretched image.
I'm using this Objective-C code into my application to apply the mask.
- (UIImage*) maskImage:(UIImage *)image withMask:(UIImage *)mask_Image {
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
//UIImage *maskImage = maskImage1;
CGImageRef maskImageRef = [mask_Image CGImage];
// create a bitmap graphics context the size of the image
CGContextRef mainViewContentContext = CGBitmapContextCreate (NULL, mask_Image.size.width, mask_Image.size.height, 8, 0, colorSpace, kCGImageAlphaPremultipliedLast);
if (mainViewContentContext==NULL)
return NULL;
CGFloat widthratio = 0;
CGFloat heightratio = 0;
widthratio = mask_Image.size.width / image.size.width;
heightratio = mask_Image.size.height / image.size.height;
CGRect rect1 = {{0, 0}, {mask_Image.size.width, mask_Image.size.height}};
CGRect rect2 = {{-((image.size.width*widthratio)-mask_Image.size.width)/2 , -((image.size.height*heightratio)-mask_Image.size.height)/2}, {image.size.width*widthratio, image.size.height*heightratio}};
CGContextClipToMask(mainViewContentContext, rect1, maskImageRef);
CGContextDrawImage(mainViewContentContext, rect2, image.CGImage);
// Create CGImageRef of the main view bitmap content, and then
// release that bitmap context
CGImageRef newImage = CGBitmapContextCreateImage(mainViewContentContext);
CGContextRelease(mainViewContentContext);
UIImage *theImage = [UIImage imageWithCGImage:newImage];
CGImageRelease(newImage);
// return the image
NSData* imageData = UIImagePNGRepresentation(theImage); // get png representation
UIImage* pngImage = [UIImage imageWithData:imageData];
UIImageWriteToSavedPhotosAlbum(pngImage, nil, nil, nil);
return theImage;
}
I want get this correctly like:
I take my picture from the camera:
I apply my mask image to the camera image in the position that I wanted:
And I get my cropped image masked:
How can I get the correct masked image?

Why when I add add a UIImage on top of another UIImage for a new image does the added image shrink?

I'm trying to add a video player icon on top of a thumbnail of a video.
I get the image from the YouTube API, then crop it to be square, then resize it to be the proper size. I then add my player icon image on top of it.
The problem lies in the fact that the player icon is much smaller than it should be on the thumbnail (it's 28x28pt when on screen it's much smaller). See in the below image where I added it to the cell to show the size it should be, versus the thumbnail size:
I crop it to a square with this method:
/**
* Given a UIImage, return it with a square aspect ratio (via cropping, not smushing).
*/
- (UIImage *)createSquareVersionOfImage:(UIImage *)image {
CGFloat originalWidth = image.size.width;
CGFloat originalHeight = image.size.height;
float smallestDimension = fminf(originalWidth, originalHeight);
// Determine the offset needed to crop the center of the image out.
CGFloat xOffsetToBeCentered = (originalWidth - smallestDimension) / 2;
CGFloat yOffsetToBeCentered = (originalHeight - smallestDimension) / 2;
// Create the square, making sure the position and dimensions are set appropriately for retina displays.
CGRect square = CGRectMake(xOffsetToBeCentered * image.scale, yOffsetToBeCentered * image.scale, smallestDimension * image.scale, smallestDimension *image.scale);
CGImageRef squareImageRef = CGImageCreateWithImageInRect([image CGImage], square);
UIImage *squareImage = [UIImage imageWithCGImage:squareImageRef scale:image.scale orientation:image.imageOrientation];
CGImageRelease(squareImageRef);
return squareImage;
}
Resize it with this method:
/**
* Resize the given UIImage to a new size and return the newly resized image.
*/
- (UIImage *)resizeImage:(UIImage *)image toSize:(CGSize)newSize {
UIGraphicsBeginImageContextWithOptions(newSize, NO, 0);
[image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
And add it on top of the other image with this method:
/**
* Adds a UIImage on top of another UIImage and returns the result. The top image is centered.
*/
- (UIImage *)addImage:(UIImage *)additionalImage toImage:(UIImage *)backgroundImage {
UIGraphicsBeginImageContext(backgroundImage.size);
[backgroundImage drawInRect:CGRectMake(0, 0, backgroundImage.size.width, backgroundImage.size.height)];
[additionalImage drawInRect:CGRectMake((backgroundImage.size.width - additionalImage.size.width) / 2, (backgroundImage.size.height - additionalImage.size.height) / 2, additionalImage.size.width, additionalImage.size.height)];
UIImage *resultingImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return resultingImage;
}
And this is how it is implemented:
UIImage *squareThumbnail = [self resizeImage:[self createSquareVersionOfImage:responseObject] toSize:CGSizeMake(110.0, 110.0)];
UIImage *playerIcon = [UIImage imageNamed:#"video-thumbnail-overlay"];
UIImage *squareThumbnailWithPlayerIcon = [self addImage:playerIcon toImage:squareThumbnail];
But in the end, the icon is always too small. The sizing things confuse me when working with images, as I'm used to it figuring out retina screen related things automatically, and for example in the above code block, I'm not sure why I set it to 110.0, 110.0 as it's a 55x55 UIImageView and I thought it scales automatically (but if I put it to 55 it's stretched terribly).
The reason you have to put 110 in your resizeImage call is because you are creating a CGGraphics context with a scale of 1.0. The graphics context for views in a view hierarchy on retina displays have a scale of 2.0 (provided you did nothing to scale anything else).
I believe that new UIImage that you create is now a "normal" image (Sorry I can't remember the technical term). It is not an #2x image. So its size that you will get when you ask for size will not scale for #2x.
Note this answer:
UIGraphicsGetImageFromCurrentImageContext retina resolutions?
I haven't tested this, but it should work. If it doesn't it should at least be more straightforward to debug.
//images should be passed in with their original scales
-(UIImage*)compositedImageWithSize:(CGSize)newSize bg:(UIImage*)backgroundImage fgImage:(UIImage*)foregroundImage{
//match the scale of screen.
CGFloat scale = [[UIScreen mainScreen] scale];
UIGraphicsBeginImageContextWithOptions(newSize, NO, scale);
//instead of resizing the image ahead of time, we just draw it into the context at the appropriate size. The context will clip the image.
CGRect aspectFillRect = CGRectZero;
if(newSize.width/newSize.height > backgroundImage.size.width/backgroundImage.size.height){
aspectFillRect.y = 0;
aspectFillRect.height = newSize.height;
CGFloat scaledWidth = (newSize.height / backgroundImage.size.height) * newSize.width;
aspectFillRect.x = (newSize.width - scaledWidth)/2.0;
aspectFillRect.width = scaledWidth;
}else{
aspectFillRect.x = 0;
aspectFillRect.width = newSize.width;
CGFloat scaledHeight = (newSize.width / backgroundImage.size.width) * newSize.height;
aspectFillRect.y = (newSize.height - scaledHeight)/2.0;
aspectFillRect.height = scaledHeight;
}
[backgroundImage drawInRect:aspectFillRect];
//pass in the 2x image for the fg image so it provides a better resolution
[foregroundImage drawInRect:CGRectMake((newSize.width - additionalImage.size.width) / 2, (newSize.height - additionalImage.size.height) / 2, additionalImage.size.width, additionalImage.size.height)];
UIImage *resultingImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return resultingImage;
}
You would skip all those methods you were calling before and do:
UIImage *playerIcon = [UIImage imageNamed:#"video-thumbnail-overlay"];
//pass in the non-retina scale of the image
UIImage *result = [self compositedImageWithSize:CGSizeMake(55.0, 55.0)
bg:responseObject
fg:playerIcon];
Hope this helps!

Rotating a UIImageView by angles

I want to rotate a UIImageView with the press of a button. I am using this code:
#import "UIImage-Extensions.h"
UIImage *rotatedImage = [SplashItGroupPicker.image imageRotatedByDegrees:360/arrayCount];
SplashItGroupPicker.image = rotatedImage;
and in "UIImage-Extensions.h" i have:
#interface UIImage (CS_Extensions)
- (UIImage *)imageRotatedByRadians:(CGFloat)radians;
- (UIImage *)imageRotatedByDegrees:(CGFloat)degrees;
and in the .m:
- (UIImage *)imageRotatedByRadians:(CGFloat)radians
{
return [self imageRotatedByDegrees:RadiansToDegrees(radians)];
}
- (UIImage *)imageRotatedByDegrees:(CGFloat)degrees
{
// calculate the size of the rotated view's containing box for our drawing space
UIView *rotatedViewBox = [[UIView alloc] initWithFrame:CGRectMake(0,0,self.size.width, self.size.height)];
CGAffineTransform t = CGAffineTransformMakeRotation(DegreesToRadians(degrees));
rotatedViewBox.transform = t;
CGSize rotatedSize = rotatedViewBox.frame.size;
// Create the bitmap context
UIGraphicsBeginImageContext(rotatedSize);
CGContextRef bitmap = UIGraphicsGetCurrentContext();
// Move the origin to the middle of the image so we will rotate and scale around the center.
CGContextTranslateCTM(bitmap, rotatedSize.width/2, rotatedSize.height/2);
// // Rotate the image context
CGContextRotateCTM(bitmap, DegreesToRadians(degrees));
// Now, draw the rotated/scaled image into the context
CGContextScaleCTM(bitmap, 1.0, -1.0);
CGContextDrawImage(bitmap, CGRectMake(-self.size.width / 2, -self.size.height / 2, self.size.width, self.size.height), [self CGImage]);
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
...the rotation works fine, but when the image is rotated, the image also gets smaller and I can't understand why. Any ideas?
Here's a guess, is this only happening on Retina screens? Whenever saving a UIImage from a graphics context, you need to make sure the scale is correct. This is how I have done it:
CGFloat scale = [[UIScreen mainScreen] scale];
CGSize size = CGSizeMake(rotatedViewBox.frame.size.width * scale, rotatedViewBox.frame.size.height * scale);
UIGraphicsBeginImageContext( size );
/* Do your image manipulations here */
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// overwrite the image with one that has the correct scale set
newImage = [UIImage imageWithCGImage:newImage.CGImage scale:scale orientation:newImage.imageOrientation];
Using Kekoa's snippet (thanks a ton), I was able to get a working rotateImage function that works on retina devices based on Apples rotateImage code:
- (UIImage *)imageRotatedByDegrees:(CGFloat)degrees {
CGFloat radians = DegreesToRadians(degrees);
UIView *rotatedViewBox = [[UIView alloc] initWithFrame:CGRectMake(0,0, self.size.width, self.size.height)];
CGAffineTransform t = CGAffineTransformMakeRotation(radians);
rotatedViewBox.transform = t;
CGSize rotatedSize = rotatedViewBox.frame.size;
UIGraphicsBeginImageContextWithOptions(rotatedSize, NO, [[UIScreen mainScreen] scale]);
CGContextRef bitmap = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(bitmap, rotatedSize.width / 2, rotatedSize.height / 2);
CGContextRotateCTM(bitmap, radians);
CGContextScaleCTM(bitmap, 1.0, -1.0);
CGContextDrawImage(bitmap, CGRectMake(-self.size.width / 2, -self.size.height / 2 , self.size.width, self.size.height), self.CGImage );
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
There is no need to UIGraphicsGetImageFromCurrentImageContext. its a way to get screenshot of desired portion of mainscreen. so the result image quality affected. the only way to get Rotated Image is...
first rotate your image view by(if you want to show rotated view)
[mainImgV setTransform:CGAffineTransformRotate(mainImgV.transform, M_PI)];
it will rotate your imageview.
now you will take this rotated image by these lines.(you can call these lines without rotate imgaeview also.)
CGImageRef cgRef = mainImgV.image.CGImage;
newImage = [[UIImage alloc] initWithCGImage:cgRef scale:1.0 orientation:UIImageOrientationDown];
there is no need to save or take screenshot or compromise bad quality image. just enjoy coding.

iOS: shrink image and makes height : width = 1 : 1

Here is a camera demo from iOS developer center, and the function used to shrink image is on below.
The problem what I met is image being stretched while its width < height.
However, I've need to scale and shrink the image into a square(width : height = 1 : 1).
Do anybody have solution on this?
Thanks you guys prompt helped in advance.
static UIImage *shrinkImage(UIImage *original, CGSize size) {
CGFloat scale = [UIScreen mainScreen].scale;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, size.width * scale,
size.height * scale, 8, 0, colorSpace, kCGImageAlphaPremultipliedFirst);
CGContextDrawImage(context,
CGRectMake(0, 0, original.size.width * scale, original.size.width * scale),
original.CGImage);
CGImageRef shrunken = CGBitmapContextCreateImage(context);
UIImage *final = [UIImage imageWithCGImage:shrunken];
CGContextRelease(context);
CGImageRelease(shrunken);
return final;
}
-(UIImage *)imageWithImage:(UIImage*)image scaledToSize:(CGSize)newSize
{
if(newSize.width > newSize.height)
newSize = CGSizeMake(newSize.height, newSize.height);
else
newSize = CGSizeMake(newSize.width, newSize.width);
UIGraphicsBeginImageContextWithOptions(newSize, YES, [UIScreen mainScreen].scale);
[image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
If you are not going to maintain aspect ratio, your image will surely look stretched.

Zooming UIImage/CGImage

I am implementing a zooming feature in a camera app using AVFoundation. I am scaling my preview view like this:
[videoPreviewView setTransform:CGAffineTransformMakeScale(cameraZoom, cameraZoom)];
Now, after I take a picture, I would like to zoom/crop the picture with the cameraZoom value before I save it to the camera roll. How best should I do this?
Edit: Using Justin's answer:
CGRect imageRect = CGRectMake(0.0f, 0.0f, image.size.width, image.size.height);
CGImageRef imageRef = CGImageCreateWithImageInRect([image CGImage], imageRect);
CGContextRef bitmapContext = CGBitmapContextCreate(NULL, CGImageGetWidth(imageRef), CGImageGetHeight(imageRef), CGImageGetBitsPerComponent(imageRef), CGImageGetBytesPerRow(imageRef), CGImageGetColorSpace(imageRef), CGImageGetBitmapInfo(imageRef));
CGContextScaleCTM(bitmapContext, scale, scale);
CGContextDrawImage(bitmapContext, imageRect, imageRef);
CGImageRef zoomedCGImage = CGBitmapContextCreateImage(bitmapContext);
UIImage* zoomedImage = [[UIImage alloc] initWithCGImage:imageRef];
It is zooming the image, but it is not taking the center of it, but rather seems to be taking the top right area. (I'm not positive).
The other problem, (I should have been clearer in the OP), is that the image remains the same resolution, but I would rather just crop it down.
+ (UIImage*)croppedImageWithImage:(UIImage *)image zoom:(CGFloat)zoom
{
CGFloat zoomReciprocal = 1.0f / zoom;
CGPoint offset = CGPointMake(image.size.width * ((1.0f - zoomReciprocal) / 2.0f), image.size.height * ((1.0f - zoomReciprocal) / 2.0f));
CGRect croppedRect = CGRectMake(offset.x, offset.y, image.size.width * zoomReciprocal, image.size.height * zoomReciprocal);
CGImageRef croppedImageRef = CGImageCreateWithImageInRect([image CGImage], croppedRect);
UIImage* croppedImage = [[UIImage alloc] initWithCGImage:croppedImageRef scale:[image scale] orientation:[image imageOrientation]];
CGImageRelease(croppedImageRef);
return croppedImage;
}
to scale/zoom:
create a CGBitmapContext
alter the context's transform (CGContextScaleCTM)
draw the image (CGContextDrawImage) - the rect you pass may be used to offset the origin and/or dimensions.
generate a new CGImage from the context (CGBitmapContextCreateImage)
to crop:
create a CGBitmapContext. pass NULL so the context creates is buffer for the bitmap.
draw the image as-is (CGContextDrawImage)
create a CGBitmapContext for the crop (with the new dimensions), using an offset of the first context's pixel data for the buffer.
generate a new CGImage from the second context (CGBitmapContextCreateImage)

Resources