Cutting out predefined piece of a photo from camera - iphone

For an application I am developing, I let the user specify dimensions of an object they want to capture on camera (for example 30" x 40"). The next thing I want to do, is show the camera feed with a cameraOverlayView on top of it, showing nothing but a stroked transparent square which has the right ratio to capture that object.
So I tried 2 things to get this to work:
Use a UIViewController which uses the AVCaptureVideoPreviewLayer to display a view with the live video feed. On top of that feed I display a transparent view, which draws a square with the right dimensions (using the ratio the user specified).
and
In another attempt I created a UIViewController, containing a button which pops up the UIImagePickerController. Using this controller, I also created a view which I attach to the picker using the cameraOverlayView property.
The main problem I am having with both these methods is that the image that is actually captured is always larger then what I see on screen, but I am not entirely sure how to cut out THAT piece of the image, after the picture has been taken.
So for example:
My UIImagePickerController is shown, I put an overlay over that showing a square that is 300 x 400px large. The user uses this square to take a picture of their object and centers their object inside this square.
The picture is taken, but instead of a picture that is 320x480 (or 640x960) I get a result that is 3500x2400 (or something like that. It's a completely different ratio then the screen ratio (of course).
How do I then make sure I cut out the right part of the image.
The code that actually calculates the size of the square that should be shown (and should be used to determine what piece of the picture should be cut):
+ (CGRect) getFrameRect:(CGRect) rect forSize:(CGSize) frameSize {
if (CGSizeEqualToSize(frameSize, CGSizeZero))
return CGRectZero;
float maxWidth = rect.size.width - 20;
float maxHeight = rect.size.height - 20;
float ratioX = maxWidth / frameSize.width;
float ratioY = maxHeight / frameSize.height;
float ratio = MIN(ratioX, ratioY);
float newWidth = frameSize.width * ratio;
float newHeight = frameSize.height * ratio;
float x = (rect.size.width - newWidth) / 2;
float y = (rect.size.height - newHeight) / 2;
return CGRectMake(x, y, newWidth, newHeight);
}
This determines the largest square that can be created with the ratio specified in the frameSize parameter, with the dimensions where the square should be drawn in supplied in the rect parameter.
Some solutions come to mind, but I am not sure that is doable.
Shrink the photo down to screen width or height (whatever comes first), take the center of the picture and that is the same center that is shown when taking the picture? Not sure this is going to work, I tried this a bit, but that failed.

Ok, I found the solution:
When you are taking a picture with your camera, the preview screen shows only a part of the photo you are taking.
When your iOS device is in portrait mode, the photo height is scaled down to the height of the screen, and only the middle 640px are shown.
The darker red part is what is shown on screen. So when you take a picture, you need to downsize your image to the max height of your screen to get the right width.
After that I cut out the middle 640x960 pixels to actually get the same image as was shown when taking the picture.
After that the coordinates of my rectangular overlay are the same as with my overlay.
- (void) imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
[picker dismissModalViewControllerAnimated:YES];
UIImage* artImage = [info objectForKey:UIImagePickerControllerOriginalImage];
CGFloat imageHeightRatio = artImage.size.height / 960;
CGFloat imageWidth = artImage.size.width / imageHeightRatio;
CGSize newImageSize = CGSizeMake(imageWidth, 960);
artImage = [artImage imageByScalingProportionallyToSize:newImageSize];
CGRect cutOutRect = CGRectMake((artImage.size.width / 2) - (640 / 2), 0, 640, 960);
artImage = [self imageByCropping:artImage toRect:cutOutRect];
CGRect imageCutRect = [FrameSizeCalculations getFrameRect:CGRectMake(0,0, artImage.size.width, artImage.size.height) forSize:self.frameSize];
artImage = [self imageByCropping:artImage toRect:imageCutRect];
CGRect imageViewRect = CGRectInset(_containmentView.bounds, 10, 10);
NSLog(#"ContainmentView: %f x %f x %f x %f",
_containmentView.frame.origin.x,
_containmentView.frame.origin.y,
_containmentView.frame.size.width,
_containmentView.frame.size.height
);
NSLog(#"imageViewRect: %f x %f x %f x %f",
imageViewRect.origin.x,
imageViewRect.origin.y,
imageViewRect.size.width,
imageViewRect.size.height
);
_imageView.frame = [FrameSizeCalculations getFrameRect:imageViewRect forSize:self.frameSize];
NSLog(#"imageViewRect: %f x %f x %f x %f",
_imageView.frame.origin.x,
_imageView.frame.origin.y,
_imageView.frame.size.width,
_imageView.frame.size.height
);
_imageView.contentMode = UIViewContentModeScaleAspectFill;
_imageView.image = artImage;
}

