How to I rotate UIImageView by 90 degrees inside a UIScrollView with correct image size and scrolling? - iphone

I have an image inside an UIImageView which is within a UIScrollView. What I want to do is rotate this image 90 degrees so that it is in landscape by default, and set the initial zoom of the image so that the entire image fits into the scrollview and then allow it to be zoomed up to 100% and back down to minimum zoom again.
This is what I have so far:
self.imageView.transform = CGAffineTransformMakeRotation(-M_PI/2);
float minimumScale = scrollView.frame.size.width / self.imageView.frame.size.width;
scrollView.minimumZoomScale = minimumScale;
scrollView.zoomScale = minimumScale;
scrollView.contentSize = CGSizeMake(self.imageView.frame.size.height,self.imageView.frame.size.width);
The problem is that if I set the transform, nothing shows up in the scrollview. However if I commented out the transform, everything works except the image is not in the landscape orientation that I want it to be!
If I apply the transform and remove the code that sets the minimumZoomScale and zoomScale properties, then the image shows up in the correct orientation, however with the incorrect zoomScale and seems like the contentSize property isn't set correctly either - since the doesn't scroll to the edge of the image in the left/right direction, however does top and bottom but much over the edge.
NB: image is being loaded from a URL

Maybe rotating the image itself fits your needs:
UIImage* rotateUIImage(const UIImage* src, float angleDegrees) {
UIView* rotatedViewBox = [[UIView alloc] initWithFrame: CGRectMake(0, 0, src.size.width, src.size.height)];
float angleRadians = angleDegrees * ((float)M_PI / 180.0f);
CGAffineTransform t = CGAffineTransformMakeRotation(angleRadians);
rotatedViewBox.transform = t;
CGSize rotatedSize = rotatedViewBox.frame.size;
[rotatedViewBox release];
UIGraphicsBeginImageContext(rotatedSize);
CGContextRef bitmap = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(bitmap, rotatedSize.width/2, rotatedSize.height/2);
CGContextRotateCTM(bitmap, angleRadians);
CGContextScaleCTM(bitmap, 1.0, -1.0);
CGContextDrawImage(bitmap, CGRectMake(-src.size.width / 2, -src.size.height / 2, src.size.width, src.size.height), [src CGImage]);
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}

I believe the easiest way (and thread safe too) is to do:
//assume that the image is loaded in landscape mode from disk
UIImage * LandscapeImage = [UIImage imageNamed: imgname];
UIImage * PortraitImage = [[UIImage alloc] initWithCGImage: LandscapeImage.CGImage
scale: 1.0
orientation: UIImageOrientationLeft];

Any calculations that you do based on the imageView's frame should probably be done before you apply any transformations to it. But I would actually suggest doing those calculations based on the size of the UIImage, not the UIImageView. Then set both the UIImageView's frame and the UIScrollView's contentSize based on that.
Max's suggestion is a good one, although with a larger image it could be a performance killer. Are you displaying this image from your app's resources? If so, why not just rotate the images before you even build the app?

There's a much easier solution that is also faster, just do this:
- (void) imageRotateTapped:(id)sender
{
[UIView animateWithDuration:0.33f animations:^()
{
self.imageView.transform = CGAffineTransformMakeRotation(RADIANS(self.rotateDegrees += 90.0f));
self.imageView.frame = self.imageView.superview.bounds; // change this to whatever rect you want
}];
}
When the user is done, you will need to actually create a new rotated image, but that is very easy to do.

