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.
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;
}
I want to swap two images. When I move the drag image and it overlaps 50% or greater on any other image in the view it must be swapped. The problem is how can I check the drag image is 50% or more than 50% overlap with the other image. Please help and suggest the logic with a code example.
The code I am trying is :
if (imgVw.tag != self.tag) {
CGRect imgVwRect = CGRectMake(imgVw.frame.origin.x +(imgVw.frame.size.width/2), imgVw.frame.origin.y+(imgVw.frame.size.height/2), imgVw.frame.size.width, imgVw.frame.size.height);
CGRect movingImgRect = CGRectMake(newCenter.x+self.frame.size.width, newCenter.y+self.frame.size.height, self.frame.size.width, self.frame.size.height);
if (movingImgRect.origin.x >= imgVwRect.origin.x && movingImgRect.origin.y >= imgVwRect.origin.y)
{
NSLog(#"img view tag %lu",imgVw.tag);
UIImage *tempImg = self.image;
[self setImage:imgVw.image];
[imgVw setImage:tempImg];
}
}
This is pretty straightforward.
Use the method CGRectIntersection to calculate the intersection of the 2 rects.
Then compare the area of the intersection with the area of the moving rect. Something like this (assuming I understand your code
if (imgVw.tag != self.tag)
{
CGRect imgVwRect = CGRectMake(imgVw.frame.origin.x +(imgVw.frame.size.width/2),
imgVw.frame.origin.y+(imgVw.frame.size.height/2),
imgVw.frame.size.width,
imgVw.frame.size.height);
CGRect movingImgRect = CGRectMake(newCenter.x+self.frame.size.width,
newCenter.y+self.frame.size.height,
self.frame.size.width,
self.frame.size.height);
//Figure out the intersection rect of the 2 rectangles
CGRect intersectionRect = CGRectIntersection(imgVwRect, movingImgRect);
//Find the area of the intersection
CGFloat xArea = intersectionRect.size.height * intersectionRect.size.width;
//Find the area of the moving image
//(this code could be done once and saved in an iVar)
CGFloat movingImgArea = movingImgRect.size.height * movingImgRect.size.width;
//Is the intersection >= 1/2 the size of the moving image?
if (xArea*2 >= movingImgArea)
{
//Do whatever you need to do when the 2 images overlap by >= 50%
}
}
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.
In my iPad app, Universal Combat Log (new-layout branch), I have a UIView subclass (UCLLineChartView) which contains a UIScrollView and the scrollview in turn contains another UIView subclass (ChartView). ChartView has multiple sub-layers, one for each line of data that has been added to the chart. UCLLineChartView draws the axes and markers. The contents of these views/layers are entirely custom drawn, no stock views are used (e.g. UIImageView).
I'm having a problem with zooming -- it's scaling the ChartView like an image, which makes the drawn line all blurred and stretched. I want the line to stay sharp, preferably even while the user is in the act of zooming, but after 3 days of hacking at this, I cannot get it to work.
If I override setTransform on the ChartView to grab the scale factor from the transform but don't call [super setTransform], then the scrollview's zoomScale stays at 1. I tried keeping the given transform and overriding the transform method to return it. I tried replicating the effects of setTransform by changing the ChartView's center and bounds but I wasn't able to get the behaviour quite right and it still didn't seem to affect the scrollview's zoomScale. It seems that the scrollview's zoomScale depends on the effects of setTransform, but I cannot determine how.
Any assistance would be greatly appreciated.
What you will need to do is update the contentScaleFactor of the chartView. You can do that by adding the following code in either scrollViewDidEndZooming:withView:atScale: or scrollViewDidZoom:.
CGFloat newScale = scrollView.zoomScale * [[UIScreen mainScreen] scale];
[self.chartView setContentScaleFactor:newScale];
I have figured out a solution to my problem that is not too gross a hack. In your UIScrollViewDelegate:
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
{
[_contentView beginZoom];
}
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale
{
CGSize size = scrollView.bounds.size;
CGPoint contentOffset = _scrollView.contentOffset;
CGFloat newScale = _contentView.scale;
newScale = MAX(newScale, kMinZoomScale);
newScale = MIN(newScale, kMaxZoomScale);
[_scrollView setZoomScale:1.0 animated:NO];
_scrollView.minimumZoomScale = kMinZoomScale / newScale;
_scrollView.maximumZoomScale = kMaxZoomScale / newScale;
_contentView.scale = newScale;
CGSize newContentSize = CGSizeMake(size.width * newScale, size.height);
_contentView.frame = CGRectMake(0, 0, newContentSize.width, newContentSize.height);
_scrollView.contentSize = newContentSize;
[_scrollView setContentOffset:contentOffset animated:NO];
[_contentView updateForNewSize];
[_contentView setNeedsDisplay];
}
In your content view, declare a scale property and the following methods:
- (void)beginZoom
{
_sizeAtZoomStart = CGSizeApplyAffineTransform(self.frame.size, CGAffineTransformMakeScale(1/self.scale, 1));
_scaleAtZoomStart = self.scale;
}
- (void)setTransform:(CGAffineTransform)transform
{
self.scale = _scaleAtZoomStart * transform.a;
self.frame = CGRectMake(0, 0, _sizeAtZoomStart.width * self.scale, _sizeAtZoomStart.height);
[self updateForNewSize];
[self setNeedsDisplay];
}
And if your content view uses sub-layers, you'll need to disable their implicit animations by adding the following to the sub-layers' delegate(s):
- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event
{
// prevent animation of the individual layers so that zooming doesn't cause weird jitter
return (id<CAAction>)[NSNull null];
}
The basic idea here is that the overridden setTransform uses the scale factor from the tranform matrix to calculate the new scale factor for the content view and then resizes the content view accordingly. The scrollview automatically adjusts the content offset to keep the content view centered.
The scrollViewDidEndZooming code keeps the zooming bounded.
There are further complexities for dealing with resizing the scrollview when rotating device for example.
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.