Related

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();

AFOpenFlow coverFlow

in the AFOpenFlow library are two lines that says that the coverFlow is in the middle of your
iPhone - Screen:
CGPoint newPosition;
newPosition.x = halfScreenWidth + aCover.horizontalPosition;
newPosition.y = halfScreenHeight + aCover.verticalPosition;
But how could i change this line, that the coverFlow at the high range of the screen?
Marco
I don't know AFOpenFlow but this math here set the middle of the View related to the center of the screen. If you want the view to only take half of the size of your screen, you also need to change its height. it would be like :
[Updated Code]
CGSize newSize = CGSizeMake(screenWidth, swcreenHeight/2);
CGPoint newCenterPosition = CGPointMake(halfScreenWidth+aCover.horizontalPosition, halfScreenHeight/2+aCover.verticalPosition)
aCover.bounds = CGRectMake(newCenterPosition.x, newCenterPosition.y, newSize.width, newSize.height);

Formula for producing a CGRect for a UIScrollView, that displays a UIImage in scaled to fit way

I am loading in images with varying sizes and putting them in UIScrollViews, all the images are larger than the UIScrollView.
The user can scroll and zoom as they please, but initially I would like for the image
to be centered and scaled so the largest side of the image aligns with the edge of the scrollView, i.e. if the picture is in landscape I would like to size and scale it so that the left and right side goes all the way to the edge of the UIScrollVIew and vice versa
I found a formula in a utility function in the Programming guide but it does not quite fit my needs.
My approach is to use:
CGrect initialPos = ?
[self.scrollView zoomToRect:initialPos animated:YES];
I know the size of my scrollView and the size of my image, what I need to figure out is the scale and CGRect to apply to the scrollView to center and size my image.
Hope someone can help out:) Thanks
Edit: previous version was sizing the image to the view rather than the other way around. I think this should correct that:
double imgRatio = imageSize.width / imageSize.height;
double viewRatio = viewSize.width / viewSize.height;
if ( imgRatio >= viewRatio )
{
initialPos.size.width = imageSize.width;
initialPos.size.height = imageSize.width / viewRatio;
initialPos.origin.x = 0;
initialPos.origin.y = (imageSize.height - initialPos.size.height) / 2;
}
else
{
initialPos.size.height = imageSize.height;
initialPos.size.width = imageSize.height * viewRatio;
initialPos.origin.y = 0;
initialPos.origin.x = (imageSize.width - initialPos.size.width) / 2;
}

UIScrollView zoomToRect not zooming to given rect (created from UITouch CGPoint)

