I have a problem with a simple iPad image collage app. I have a UIImageView where it's possible to drag'n'drop different UIImageViews into. The dragged views can be rotated and stretched. When the user have finished editing the image, I'll merge all the images in on final image. My solution have been to use UIGraphics and draw the large image, then loop through all subviews and draw them in the context:
- (UIImage*)mergeImages
{
CGSize imageSize = imageView.frame.size;
UIGraphicsBeginImageContext(imageSize);
[imageView.image drawInRect:CGRectMake(0, 0, imageSize.width, imageSize.height)];
[[imageView subviews] enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop) {
UIImage *imageOriginal = ((UIImageView*)object).image;
CGRect rect = ((UIImageView*)object).frame;
UIImage *imageToAdd = [self scaleImage:imageOriginal :rect.size];
// Find the w/h ratioes
float widthRatio = rect.size.width / imageOriginal.size.width;
float heightRatio = rect.size.height / imageOriginal.size.height;
// Calculate point to draw at
CGPoint pointToDrawAt;
if (widthRatio > heightRatio)
pointToDrawAt = CGPointMake(rect.origin.x + (rect.size.width / 2) - (imageToAdd.size.width / 2), rect.origin.y);
else
pointToDrawAt = CGPointMake(rect.origin.x, rect.origin.y + (rect.size.width / 2) - (imageToAdd.size.height / 2));
[imageToAdd drawAtPoint:pointToDrawAt];
}];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// Save image in photogalery
UIImageWriteToSavedPhotosAlbum(newImage, nil, nil, nil);
return newImage;
}
The above works fines as long the images isn't rotated. So my question is:
Is there a way I can find the rotation angle from the subview, or do I have to subclass UIView and make it a property? Or is there another way I can merge images and get the drawing specific properties like angle and filters?
If I can help with further code or anything just comment. And thanks in advance :)
UIImages have a property called "imageOrientation" which can be queried. Once you know this, you can apply a CGAffineTransform to the context to get the image to render as you see it. There was a recent posting on this - in the past few days - if you search on 'CGAffineTransform' you should be able to find it (or a similar post) and see how its done.
Related
I have a CGPath shaped like an arrow that I am drawing in the CGContext of my current view. I would like to generate a miniature version (thumbnail) of the arrow to add it as an Image to a UITableView showing all selected arrows.
I am succeeding to downscale a picture of the full context which leaves the arrow smaller than it should be. Ideally I would like to crop the image of the full context to the bounds of the arrow. However, I was not yet successful. Any leads? Thanks for the help!
Here are a picture of the full view containing an arrow and another picture of the thumbnail I am generating.
Ideally the thumbnail above would be cropped to contain the arrow only - not the full context.
The code I use is the follwoing:
- (UIImage*) imageForObject:(id<GraphicalObject>) object
inRect:(CGRect)rect {
UIImage *image = [UIImage new];
CGRect objectBounds = [object objectBounds];
UIGraphicsBeginImageContext(self.view.frame.size);//objectBounds.size);
CGContextRef context =UIGraphicsGetCurrentContext();
[object drawInContext:context];
//doesn't work
CGContextClipToRect(context, objectBounds);
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
The CGRect called objectBounds has two components, an origin and a size. In order the draw the object correctly as a thumbnail, the code needs to scale the image (to get the size right) and translate the image (to move the origin to {0,0}). So the code looks like this
- (UIImage *)getThumbnailOfSize:(CGSize)size forObject:(UIBezierPath *)object
{
// to maintain the aspect ratio, we need to compute the scale
// factors for x and y, and then use the smaller of the two
CGFloat xscale = size.width / object.bounds.size.width;
CGFloat yscale = size.height / object.bounds.size.height;
CGFloat scale = (xscale < yscale) ? xscale : yscale;
// start a graphics context with the thumbnail size
UIGraphicsBeginImageContext( size );
CGContextRef context = UIGraphicsGetCurrentContext();
// here's where we scale and translate to make the image fit
CGContextScaleCTM( context, scale, scale );
CGContextTranslateCTM( context, -object.bounds.origin.x, -object.bounds.origin.y );
// draw the object and get the resulting image
[object stroke];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
thanks for reading.
I have a background image and a foreground image. The foreground image is in a UIScrollView so can be resized and repositioned over the background image. The background image is set as Aspect Fit. I have a function that combines the two UIImages into a new UIImage. That works fine, but what I can't get right is the x,y co-ordinates of one view over the other.
Here's some code:
CGFloat bgImageScale = self.backgroundImageView.bounds.size.height / self.bgImage.size.height; // Gives me the AspectFit scale.
CGFloat bgOffsetX = (self.backgroundImageView.bounds.size.width - self.bgImage.size.width * bgImageScale) / 2.0;
CGFloat bgOffsetY = 0.0;
CGFloat fgImageScale = self.fgImageScrollView.zoomScale;
CGFloat fgOffsetX = -self.fgImageScrollView.contentOffset.x;
CGFloat fgOffsetY = -self.fgImageScrollView.contentOffset.y;
CGPoint imageOffset = CGPointMake((fgOffsetX - bgOffsetX) * bgImageScale, (fgOffsetY - bgOffsetY) * bgImageScale);
[self.delegate completedOverlayImage:
[self mergeImage:self.fgImage
withImage:self.bgImage
usingAlpha:0.5f
withOffset:imageOffset
andScale:fgImageScale / bgImageScale
]];
In brief, the compeletedOverlayImage code does the following relevant bit:
[bottomImage drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
[topImage drawInRect:CGRectMake(imageOffset.x, imageOffset.y, newSize.width*imageScale, newSize.height*imageScale) blendMode:kCGBlendModeNormal alpha:alpha];
So I just can't get the imageOffset stuff right to get the new image overlaid the same as it appeared on-screen.
By the way, this app is iOS 7 and up only.
Can anyone help? Thanks.
Take a look at the UIView convertRect:toView: method.
Assuming that your view hierarchy looks like this
SomeViewController
View // the view controller's main view
BgView // the background view (UIImageView)
ScrollView
FgView // the foreground view (UIImageView)
If the foreground image is a UIImageView that's a child of the scroll view, then you can convert the FgView frame coordinates to the main view coordinate system with a line of code like this
CGRect foregroundFrame = [self.foregroundImageView convertRect:self.foregroundImageView.bounds toView:self.view];
Since the BgView's frame is already in mainView coordinates, this will give you both frames in the same coordinate system.
OK, I solved it myself. So for anyone else attempting the same thing, here's the code:
CGFloat bgImageScale = self.backgroundImageView.bounds.size.height / self.bgImage.size.height;
CGFloat bgOffsetX = (self.backgroundImageView.bounds.size.width - self.bgImage.size.width * bgImageScale) / 2.0;
CGFloat bgOffsetY = 0.0;
CGFloat fgImageScale = self.fgImageScrollView.zoomScale;
CGFloat fgRelativeZoom = fgImageScale / bgImageScale; // How much is fg zoomed compared to bg?
CGFloat fgOffsetX = -self.fgImageScrollView.contentOffset.x; // We want the offset of the (0,0), not the offset of the viewport. Hence, negative.
CGFloat fgOffsetY = -self.fgImageScrollView.contentOffset.y;
CGPoint imageOffset = CGPointMake(fgOffsetX / bgImageScale - bgOffsetX, fgOffsetY / bgImageScale - bgOffsetY);
[self.delegate completedOverlayImage:
[self mergeImage:self.fgImage
withImage:self.bgImage
usingAlpha:0.5f
withOffset:imageOffset
andScale:fgRelativeZoom
]];
and the function to combine the images (assuming iOS 7 which lets you set scale to zero in the UIGraphicsBeginContextWithOptions() call):
- (UIImage*) mergeImage:(UIImage*)topImage withImage:(UIImage*)bottomImage usingAlpha:(CGFloat)alpha withOffset:(CGPoint)imageOffset andScale:(CGFloat)imageScale {
int width = bottomImage.size.width;
int height = bottomImage.size.height;
CGSize newSize = CGSizeMake(width, height);
UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0);
[bottomImage drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
[topImage drawInRect:CGRectMake(imageOffset.x, imageOffset.y, newSize.width*imageScale, newSize.height*imageScale) blendMode:kCGBlendModeNormal alpha:alpha];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
Hope that helps someone else out there.
Hi,
I want to rotate my UIImageView without moving the whole "png". No code is only to test what happens
_fanImage.transform = CGAffineTransformMakeRotation(45);
It turns but the whole image moves. What can I do that this doesn't happen ?
You can try something like this.. You should rotate the UIImage rather than UIImageView.
- (UIImage *)imageWithTransform:(CGAffineTransform)transform {
CGRect rect = CGRectMake(0, 0, self.size.height, self.size.width);
CGImageRef imageRef = self.CGImage;
// Build a context that's the same dimensions as the new size
CGContextRef bitmap = CGBitmapContextCreate(NULL,
self.size.width,
self.size.height,
CGImageGetBitsPerComponent(imageRef),
0,
CGImageGetColorSpace(imageRef),
CGImageGetBitmapInfo(imageRef));
// Rotate and/or flip the image if required by its orientation
CGContextConcatCTM(bitmap, transform);
// Draw into the context; this scales the image
CGContextDrawImage(bitmap, rect, imageRef);
// Get the resized image from the context and a UIImage
CGImageRef newImageRef = CGBitmapContextCreateImage(bitmap);
UIImage *newImage = [UIImage imageWithCGImage:newImageRef];
// Clean up
CGContextRelease(bitmap);
CGImageRelease(newImageRef);
return newImage;
}
I think you mean that you want your image view to rotate around it's center point. Is that right? If so, that's what a view should do by default.
You should do a search on "Translating, Scaling, and Rotating Views" in Xcode and read the resulting article.
Note that all of iOS's angles are specified in radians, not degrees.
Your sample images aren't really helpful, since we can't see the frame that the image view is drawn into. It's almost impossible to tell what your image views are doing and what they are supposed to be doing instead based on the pictures you linked from your dropbox.
A full 360 degrees is 2pi.
You should use
CGFloat degrees = 45;
CGFloat radians = degrees/180*M_PI;
_fanImage.transform = CGAffineTransformMakeRotation(radians);
That will fix the rotation amount for your code, but probably not the rotation position.
For an iPad project I implemented an NSBrowser-like interface wich supports a dynamic number of columns. Each column is represented by an UITableView.
When adding or removing columns, I'm using UIView's animateWithDuration:animations: to change the width of the UITableView.
The problem I'm running into:
The system adds an unwanted frame size animation for the imageView of each table view cell, which resizes to imageView from very large to it's initial size. This looks really awkward, so I'm looking for ways to get rid off it - while keeping the animated frame size change for the enclosing tableViews.
Any ideas what I might be doing wrong?
I posted a sample project demonstration the issue here:
https://github.com/iljaiwas/TableViewFrameTest
Here is where I setup the cell:
https://github.com/iljaiwas/TableViewFrameTest/blob/master/TableViewFrameTest/TestTableViewController.m#L61
Here is where I trigger the animation:
https://github.com/iljaiwas/TableViewFrameTest/blob/master/TableViewFrameTest/TestViewController.m#L46
I was having the same issue and found this link (http://www.objc.io/issue-5/iOS7-hidden-gems-and-workarounds.html) which has a section on Blocking Animations.
To get your example working add the following at the top of TestTableViewController.m after the import statement:
#interface MyTableViewCell : UITableViewCell
#end
#implementation MyTableViewCell
- (void) layoutSubviews {
[UIView performWithoutAnimation:^{
[super layoutSubviews];
}];}
#end
Then change the following line in viewDidLoad to use MyTableViewCell:
[self.tableView registerClass:[MyTableViewCell class] forCellReuseIdentifier:#"Cell"];
Now run the example and you will no longer have your unwanted image animation.
this hepled to me:
set UIImageView ** (in TableView cell) **contentMode to aspect Fit.
do not know why, but works for me.
I was having a similar issue with the (pretty large) image shrinking down from its original size when the editing animation fired to show the delete button. The picture would fly across my screen as it shrank. Pretty crazy. Anyway, I fixed it by using this category on UIImage to resize the image before putting it in the UIImageView:
- (UIImage *)imageWithImage:(UIImage *)image scaledToSize:(CGSize)scaledSize {
UIGraphicsBeginImageContext(scaledSize);
[image drawInRect:CGRectMake(0, 0, scaledSize.width, scaledSize.height)];
UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return scaledImage;
}
Edit: here's another category that does a much better job of scaling down the image, depending on how good you need it to look. I originally found these on another SO question a while back but can't seem to find it now to link to, but I used them as a starting point and this one was especially helpful:
- (UIImage *)imageScaledToFitRect:(CGRect)rect {
// compute scale factor for imageView
CGFloat widthScaleFactor = CGRectGetWidth(rect) / self.size.width;
CGFloat heightScaleFactor = CGRectGetHeight(rect) / self.size.height;
NSLog(#"Rect width: %f, Image width: %f, WidthFactor: %f", rect.size.width, self.size.width, widthScaleFactor);
NSLog(#"Rect height: %f, Image height: %f, HeightFactor: %f", rect.size.height, self.size.height, heightScaleFactor);
CGFloat imageViewXOrigin = 0;
CGFloat imageViewYOrigin = 0;
CGFloat imageViewWidth = 0;
CGFloat imageViewHeight = 0;
// if image is narrow and tall, scale to width and align vertically to the top
if (widthScaleFactor > heightScaleFactor) {
imageViewWidth = self.size.width * widthScaleFactor;
imageViewHeight = self.size.height * widthScaleFactor;
}
// else if image is wide and short, scale to height and align horizontally centered
else {
imageViewWidth = self.size.width * heightScaleFactor;
imageViewHeight = self.size.height * heightScaleFactor;
imageViewXOrigin = - (imageViewWidth - CGRectGetWidth(rect))/2;
}
return [self imageScaledToRect:CGRectMake(imageViewXOrigin, imageViewYOrigin, imageViewWidth, imageViewHeight)];
}
Hope this can help someone else.
It looks like I might've potentially found an answer to one of my earlier problems and would be happy to post the solution on SO though I first need to confirm it works properly.
The problem is it seems to be - most of the time, though not always. I've isolated the problematic code - it's a method I created whose purpose is to return a UIImage of what is currently visible on the device's screen. It looks like this:
+ (UIImage *)getImageVisibleOnScreenWith: (CGRect) boundingRect rotationAngle: (CGFloat) angle scalingRatio: (CGFloat) scale entireImageView: (UIImageView *) imageView actualVisibleView: (UIView *) visibleView {
// Create a graphics context the size of the bounding rectangle
UIGraphicsBeginImageContext(boundingRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
// Rotate and translate the context
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformTranslate(transform, boundingRect.size.width/2, boundingRect.size.height/2);
transform = CGAffineTransformRotate(transform, angle);
transform = CGAffineTransformScale(transform, scale, -scale);
CGContextConcatCTM(context, transform);
// Draw the image into the context
CGContextDrawImage(context, CGRectMake(-imageView.image.size.width/2, -imageView.image.size.height/2, imageView.image.size.width, imageView.image.size.height), imageView.image.CGImage);
// Get an image from the context
UIImage *viewImage = [UIImage imageWithCGImage: CGBitmapContextCreateImage(context)];
// Clean up
UIGraphicsEndImageContext();
// Get the image currently on the screen (it's an intersection of specific UIImageViews)
CGRect visibleImageRect = CGRectIntersection(imageView.frame, visibleView.frame);
UIImage *visibleImage = (__bridge UIImage *)(CGImageCreateWithImageInRect((__bridge CGImageRef)(viewImage), visibleImageRect));
return visibleImage;
}
I pass on the result of this method to another one and noticed it sometimes returns nil - for no apparent reason, well at least I couldn't find any.
As usual, any ideas and help will be appreciated; also let me know if you need to see more code or if there's anything unclear as to what the purpose it is.