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;
}
Related
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.
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.
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
I am creating an iPhone app which has image cropping feature. In this, I am getting the photos from the UIImagePickerController and passing it for cropping. There it has a scrollview and the selected image will be added as a subview to the scrollview. And I am using a UIButton for selecting the area for cropping. User can move the button over the imageview and place it anywhere, and when click on CROP button, the area similar to the frame size of the button should be cropped from the imageview.
I used the following code, but it is not returning the actual image.
CGRect clippedRect = CGRectMake(self.scrollView.frame.origin.x+90, self.scrollView.frame.origin.y, self.scrollView.frame.size.width-180, self.scrollView.frame.size.height-220);
CGImageRef imageRef = CGImageCreateWithImageInRect([self.myPhoto CGImage], clippedRect);
UIImage *newImage = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
self.imageView.image = newImage;
also used
- (UIImage *)cropImage:(UIImage *)oldImage {
CGSize imageSize = self.cropFrame.frame.size;
UIGraphicsBeginImageContextWithOptions( CGSizeMake( imageSize.width, imageSize.height), NO, 0.);
[oldImage drawAtPoint:CGPointMake( xPosition, yPosition)
blendMode:kCGBlendModeCopy
alpha:1.];
UIImage *croppedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return croppedImage;
}
but the result image is not the exact image as per the button frame. I am getting the image from another area.
Updated code
- (void)loadPhoto{
CGFloat w = self.myPhoto.size.width;
CGFloat h = self.myPhoto.size.height;
CGRect imageViewFrame = CGRectMake(0.0f, 0.0f, roundf(w / 2.0f), roundf(h / 2.0f));
self.scrollView.contentSize = imageViewFrame.size;
UIImageView *iv = [[UIImageView alloc] initWithFrame:imageViewFrame];
iv.contentMode = UIViewContentModeScaleAspectFit;
iv.image = self.myPhoto;
[self.view addSubview:iv];
self.imageView = iv;
[iv release];
}
CGRect crop;//= CGRectMake(10, 10, 360, 360);
crop.origin.x = self.cropFrame.frame.origin.x;
crop.origin.y = self.cropFrame.frame.origin.y;
crop.size.width = roundf(self.cropFrame.frame.size.width * 2.0f); //self.cropFrame.frame.size.width * 2;
crop.size.height = roundf(self.cropFrame.frame.size.height * 2.0f); //self.cropFrame.frame.size.height * 2;
NSLog(#"Rect: %#", NSStringFromCGRect(crop));
self.imageView.image = [self croppedImage:crop];
- (UIImage *)croppedImage:(CGRect)bounds {
CGImageRef imageRef = CGImageCreateWithImageInRect([self.imageView.image CGImage], bounds);
UIImage *croppedImage = [UIImage imageWithCGImage:imageRef scale:1.0 orientation:self.myPhoto.imageOrientation];
CGImageRelease(imageRef);
return croppedImage;
}
Please help to find a solution.
The iOS has a default feature for cropping images.Try this code.
picker.allowsEditing = YES;
and also check this controller for cropping..this is exactly the one you are looking for I think https://github.com/barrettj/BJImageCropper .Hope this helps you..
Since you are using a scrollView that allows the image to be scrolled, you need to adjust your crop rect to the scrollView's position:
float zoomScale = self.scrollView.zoomScale;
int cropX = (self.scrollView.contentOffset.x-imageView.frame.origin.x)/zoomScale;
int cropY = (self.scrollView.contentOffset.y-imageView.frame.origin.y)/zoomScale;
You could use this crop tool that I made. It essentially gives you an interface to allow the user to select the crop area. I think it is in line with that you are looking for.
https://github.com/nicholjs/BFCropInterface
Believing you have solve this problem. Me too had this when tried cropping functionality
Set image.size as the imageView.size & scrollView.contentSize. Below code will give the rect to crop
cropRect.origin = scrollView.contentOffset;
cropRect.size = scrollView.bounds.size;
cropRect.origin.x /= scrollView.zoomScale;
cropRect.origin.y /= scrollView.zoomScale;
cropRect.size.width /= scrollView.zoomScale;
cropRect.size.height /= scrollView.zoomScale;
If planning to show the full image first on visible rect. Setting the imageView.size & scrollView.contentSize to visible view size will give crop image of some other area. Instead try finding the zoom scale by
CGFloat dxWidth = viewCrop.frame.size.width / imageView.image.size.width;
CGFloat dxHeight = viewCrop.frame.size.height / imageView.image.size.height;
CGFloat zoomScale = fmaxf(dWidth, dHeight)
and apply (if by adding subView then after addSubView)
scrollView.minimumZoomScale = zoomScale; // to disable further zoom-out
[scrollView setZoomScale: zoomScale];
I have an UIImage and a CGPoint which tells me in what direction I should move it to create another image. The background can be anything.
Give the initial UIImage how I can create the new one? What is the most efficient way of doing it?
Here is what I'm doing:
int originalWidth = image.size.width;
int originalHeight = image.size.height;
float xDifference = [[coords objectAtIndex:0] floatValue];
float yDifference = [[coords objectAtIndex:1] floatValue];
UIView *tempView = [[UIView alloc] initWithFrame:CGRectMake(0, 240, originalWidth, originalHeight)];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
imageView.contentMode = UIViewContentModeTopLeft;
CGRect imageFrame = imageView.frame;
imageFrame.origin.x = xDifference;
imageFrame.origin.y = -yDifference;
imageView.frame = imageFrame;
UIGraphicsBeginImageContext(tempView.bounds.size);
[tempView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Is there a more optimal version?
You can use CGImageCreateWithImageInRect(). You can get a CGImage from a UIImage with the property of the same name.
Basically what you end up doing is apply masks to the existing image to extract the portions you need. Like so -
myImageArea = CGRectMake(xOrigin, yOrigin, myWidth, myHeight);//newImage
mySubimage = CGImageCreateWithImageInRect(oldImage, myImageArea);
myRect = CGRectMake(0, 0, myWidth*2, myHeight*2);
CGContextDrawImage(context, myRect, mySubimage);
This post gives a good idea of how to use this property.