My application has a UIScrollView with one subview. The subview is an extended UIView which prints a PDF page to itself using layers in the drawLayer event.
Zooming using the built in pinching works great. setZoomScale also works as expected.
I have been struggling with the zoomToRect function. I found an example online which makes a CGRect zoomRect variable from a given CGPoint.
In the touchesEnded function, if there was a double tap and they are all the way zoomed out, I want to zoom in to that PDFUIView I created as though they were pinching out with the center of the pinch where they double tapped.
So assume that I pass the UITouch variable to my function which utilizes zoomToRect if they double tap.
I started with the following function I found on apples site:
http://developer.apple.com/iphone/library/documentation/WindowsViews/Conceptual/UIScrollView_pg/ZoomZoom/ZoomZoom.html
The following is a modified version for my UIScrollView extended class:
- (void)zoomToCenter:(float)scale withCenter:(CGPoint)center {
CGRect zoomRect;
zoomRect.size.height = self.frame.size.height / scale;
zoomRect.size.width = self.frame.size.width / scale;
zoomRect.origin.x = center.x - (zoomRect.size.width / 2.0);
zoomRect.origin.y = center.y - (zoomRect.size.height / 2.0);
//return zoomRect;
[self zoomToRect:zoomRect animated:YES];
}
When I do this, the UIScrollView seems to zoom using the bottom right edge of the zoomRect above and not the center.
If I make UIView like this
UIView *v = [[UIView alloc] initWithFrame:zoomRect];
[v setBackgroundColor:[UIView redColor]];
[self addSubview:v];
The red box shows up with the touch point dead in the center.
Please note: I am writing this from my PC, I recall messing around with the divided by two part on my Mac, so just assume that this draws a rect with the touch point in the center. If the UIView drew off center but zoomed to the right spot it would be all good.
However, what happens is when it preforms the zoomToRect it seems to use the bottom right off the zoomRect at the top left of the zoomed in results.
Also, I noticed that depending on where I click on the UIScrollView, it anchors to diffrent spots. It almost seems like there is a cross down the middle and it's reflecting the points somehow as though anywhere left of the middle is a negative reflection and anywhere right of the middle is a positive reflection?
This seems to complicated, shouldn't it just zoom to the rect that was drawn as the UIView was able to draw?
I used a lot of research to figure out how to create a PDF that scales in high quality, so I am assuming that using the CALayer may be throwing off the coordinate system? But to the UIScrollView it should just treat it as a view with 768x985 dimensions.
This is sort of advanced, please assume the code for creating the zoomRect is all good. There is something deeper with the CALayer in the UIView which is in the UIScrollView....
Ok another answer:
The apple supplied routine works for me, but you need to have the gesture recognizer convert the tap point to the imageView coords - not to the scroller.
Apple's example does this, but since our app works differently (we change the UIImageView), so the gestureRecongnizer was set up on the uiscrollview - which works fine, but you need to do this in the handleDoubleTap:
This is loosely based on the apple example code "TaptoZoom", but as I said we needed our gesture recognizer hooked up to the scroll view.
- (void)handleDoubleTap:(UIGestureRecognizer *)gestureRecognizer {
// double tap zooms in
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(handleSingleTap:) object:nil];
float newScale = [imageScrollView zoomScale] * 1.5;
// Note we need to get location of the tap in the imageView coords, not the imageScrollView
CGRect zoomRect = [self zoomRectForScale:newScale withCenter:[gestureRecognizer locationInView:imageView]];
[imageScrollView zoomToRect:zoomRect animated:YES];
}
Declare BOOL isZoom; in .h
-(void)handleDoubleTap:(UIGestureRecognizer *)recognizer {
if(isZoom){
CGPoint Pointview=[recognizer locationInView:self];
CGFloat newZoomscal=3.0;
newZoomscal=MIN(newZoomscal, self.maximumZoomScale);
CGSize scrollViewSize=self.bounds.size;
CGFloat w=scrollViewSize.width/newZoomscal;
CGFloat h=scrollViewSize.height /newZoomscal;
CGFloat x= Pointview.x-(w/2.0);
CGFloat y = Pointview.y-(h/2.0);
CGRect rectTozoom=CGRectMake(x, y, w, h);
[self zoomToRect:rectTozoom animated:YES];
[self setZoomScale:3.0 animated:YES];
isZoom=NO;
}
else{
[self setZoomScale:1.0 animated:YES];
isZoom=YES;
}
}
I've noticed that the apple you're using doesn't zoom properly if the image is starting at a zoomScale less than 1 because the zoomRect origin is incorrect. I edited it to work correctly. Here's the code:
- (CGRect)zoomRectForScale:(float)scale withCenter:(CGPoint)center {
CGRect zoomRect;
// the zoom rect is in the content view's coordinates.
// At a zoom scale of 1.0, it would be the size of the imageScrollView's bounds.
// As the zoom scale decreases, so more content is visible, the size of the rect grows.
zoomRect.size.height = [self frame].size.height / scale;
zoomRect.size.width = [self frame].size.width / scale;
// choose an origin so as to get the right center.
zoomRect.origin.x = (center.x * (2 - self.minimumZoomScale) - (zoomRect.size.width / 2.0));
zoomRect.origin.y = (center.y * (2 - self.minimumZoomScale) - (zoomRect.size.height / 2.0));
return zoomRect;
}
The key is this part multiplying the center value by (2 - self.minimumZoomScale).
Hope this helps.
In my case it was:
zoomRect.origin.x = center.x / self.zoomScale - (zoomRect.size.width / 2.0);
zoomRect.origin.y = center.y / self.zoomScale - (zoomRect.size.height / 2.0);
extension UIScrollView {
func getRectForVisibleView() -> CGRect {
var visibleRect: CGRect = .zero
visibleRect.origin = self.contentOffset
visibleRect.size = self.bounds.size
let theScale = 1.0 / self.zoomScale
visibleRect.origin.x *= theScale
visibleRect.origin.y *= theScale
visibleRect.size.width *= theScale
visibleRect.size.height *= theScale
return visibleRect
}
func moveToRect(rect: CGRect) {
let scale = self.bounds.width / rect.width
self.zoomScale = scale
self.contentOffset = .init(x: rect.origin.x * scale, y: rect.origin.y * scale)
}
}
I had something similar and it was because I didn't adjust the center.x and center.y values by dividing them by the scale also (using center.x/scale and center.y/scale). Maybe I'm not reading your code right.
I am having the same behavior and it is quite frustrating... The rectangle being fed to the UIScrollView is perfect.. yet my view, no matter what I do anything that involves changing the zoomScale programmatically always zooms and scales to coordinate 0,0, no matter what.
I have tried just changing the zoomScale, I've tried zoomToRect, I have tried them all, and every one the minute I touch the zoomScale in code, it goes to coordinate 0,0.
I did also have to add and explicit setContentSize to the resized image in the scrollview after a zooming operation, or otherwise I cannot scroll after a zoom or pinch.
Is this a bug in 3.1.3 or what?
I have tried different solutions, but this looks the best resolution
It is really straight forward and conceptional?
CGRect frame = [[UIScreen mainScreen] applicationFrame];
scrollView.contentInset = UIEdgeInsetsMake(frame.size.height/2,
frame.size.width/2,
frame.size.height/2,
frame.size.width/2);
I disagree with one of the comments above saying that you should never multiply the center's coordinates by some factor.
Say that you are currently displaying an entire 400x400px image or PDF file in a 100x100 scroll view and want to allow the users to double the size of the content until it's 1:1.
If you double tap at point (75,75), you expect the zoomed-in rectangle to have origin 100,100 and size 100x100 within the new 200x200 content view. So the original tapping point (75,75) is now (150,150) in the new 200x200 space.
Now, after zoom action #1 has completed, if you again double tap at (75,75) inside the new 100x100 rectangle (which is the bottom-right square of the larger 200x200 rectangle), you expect the user to be shown the bottom-right 100x100 square of the larger image, which would now become zoomed to 400x400 pixels.
In order to calculate the origin of this latest 100x100 rectangle within the larger 400x400 rectangle, you would need to consider the scale and current content offset (since before this last zoom action we were displaying the bottom-right 100x100 rectangle within a 200x200 content rectangle).
So the x coordinate of the final rectangle becomes:
center.x/currentScale - (scrollView.frame.size.width/2) + scrollView.contentOffset.x/currentScale
= 75/.5 - 100/2 + 100/.5 = 150 - 50 + 200 = 300.
In this case, being a square, the calculation for the y coordinate is the same.
And we did indeed zoom in the bottom-right 100x100 rectangle, which, in the larger 400x400 content view has origin 300,300.
So here is how you would calculate the zoom rectangle's size and origin:
zoomRect.size.height = mScrollView.frame.size.height/scale;
zoomRect.size.width = mScrollView.frame.size.width/scale;
zoomRect.origin.x = center.x/currentScale - (mScrollView.frame.size.width/2) + mScrollView.contentOffset.x/currentScale;
zoomRect.origin.y = center.y/currentScale - (mScrollView.frame.size.height/2) + mScrollView.contentOffset.y/currentScale;
Hope this made sense; it's hard to explain it in writing without sketching out the various squares/rectangles.
Cheers,
Raf Colasante