I was using the accepted answer for a while until we noticed that non-square rotations based on images taken directly from the camera seemed stretched (they were rotated as desired, just the frame width/height wasn't adjusted).
Great explanation/post here from Trevor: http://vocaro.com/trevor/blog/2009/10/12/resize-a-uiimage-the-right-way/
In the end, it was a very simple import of Trevor's code which uses categories to add a resizedImage:interpoationQuality method to UIImage. So yeah, user beware, if it still works for you, great. But if it doesn't, I'd take a look at the library instead.

Related

Rendering views, scaled #2x, renderInContext, iPhone

I have a view (called outPutView) that contains graphics, like uIImageViews and labels. I need to render an image of the outPutView and it's sub-views. I am using renderInContext:UIGraphicsGetCurrentContext() to do so. Works fine, except that I need to scale the views. I am using a transform on the outPutView. This successfully scales the view and it's sub-views, but the transform does not render. While the views are scaled onscreen. the final render displays the vies at their original size, while the render context is at the target size (here #2x iPhone view size).
Thanks for reading!!
[outPutView setTransform:CGAffineTransformMake(2, 0, 0, 2, 0, 0)];
CGSize renderSize = CGSizeMake(self.view.bounds.size.width*2, self.view.bounds.size.height*2);
UIGraphicsBeginImageContext(renderSize);
[[outPutView layer] renderInContext:UIGraphicsGetCurrentContext()];
renderedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
I've just made this work, although in the opposite direction (scaling down). Here's a summary of the relevant code:
// destination size is half of self (self is a UIView)
float rescale = 0.5;
CGSize resize = CGSizeMake(self.width * rescale, self.height * rescale);
// make the destination context
UIGraphicsBeginImageContextWithOptions(resize, YES, 0);
// apply the scale to dest context
CGContextScaleCTM(UIGraphicsGetCurrentContext(), rescale, rescale);
// render self into dest context
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
// grab the resulting UIImage
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
To scale up instead of down, it should be fine to have rescale = 2.
I solved this by re-ordering my views. Actually adding another view between the output view: the view that the rendered context is taken from, and the view that is scaled via transform. It worked, but I have no idea why at this point. Any thoughts on this would be appreciated. Thanks for reading.

iPhone SDK: Problem saving one image over another

basically I am making an app that involves a user taking a photo, or selecting one already on their device, and then placing an overlay onto the image.
So, I seem to have coded everything fine, apart from one thing, after the user has selected the overlay and positioned it, when saved the size of the overlay has changed, whereas the x and y values seem correct.
And so this is the code I use to add the overlay ("image" being the users photo):
float wid = (overlay.image.size.width);
float hei = (overlay.image.size.height);
overlay.frame = CGRectMake(0, 0, wid, hei);
[image addSubview:overlay];
And this is the code used to save the resulting image:
UIGraphicsBeginImageContext(image.image.size);
// Draw the users photo
[image.image drawInRect:CGRectMake(0, 0, image.image.size.width, image.image.size.height)];
// Draw the overlay
float xx = (overlay.center.x);
float yy = (overlay.center.y);
CGRect aaFrame = overlay.frame;
float width = aaFrame.size.width;
float height = aaFrame.size.height;
[overlay.image drawInRect:CGRectMake(xx, yy, width, height)];
UIGraphicsEndImageContext();
Any help? Thanks
The problem is that you are using image's size rather than the image view's frame size. Image seems to be much larger than its image view so when you use the image's size the other image's size ends up being much smaller in comparison although it is still the correct size. You can modify your snippet to this –
UIGraphicsBeginImageContext(image.frame.size);
// Draw the users photo
[image.image drawInRect:CGRectMake(0, 0, image.frame.size.width, image.frame.size.height)];
[overlay.image drawInRect:overlay.frame];
UIImage * resultingImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Avoiding loss of quality
While the above method leads to loss of resolution, trying to draw the parent image in its proper resolution might have an unwanted effect on its child image i.e. if the overlay wasn't of high resolution itself then it can end being stretchy. However you can try this code to draw it in the parent image's resolution (untested, let me know if you've problems ) –
float verticalScale = image.image.size.height / image.frame.size.height;
float horizontalScale = image.image.size.width / image.frame.size.width;
CGRect overlayFrame = overlay.frame;
overlayFrame.origin.x *= horizontalScale;
overlayFrame.origin.y *= verticalScale;
overlayFrame.size.width *= horizontalScale;
overlayFrame.size.height *= verticalScale;
UIGraphicsBeginImageContext(image.image.size);
// Draw the users photo
[image.image drawInRect:CGRectMake(0, 0, image.image.size.width, image.image.size.height)];
[overlay.image drawInRect:overlayFrame];
UIImage * resultingImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

A simple way to put a UIImage in a UIButton

I have a UIButton in my iPhone app. I set its size to 100x100.
I have an image that is 400x200 that I wish to display in the button.
The button STILL needs to stay at 100x100... and I want the image to downsize to fit... but
keep the correct aspect ratio.
I thought that's what "Aspect Fit" was used for.
Do I include my image with setImage, or setBackgroundImage?
Do I set AspectFit for the button? or the image? or the background image?
(I need the smaller images to INCREASE in size. While larger images should DESCREASE in size.
Always keeping the button at 100x100.)
I've tried countless combinations of all of the above... and I just can't seem to get
everything to work at the same time:
Don't change the button's size of 100x100.
Don't destroy the image's aspect ratio.
Always increase small images to fit the button.
Always decrease large images to fit the button.
Never clip any of the image edges.
Never require the "put UIButtons over all your UIimages" hack.
Don't require everyone to upgrade to v4.2.1 just to use new framework methods.
I see so many apps, with so many fully-working image-buttons... that I can't believe I can't figure out this very simple, very common thing.
Ugh.
UIButton is broken. That's the short answer. The UIImageViews in its image and backgroundImage properties don't respect UIViewContentMode settings. They're read-only properties, and while the UIImage contents of those UIImageViews can be set through setImage: and setBackgroundImage: methods, the content mode can't be.
The solution is either to provide properly-sized images in your bundle to begin with, or to put a UIImageView down, configure it the way you want it, and then put a clear "custom" UIButton over top of it. That's the hack all those fancy professional apps you've seen have used, I promise. We're all having to do it.
UIImage *img = [UIImage imageNamed:#"yourImageName"];
button.imageView.contentMode = UIViewContentModeScaleAspectFit;
[button setImage:img forState:UIControlStateNormal];
To do this correctly, I would actually programmatically resize and manipulate the image to get the desired aspect ratio. This avoids the need for any view hierarchy hacks, and also reduces any performance hit to a single operation, instead of every redraw.
This (untested) code should help illustrate what I mean:
CGSize imageSize = image.size;
CGFloat currentAspect = imageSize.width / imageSize.height;
// for purposes of illustration
CGFloat targetWidth = 100;
CGFloat targetHeight = 100;
CGFloat targetAspect = targetWidth / targetHeight;
CGFloat newWidth, newHeight;
if (currentAspect > targetAspect) {
// width will end up at 100, height needs to be smaller
newWidth = targetWidth;
newHeight = targetWidth / currentAspect;
} else {
// height will end up at 100, width needs to be smaller
newHeight = targetHeight;
newWidth = targetHeight * currentAspect;
}
size_t bytesPerPixel = 4;
// although the image will be resized to { newWidth, newHeight }, it needs
// to be padded with empty space to provide the aspect fit behavior
//
// use calloc() to clear the data as it's allocated
void *imageData = calloc(targetWidth * targetHeight, bytesPerPixel);
if (!imageData) {
// error out
return;
}
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
if (!colorSpace) {
// error out
return;
}
CGContextRef context = CGBitmapContextCreate(
imageData,
targetWidth,
targetHeight,
8, // bits per component
targetWidth * bytesPerPixel, // bytes per row
colorSpace,
kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst
);
CGColorSpaceRelease(colorSpace);
// now we have a context to draw the original image into
// in doing so, we want to center it, so prepare the geometry
CGRect drawRect = CGRectMake(
floor((targetWidth - newWidth) / 2),
floor((targetHeight - newHeight) / 2),
round(newWidth),
round(newHeight)
);
CGContextDrawImage(context, drawRect, image.CGImage);
// now that the bitmap context contains the aspect fit image with transparency
// letterboxing, we want to pull out a new image from it
CGImageRef newImage = CGBitmapContextCreateImage(context);
// destroy the temporary context
CGContextRelease(context);
free(imageData);
// and, finally, create a new UIImage
UIImage *newUIImage = [UIImage imageWithCGImage:newImage];
CGImageRelease(newImage);
Let me know if any part of that is unclear.
I think what Dan is trying to say (but without ever saying it) is to do this:
Use a "temp image" to do the resizing for you.
The temp-image needs to be set to ASPECT FIT and HIDDEN.
Make sure your button is set to your desired size, and NOT set to ASPECT FIT.
// Make a frame the same size as your button
CGRect aFrame = CGRectMake(0, 0, myButton.frame.size.width, myButton.frame.size.height);
// Set your temp-image to the size of your button
imgTemp.frame = aFrame;
// Put your image into the temp-image
imgTemp.image = anImage;
// Copy that resized temp-image to your button
[myButton setBackgroundImage:tempImage forState:UIControlStateNormal];
Since none of my attempts have worked....
Maybe I should be asking this instead. When using a UIButton:
When DO I use setImage instead of setBackgroundImage? (Why are there both?)
When DO I use "Aspect Fit" instead of "Center"? (Why do both seem to stretch my images when I expect them to "keep aspect ratio" and "don't resize anything", respective.)
And the big question: Why is such a common thing... such a huge mess?
It would all be solved if I could find a work-around method like: Just use UIImage instead and detect TAPS. (But that seems to be even a LARGER nightmare of code.)
Apple, if you've tried to make my job easier... you have instead made it 400 times more confusing.
Place a imageview over the button, set your image for the imageview and not for button.
All the best.
I would resize the image to 100x100 maintaining the aspect ratio of the content contained in the image. Then set the backgroundImage property of the UIButton to the image.
I faced same issue few days back and resolved it. Please try with this
[_profilePicBtn setImage:profilePic forState:UIControlStateNormal];
_profilePicBtn.imageView.contentMode = UIViewContentModeScaleAspectFit;

Save a UIImage from a UIImageView with CGAffineTransform

I have a UIImageView within a UIScrollView which I have enabled the user to perform any number of flip and rotation operations on. I have this all working which allows the user to zoom, pan, flip and rotate. Now I want to be able to save the final image out to a png.
however it is doing my head in trying to work this out...
I have seen quite a few other posts similar to this but most only require applying a single transform such as a rotation eg Creating a UIImage from a rotated UIImageView
I would like to apply any transform that the user has "created" which will be a series of flip and rotations concatenated togethers
As the user is applying various rotations, flips etc, I store the concatenated transform using CGAffineTransformConcat. For example when they rotate I do:
CGAffineTransform newTransform = CGAffineTransformMakeRotation(angle);
self.theFullTransform = CGAffineTransformConcat(self.theFullTransform, newTransform);
self.fullPhotoImageView.transform = self.theFullTransform;
The following method is the best I have gotten so far for creating a UIImage with the full transform however the image is always translated in the wrong place. Eg the image is "offset". Which my guess is either related to using the wrong bounds being set in in CGAffineTransformTranslate or CGContextDrawImage.
Does anyone have any ideas? This seems a lot harder that I thought it should be...
- (UIImage *) translateImageFromImageView: (UIImageView *) imageView withTransform:(CGAffineTransform) aTransform
{
UIImage *rotatedImage;
// Get image width, height of the bounding rectangle
CGRect boundingRect = CGRectApplyAffineTransform(imageView.bounds, aTransform);
// Create a graphics context the size of the bounding rectangle
UIGraphicsBeginImageContext(boundingRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGAffineTransform transform = CGAffineTransformIdentity;
//I think this translaton is the problem?
transform = CGAffineTransformTranslate(transform, boundingRect.size.width/2, boundingRect.size.height/2);
transform = CGAffineTransformScale(transform, 1.0, -1.0);
transform = CGAffineTransformConcat(transform, aTransform);
CGContextConcatCTM(context, transform);
// Draw the image into the context
// or the boundingRect is incorrect here?
CGContextDrawImage(context, boundingRect, imageView.image.CGImage);
// Get an image from the context
rotatedImage = [UIImage imageWithCGImage: CGBitmapContextCreateImage(context)];
// Clean up
UIGraphicsEndImageContext();
return rotatedImage;
}
Is the offset predictable, like always half the image, or does it depend on aTransform?
struct CGAffineTransform {
CGFloat a, b, c, d;
CGFloat tx, ty;
};
If the latter, set tx and ty to zero in aTransform before using it.

Any quick and dirty anti-aliasing techniques for a rotated UIImageView?

I've got a UIImageView (full frame and rectangular) that i'm rotating with a CGAffineTransform. The UIImage of the UIImageView fills the entire frame. When the image is rotated and drawn the edges appear noticeably jagged. Is there anything I can do to make it look better? It's clearly not being anti-aliased with the background.
The edges of CoreAnimation layers aren't antialiased by default on iOS. However, there is a key that you can set in Info.plist that enables antialiasing of the edges: UIViewEdgeAntialiasing.
https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/iPhoneOSKeys.html
If you don't want the performance overhead of enabling this option, a work-around is to add a 1px transparent border around the edge of the image. This means that the 'edges' of the image are no longer on the edge, so don't need special treatment!
New API – iOS 6/7
Also works for iOS 6, as noted by #Chris, but wasn't made public until iOS 7.
Since iOS 7, CALayer has a new property allowsEdgeAntialiasing which does exactly what you want in this case, without incurring the overhead of enabling it for all views in your application! This is a property of CALayer, so to enable this for a UIView you use myView.layer.allowsEdgeAntialiasing = YES.
just add 1px transparent border to your image
CGRect imageRect = CGRectMake(0, 0, image.size.width, image.size.height);
UIGraphicsBeginImageContextWithOptions(imageRect.size, NO, 0.0);
[image drawInRect:CGRectMake(1,1,image.size.width-2,image.size.height-2)];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Remember to set the appropriate anti-alias options:
CGContextSetAllowsAntialiasing(theContext, true);
CGContextSetShouldAntialias(theContext, true);
just add "Renders with edge antialiasing" with YES in plist and it will work.
I would totally recommend the following library.
http://vocaro.com/trevor/blog/2009/10/12/resize-a-uiimage-the-right-way/
It contains lots of useful extensions to UIImage that solve this problem and also include code for generating thumbnails etc.
Enjoy!
The best way I've found to have smooth edges and a sharp image is to do this:
CGRect imageRect = CGRectMake(0, 0, self.photo.image.size.width, self.photo.image.size.height);
UIGraphicsBeginImageContextWithOptions(imageRect.size, NO, 0.0);
[self.photo.image drawInRect:CGRectMake(1, 1, self.photo.image.size.width - 2, self.photo.image.size.height - 2)];
self.photo.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Adding the Info.plist key like some people describe has a big hit on performance and if you use that then you're basically applying it to everything instead of just the one place you need it.
Also, don't just use UIGraphicsBeginImageContext(imageRect.size); otherwise the layer will be blurry. You have to use UIGraphicsBeginImageContextWithOptions like I've shown.
I found this solution from here, and it's perfect:
+ (UIImage *)renderImageFromView:(UIView *)view withRect:(CGRect)frame transparentInsets:(UIEdgeInsets)insets {
CGSize imageSizeWithBorder = CGSizeMake(frame.size.width + insets.left + insets.right, frame.size.height + insets.top + insets.bottom);
// Create a new context of the desired size to render the image
UIGraphicsBeginImageContextWithOptions(imageSizeWithBorder, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
// Clip the context to the portion of the view we will draw
CGContextClipToRect(context, (CGRect){{insets.left, insets.top}, frame.size});
// Translate it, to the desired position
CGContextTranslateCTM(context, -frame.origin.x + insets.left, -frame.origin.y + insets.top);
// Render the view as image
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
// Fetch the image
UIImage *renderedImage = UIGraphicsGetImageFromCurrentImageContext();
// Cleanup
UIGraphicsEndImageContext();
return renderedImage;
}
usage:
UIImage *image = [UIImage renderImageFromView:view withRect:view.bounds transparentInsets:UIEdgeInsetsZero];