Multiple CGContextRefs? - ios

I am making a app similar to a drawing app, and want to draw an image at the place the user touches. I can draw the image at the location O.K. with this code:
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGRect imageRect = CGRectMake(self.locationOfTouch.x, self.locationOfTouch.y, 50, 50);
CGFloat centerX = self.locationOfTouch.x - (imageRect.size.width/2);
CGFloat centerY = self.locationOfTouch.y - (imageRect.size.height/2);
// To center image on touch loc
imageRect.origin.x = centerX;
imageRect.origin.y = centerY;
UIImage * imageImage = [UIImage imageNamed:#"image.png"];
CGImageRef imageRef = imageImage.CGImage;
CGContextBeginPath(ctx);
CGContextDrawImage(ctx, imageRect, imageRef);
But, whenever I tap again, the image moves to the new spot.
I would like it to "duplicate" every time it was tapped.
How can I do this?

You can try this.
- (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)ctx
{
CGPoint locationPoint = [self.touch locationInView:self];
CGRect imageRect = CGRectMake(locationPoint.x, locationPoint.y, 50, 50);
CGFloat centerX = locationPoint.x - (imageRect.size.width/2);
CGFloat centerY = locationPoint.y - (imageRect.size.height/2);
imageRect.origin.x = centerX;
imageRect.origin.y = centerY;
UIImage * imageImage = [UIImage imageNamed:#"add.png"];
UIImageView *imageView = [[UIImageView alloc ] initWithFrame:imageRect];
imageView.image = imageImage;
[layer addSublayer:imageView.layer];
}
it can work.

Related

Convert UIScrollViewPoint to UIImage + ZOOM + ROTATE

I need to crop UIImage which is loaded in UIScrollview with some rect of Another UIView which is also in UIScrollView
Following is View Hierarchy
--> View
--> UIScrollView
--> viewBase (UIView)
--> UIImageView -> (Zoomed & rotated )
--> UIView (Target View)(Movable User can move anywhere in scrollview to crop rect)
My Image is Rotated & Zoomed I need to get exact part of image in TargetView
I am drawing UIImage with rotation on context following is code
CGFloat angleCroppedImageRetreacted = atan2f(self.imgVPhoto.transform.b, self.imgVPhoto.transform.a);
angleCroppedImageRetreacted = angleCroppedImageRetreacted * (180 / M_PI);
UIView *rotatedViewBox = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, self.imgVPhoto.image.size.width, self.imgVPhoto.image.size.height)];
rotatedViewBox.transform = CGAffineTransformMakeRotation(-angleCroppedImageRetreacted);
CGSize rotatedSize = rotatedViewBox.frame.size;
UIGraphicsBeginImageContext(rotatedSize);
CGContextRef bitmap = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(bitmap, rotatedSize.width / 2.0f, rotatedSize.height / 2.0f);
CGContextRotateCTM(bitmap, -angleCroppedImageRetreacted);
CGContextScaleCTM(bitmap, 1.0f, -1.0f);
CGContextDrawImage(bitmap, CGRectMake(-self.imgVPhoto.image.size.width / 2.0f,
-self.imgVPhoto.image.size.height / 2.0f,
self.imgVPhoto.image.size.width,
self.imgVPhoto.image.size.height),
self.imgVPhoto.image.CGImage);
UIImage *resultImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
And it works fine . I am getting Rotated UIImage same as i can see in Simulator
For converting Point of Target View to UIImage I use following code which is NOT WORKING
CGPoint imageViewPoint = [self.viewBase convertPoint:self.targetImageview.center toView:self.imgVPhoto];
float percentX = imageViewPoint.x / self.imgVPhoto.frame.size.width;
float percentY = imageViewPoint.y / self.imgVPhoto.frame.size.height;
CGPoint imagePoint = CGPointMake(resultImage.size.width * percentX, resultImage.size.height * percentY);
rect.origin = imagePoint;
//rect.origin.x *= (self.imgVPhoto.image.size.width / self.imgVPhoto.frame.size.width);
//rect.origin.y *= (self.imgVPhoto.image.size.height / self.imgVPhoto.frame.size.height);
imageRef = CGImageCreateWithImageInRect([resultImage CGImage], rect);
img = [UIImage imageWithCGImage:imageRef scale:viewImage.scale orientation:viewImage.imageOrientation];
I think issue is we can't use Rect after Transform Apply
Please Help me to crop UIImage which is zoomed and rotated from rect on same Hierarchy
If you need more info pls ask
I am answering my own question .
Thanks to Matic for giving a idea
I changed a logic
I have achieved same functionality what i looking for
CGPoint locationInImageView = [self.viewBase convertPoint:self.targetImageview.center toView:self.view]; // received from touch
locationInImageView = [self.view convertPoint:locationInImageView toView:self.imgVPhoto];
// I GOT LOCATION IN UIIMAGEVIEW OF TOUCH POINT
UIGraphicsBeginImageContextWithOptions(self.view.frame.size, NO, 0);
[self.imgVPhoto.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *img1 = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// I GOT UIIMAGE FROM CURRENT CONTEXT
CGFloat width = self.targetImageview.frame.size.width * self.zoomScale ;
CGFloat height = self.targetImageview.frame.size.height * self.zoomScale ;
//2 IS SCALE FACTOR
CGFloat xPos = (locationInImageView.x * 2) - width / 2;
CGFloat yPos = (locationInImageView.y * 2) - height / 2;
CGRect rect1 = CGRectMake(xPos, yPos, width, height);
CGImageRef imageRef = CGImageCreateWithImageInRect([img1 CGImage], rect1);
// YAHHH YOU HAVE EXACT IMAGE
UIImage *img = [UIImage imageWithCGImage:imageRef scale:img1.scale orientation:img1.imageOrientation];

Move UIImage inside UIImageView

I have an UIImageView (red squares) that will display a UIImage that must be scaled (I can receive images greater or smaller that the UIImageView). After scaling it, the showed part of the UIImage is the center of it.
What I need is to show the part of the image in the blue squares, how can I archive it?
I'm only able to get the image size (height and width), but it display the original size, when it's supposed to be the scaled one.
self.viewIm = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 120, 80)];
self.viewIm.backgroundColor = [UIColor greenColor];
self.viewIm.layer.borderColor = [UIColor redColor].CGColor;
self.viewIm.layer.borderWidth = 5.0;
UIImage *im = [UIImage imageNamed:#"benjen"];
self.viewIm.image = im;
self.viewIm.contentMode = UIViewContentModeScaleAspectFill;
// self.viewim.clipsToBounds = YES;
[self.view addSubview:self.viewIm];
To do what you're trying to do, I'd recommend looking into CALayer's contentsRect property.
Since seeing your answer, I've been trying to work out the proper solution for a while, but the mathematics escapes me because contentsRect:'s x and y parameters seem sort of mysterious... But here's some code that may point you in the right direction...
float imageAspect = self.imageView.image.size.width/self.imageView.image.size.height;
float imageViewAspect = self.imageView.frame.size.width/self.imageView.frame.size.height;
if (imageAspect > imageViewAspect) {
float scaledImageWidth = self.imageView.frame.size.height * imageAspect;
float offsetWidth = -((scaledImageWidth-self.imageView.frame.size.width)/2);
self.imageView.layer.contentsRect = CGRectMake(offsetWidth/self.imageView.frame.size.width, 0.0, 1.0, 1.0);
} else if (imageAspect < imageViewAspect) {
float scaledImageHeight = self.imageView.frame.size.width * imageAspect;
float offsetHeight = ((scaledImageHeight-self.imageView.frame.size.height)/2);
self.imageView.layer.contentsRect = CGRectMake(0.0, offsetHeight/self.imageView.frame.size.height, 1.0, 1.0);
}
Try something like this:
CGRect cropRect = CGRectMake(0,0,200,200);
CGImageRef imageRef = CGImageCreateWithImageInRect([ImageToCrop CGImage],cropRect);
UIImage *image = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
I found a very good approximation on this answer. In that, the category resize the image, and use the center point to crop after that. I adapt it to crop using (0,0) as origin point. As I don't really need a category, I use it as a single method.
- (UIImage *)imageByScalingAndCropping:(UIImage *)image forSize:(CGSize)targetSize {
UIImage *sourceImage = image;
UIImage *newImage = nil;
CGFloat scaleFactor = 0.0;
CGFloat scaledWidth = targetSize.width;
CGFloat scaledHeight = targetSize.height;
if (CGSizeEqualToSize(image.size, targetSize) == NO) {
if ((targetSize.width / image.size.width) > (targetSize.height / image.size.height)) {
scaleFactor = targetSize.width / image.size.width; // scale to fit height
} else {
scaleFactor = targetSize.height / image.size.height; // scale to fit width
}
scaledWidth = image.size.width * scaleFactor;
scaledHeight = image.size.height * scaleFactor;
}
UIGraphicsBeginImageContext(targetSize); // this will crop
CGRect thumbnailRect = CGRectZero;
thumbnailRect.origin = CGPointZero;
thumbnailRect.size.width = scaledWidth;
thumbnailRect.size.height = scaledHeight;
[sourceImage drawInRect:thumbnailRect];
newImage = UIGraphicsGetImageFromCurrentImageContext();
if(newImage == nil) {
NSLog(#"could not scale image");
}
//pop the context to get back to the default
UIGraphicsEndImageContext();
return newImage;
}
And my call is something like this:
self.viewIm = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 120, 80)];
self.viewIm.image = [self imageByScalingAndCropping:[UIImage imageNamed:#"benjen"] forSize:CGSizeMake(120, 80)];
[self.view addSubview:self.viewIm];
I've spent some time on this and finally created a Swift 3.2 solution (based on one of my answers on another thread, as well as one of the answers above). This code only allows for Y translation of the image, but with some tweaks anyone should be able to add horizontal translation as well ;)
let yOffset: CGFloat = 20
myImageView.contentMode = .scaleAspectFill
//scale image to fit the imageView's width (maintaining aspect ratio), but allow control over the image's Y position
UIGraphicsBeginImageContextWithOptions(myImageView.frame.size, myImageView.isOpaque, 0.0)
let ratio = myImage.size.width / myImage.size.height
let newHeight = myImageView.frame.width / ratio
let rect = CGRect(x: 0, y: -yOffset, width: myImageView.frame.width, height: newHeight)
myImage.draw(in: rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext() ?? myImage
UIGraphicsEndImageContext()
//set the new image
myImageView.image = newImage
Now you can adjust how far down or up you need the image to be by changing the yOffset.

Retrieving UIImage from UIImageView sized accordingly

How may I retrieve the image from imageView sized as it is displayed (given the content mode), and not as it is according to native properties?
Code:
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, WID, WID)];
imageView.center = CGPointMake(point.x, point.y + Y_OFFSET);
imageView.image = [UIImage imageNamed:#"img"];
imageView.contentMode = UIViewContentModeScaleAspectFit;
You have to draw the image again then save it.
// Image frame size
CGSize size = imageView.bounds.size;
// Grab a new CGContext
UIGraphicsBeginImageContextWithOptions(size, false, 0.0);
// Draw the image
[image drawInRect:CGRectMake(0, 0, size.width, size.height)];
// Grab the new image
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
The above code draws the image in the frame, stretched to the bounds. If you want any other modes of how it is drawn, you have to calculate them yourself and put the desired stuff in the "Draw the image" line of code.
For example, for aspect fit, check out this algorithm:
- (CGRect) aspectFittedRect:(CGSize)inSize max:(CGRect)maxRect {
float originalAspectRatio = inSize.width / inSize.height;
float maxAspectRatio = maxRect.size.width / maxRect.size.height;
CGRect newRect = maxRect;
if (originalAspectRatio > maxAspectRatio) { // scale by width
newRect.size.height = maxRect.size.height * inSize.height / inSize.width;
newRect.origin.y += (maxRect.size.height - newRect.size.height)/2.0;
} else {
newRect.size.width = maxRect.size.height * inSize.width / inSize.height;
newRect.origin.x += (maxRect.size.width - newRect.size.width)/2.0;
}
return CGRectIntegral(newRect);
}
Just pass in imageView.image.size as inSize and imageView.bounds as maxRect.
Source:
http://iphonedevsdk.com/forum/iphone-sdk-development-advanced-discussion/15001-aspect-fit-algorithm.html

Cropping an image

I use two different methods to crop the center square out of this image. One works, one doesn't. My question is why.
Here are the two results:
Clearly, the left is buggy and the right works.
The image you see on the left uses only CGImageCreateWithImageInRect to
select the region of the image, where the rect is scaled by the ratio of the
original image dimensions to those of the view's dimensions. Why doesn't this method work?
The image you see on the right translates the image and then selects the region
of interest with the origin at 0,0 using CGImageCreateWithImageInRect
Here's the code that draws both images:
- (UIImage *)cropImage:(UIImage *)original inRect:(CGRect)rect {
CGFloat heightScale = original.size.height / self.view.frame.size.height;
CGFloat widthScale = original.size.width / self.view.frame.size.width;
CGRect scaledRect = CGRectMake(rect.origin.x * widthScale, rect.origin.y * heightScale, rect.size.width * widthScale, rect.size.height * heightScale);
UIGraphicsBeginImageContextWithOptions(original.size, YES, 1.0);
[original drawAtPoint:CGPointMake(-scaledRect.origin.x, -scaledRect.origin.y)];
UIImage *translatedImage = UIGraphicsGetImageFromCurrentImageContext();
CGRect finalRect = CGRectMake(0, 0, scaledRect.size.width, scaledRect.size.height);
CGImageRef imageRefForRightImage = CGImageCreateWithImageInRect([translatedImage CGImage], finalRect);
CGImageRef imageRefForLeftImage = CGImageCreateWithImageInRect([original CGImage], scaledRect);
UIImage *croppedRightImage = [UIImage imageWithCGImage:imageRefForRightImage];
UIImage *croppedLeftImage = [UIImage imageWithCGImage:imageRefForLeftImage];
CGImageRelease(imageRefForRightImage);
CGImageRelease(imageRefForLeftImage);
UIImageView *colorImageView = [[UIImageView alloc] initWithFrame:self.view.frame];
colorImageView.backgroundColor = [UIColor purpleColor];
[self.view addSubview:colorImageView];
CGRect rectLeft = CGRectMake(0, 0, 160, 160);
CGRect rectRight = CGRectMake(160, 0, 160, 160);
UIImageView *croppedImageViewLeft = [[UIImageView alloc] initWithFrame:rectLeft];
UIImageView *croppedImageViewRight = [[UIImageView alloc] initWithFrame:rectRight];
croppedImageViewLeft.image = croppedLeftImage;
croppedImageViewRight.image = croppedRightImage;
croppedImageViewLeft.contentMode = UIViewContentModeScaleAspectFit;
croppedImageViewRight.contentMode = UIViewContentModeScaleAspectFit;
[self.view addSubview:croppedImageViewLeft];
[self.view addSubview:croppedImageViewRight];
croppedImageViewRight.image = croppedRightImage;
croppedImageViewLeft.image = croppedLeftImage;
return croppedRightImage;
}

When using avfoundation - crop visible portion of captured image represented in display layer

I am using PBJVision library to capture images.
Under the hood it is using AVFoundation.
I set up the camera to use the following options
PBJVision *camera = [[PBJVision alloc] init];
self.camera = camera;
self.camera.delegate = self;
[self.camera setCameraMode:PBJCameraModePhoto];
[self.camera setCameraDevice:PBJCameraDeviceFront];
[self.camera setCameraOrientation:PBJCameraOrientationPortrait];
[self.camera setFocusMode:PBJFocusModeAutoFocus];
[self.camera setPresentationFrame:self.previewView.frame];
[self.camera previewLayer].frame = self.previewView.bounds;
[self.camera previewLayer].videoGravity = AVLayerVideoGravityResizeAspectFill;
[self.previewView.layer addSublayer:[self.camera previewLayer]];
because the preview layer gravity is set as AVLayerVideoGravityResizeAspectFill the captured image isn't identical to the previewed image.
How can I crop it according to the video gravity?
Based on Erica Sadun's excellent Cookbook, adding the code below to your view controller will allow you to do
UIImage *newImage = [self.applyAspectFillImage: image InRect: self.previewView.bounds];
You can obtain the maximum image size by using the smaller edge (width or height) of the original photo image to size your destination rectangle.
CGFloat scaleW = image.size.width / previewView.bounds.size.width;
CGRect destRect = CGRectMake(0, 0, image.size.width, preview.bounds.size.height * scaleW);
UIImage *newImage = [self.applyAspectFillImage: image InRect: destRect];
The code:
CGRect CGRectCenteredInRect(CGRect rect, CGRect mainRect)
{
CGFloat xOffset = CGRectGetMidX(mainRect)-CGRectGetMidX(rect);
CGFloat yOffset = CGRectGetMidY(mainRect)-CGRectGetMidY(rect);
return CGRectOffset(rect, xOffset, yOffset);
}
// Calculate the destination scale for filling
CGFloat CGAspectScaleFill(CGSize sourceSize, CGRect destRect)
{
CGSize destSize = destRect.size;
CGFloat scaleW = destSize.width / sourceSize.width;
CGFloat scaleH = destSize.height / sourceSize.height;
return MAX(scaleW, scaleH);
}
CGRect CGRectAspectFillRect(CGSize sourceSize, CGRect destRect)
{
CGSize destSize = destRect.size;
CGFloat destScale = CGAspectScaleFill(sourceSize, destRect);
CGFloat newWidth = sourceSize.width * destScale;
CGFloat newHeight = sourceSize.height * destScale;
CGFloat dWidth = ((destSize.width - newWidth) / 2.0f);
CGFloat dHeight = ((destSize.height - newHeight) / 2.0f);
CGRect rect = CGRectMake (dWidth, dHeight, newWidth, newHeight);
return rect;
}
- (UIImage *) applyAspectFillImage: (UIImage *) image InRect: (CGRect) bounds
{
CGRect destRect;
UIGraphicsBeginImageContext(bounds.size);
CGRect rect = CGRectAspectFillRect(image.size, bounds);
destRect = CGRectCenteredInRect(rect, bounds);
[image drawInRect: destRect];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}

Resources