Proper use of UIRectClip to scale a UIImage down to icon size

Given a UIImage of any dimension, I wish to generate a square "icon" sized version, px pixels to a side, without any distortion (stretching). However, I'm running into a little snag. Not quite sure where the problem is. Here's what I'm doing so far.
First, given a UImage size, I determine three things: the ratio to use when scaling down the image; a delta (the difference between our desired icon size and the longest side), and an offset (which is used to figure out our origin coordinate when clipping the image):
if (size.width > size.height) {
ratio = px / size.width;
delta = (ratio*size.width - ratio*size.height);
offset = CGPointMake(delta/2, 0);
} else {
ratio = px / size.height;
delta = (ratio*size.height - ratio*size.width);
offset = CGPointMake(0, delta/2);
}
Now, let's say you have an image 640px wide by 480px high, and we want to get a 50px x 50px icon out of this. The width is greater than the height, so our calculations are:
ratio = 50px / 640px = 0.078125
delta = (ratio * 640px) - (ratio * 480px) = 50px - 37.5px = 12.5px
offset = {x=6.25, y=0}
Next, I create a CGRect rect that is large enough to be cropped down to our desired icon size without distortion, plus a clipRect for clipping purposes:
CGRect rect = CGRectMake(0.0, 0.0, (ratio * size.width) + delta,
(ratio * size.height) + delta);
CGRect clipRect = CGRectMake(offset.x, offset.y, px, px);
Substituting our values from above, we get:
rect = origin {x=0.0, y=0.0}, size {width=62.5, height=50.0}
clipRect = origin {x=6.25, y=0}, size {width=50.0, height=50.0}
So now we have a 62.5px wide by 50px high rect to work with, and a clipping rectangle that grabs the "middle" 50x50 portion.
On to the home stretch! Next, we set up our image context, draw the UIImage (called myImage here) into the rect, set the clipping rectangle, get the (presumably now-clipped) image, use it, and finally clean up our image context:
UIGraphicsBeginImageContext(rect.size);
[myImage drawInRect:rect];
UIRectClip(clipRect);
UIImage *icon = UIGraphicsGetImageFromCurrentImageContext();
// Do something with the icon here ...
UIGraphicsEndImageContext();
Only one problem: The clipping never occurs! I end up with an image 63px wide x 50px high. :(
Perhaps I'm misusing/misunderstanding UIRectClip? I've tried shuffling various things around: swapping the use of rect and clipRect, moving UIRectClip before drawInRect:. No dice.
I tried searching for an example of this method online as well, to no avail. For the record, UIRectClip is defined as:
Modifies the current clipping path by
intersecting it with the specified
rectangle.
Shuffling things around gets us a little bit closer:
UIGraphicsBeginImageContext(clipRect.size);
UIRectClip(rect);
[myImage drawInRect:rect];
Now we don't have distortion, but the clipped image isn't centered on the original as I expected. Still, at least the image is 50x50, though the variable names are now fouled up as a result of said shuffling. (I'll respectfully leave renaming as an exercise for the reader.)
Eureka! I had things a little mixed up. This works:
CGRect clipRect = CGRectMake(-offset.x, -offset.y,
(ratio * size.width) + delta,
(ratio * size.height) + delta);
UIGraphicsBeginImageContext(CGSizeMake(px, px));
UIRectClip(clipRect);
[myImage drawInRect:clipRect];
UIImage *icon = UIGraphicsGetImageFromCurrentImageContext();
// Do something with the icon here ...
UIGraphicsEndImageContext();
No more need for rect. The trick appears to be using a negative offset in the clipping rectangle, thereby lining up the origin of where we want to grab our 50 x 50 image (in this example).
Perhaps there's an easier way. If so, please weigh in!
I wanted to achieve a similar thing but found the answer from by the original poster didn't quite work. It distorted the image. This may well be solely because he didn't post the whole solution and had changed some of how the variables are initialised:
(if (size.width > size.height)
ratio = px / size.width;
Was wrong for my solution (which wanted to use the largest possible square from the source image). Also it is not necessary to use UIClipRect - if you make the context the size of the image you want to extract, no actual drawing will be done outside that rect anyway. It is just a matter of scaling the size of the image rect and offsetting one of the origin coordinates. I have posted my solution below:
+(UIImage *)makeIconImage:(UIImage *)image
{
CGFloat destSize = 400.0;
CGRect rect = CGRectMake(0, 0, destSize, destSize);
UIGraphicsBeginImageContext(rect.size);
if(image.size.width != image.size.height)
{
CGFloat ratio;
CGRect destRect;
if (image.size.width > image.size.height)
{
ratio = destSize / image.size.height;
CGFloat destWidth = image.size.width * ratio;
CGFloat destX = (destWidth - destSize) / 2.0;
destRect = CGRectMake(-destX, 0, destWidth, destSize);
}
else
{
ratio = destSize / image.size.width;
CGFloat destHeight = image.size.height * ratio;
CGFloat destY = (destHeight - destSize) / 2.0;
destRect = CGRectMake(0, destY, destSize, destHeight);
}
[image drawInRect:destRect];
}
else
{
[image drawInRect:rect];
}
UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return scaledImage;
}
wheeliebin answers is correct but he forgot a minus sign in front of destY
destRect = CGRectMake(0, -destY, destSize, destHeight);