I am building a app where you can crop multiple images. I am using this code directly from apple:
func cropImage(_ inputImage: UIImage, toRect cropRect: CGRect, viewWidth: CGFloat, viewHeight: CGFloat) -> UIImage?
{
let imageViewScale = max(inputImage.size.width / viewWidth,
inputImage.size.height / viewHeight)
// Scale cropRect to handle images larger than shown-on-screen size
let cropZone = CGRect(x:cropRect.origin.x * imageViewScale,
y:cropRect.origin.y * imageViewScale,
width:cropRect.size.width * imageViewScale,
height:cropRect.size.height * imageViewScale)
// Perform cropping in Core Graphics
guard let cutImageRef: CGImage = inputImage.cgImage?.cropping(to:cropZone)
else {
return nil
}
// Return image to UIImage
let croppedImage: UIImage = UIImage(cgImage: cutImageRef)
return croppedImage
}
to crop the image I need a cropRect. I found also a solution in the Internet that I implemented in my code:
func realImageRect() -> CGRect {
let imageViewSize = self.frame.size
let imgSize = self.image?.size
guard let imageSize = imgSize else {
return CGRect.zero
}
let scaleWidth = imageViewSize.width / imageSize.width
let scaleHeight = imageViewSize.height / imageSize.height
let aspect = fmin(scaleWidth, scaleHeight)
var imageRect = CGRect(x: 0, y: 0, width: imageSize.width * aspect, height: imageSize.height * aspect)
// Center image
imageRect.origin.x = (imageViewSize.width - imageRect.size.width) / 2
imageRect.origin.y = (imageViewSize.height - imageRect.size.height) / 2
// Add imageView offset
imageRect.origin.x += self.frame.origin.x
imageRect.origin.y += self.frame.origin.y
return imageRect
}
As I already said, the app can crop multiple images. The images are stored in a array. I also have a crop view, which you can drag around the image with a pan gesture
for i in 0..<imageContentView.count {
let cropRect = CGRect(x: croppedViewArray[i].frame.origin.x - imageContentView[i].realImageRect().origin.x, y: croppedViewArray[i].frame.origin.y - imageContentView[i].realImageRect().origin.y, width: croppedViewArray[i].frame.width, height: croppedViewArray[i].frame.height)
print("cropRect", cropRect)
let croppedImage = ImageCrophandler.sharedInstance.cropImage(imageContentView[i].image!, toRect: cropRect, viewWidth: imageContentView[i].frame.width, viewHeight: imageContentView[i].frame.height)
print("cheight", croppedImage!.size.height,"cwidth", croppedImage!.size.width)
arrayOfCropedImages.append(croppedImage!)
}
The problem what I have is, that every cropped image has a different height and widths, but the images should be all the same size.
I figured out that the size gets calculated on which position the crop view is located.
Related
I want to get original x and y position of UIImage when we set it in UIImageView with scaleAspectFill.
As we know in scaleAspectFill, some of the portion is clipped. So as per my requirement I want to get x and y value (it may be - value I don't know.).
Here is the original image from gallery
Now I am setting this above image to my app view.
So as above situation, I want to get it's hidden x, y position of image which are clipped.
Can any one tell how to get it?
Use following extension
extension UIImageView {
var imageRect: CGRect {
guard let imageSize = self.image?.size else { return self.frame }
let scale = UIScreen.main.scale
let imageWidth = (imageSize.width / scale).rounded()
let frameWidth = self.frame.width.rounded()
let imageHeight = (imageSize.height / scale).rounded()
let frameHeight = self.frame.height.rounded()
let ratio = max(frameWidth / imageWidth, frameHeight / imageHeight)
let newSize = CGSize(width: imageWidth * ratio, height: imageHeight * ratio)
let newOrigin = CGPoint(x: self.center.x - (newSize.width / 2), y: self.center.y - (newSize.height / 2))
return CGRect(origin: newOrigin, size: newSize)
}
}
Usage
let rect = imageView.imageRect
print(rect)
UI Test
let testView = UIView(frame: rect)
testView.backgroundColor = UIColor.red.withAlphaComponent(0.5)
imageView.superview?.addSubview(testView)
Use below extension to find out accurate details of Image in ImageView.
extension UIImageView {
var contentRect: CGRect {
guard let image = image else { return bounds }
guard contentMode == .scaleAspectFit else { return bounds }
guard image.size.width > 0 && image.size.height > 0 else { return bounds }
let scale: CGFloat
if image.size.width > image.size.height {
scale = bounds.width / image.size.width
} else {
scale = bounds.height / image.size.height
}
let size = CGSize(width: image.size.width * scale, height: image.size.height * scale)
let x = (bounds.width - size.width) / 2.0
let y = (bounds.height - size.height) / 2.0
return CGRect(x: x, y: y, width: size.width, height: size.height)
}
}
How to test
let rect = imgTest.contentRect
print("Image rect:", rect)
Reference: https://www.hackingwithswift.com/example-code/uikit/how-to-find-an-aspect-fit-images-size-inside-an-image-view
If you want to show image like it shows in gallery then you can use contraints
"H:|[v0]|" and "V:|[v0]|" and in imageview use .aspectFit
And if you want the image size you can use imageView.image!.size and calculate the amount of image which is getting cut. In aspectFill the width is matched to screenwidth and accordingly the height gets increased. So I guess you can find how how much amount of image is getting cut.
Try this Library ImageCoordinateSpace
I am not sure if it works for you or not, but it has a feature to convert CGPoint from image coordinates to any view coordinates and vice versa.
I'm working on an app where I'm cropping an image.
Currently, this is how I crop it:
mainPicture.layer.cornerRadius = mainPicture.frame.size.width / 2
mainPicture.clipsToBounds = true
The request is not to crop it from the middle but rather to crop it in a specific radius and 12 px from the top.
I start with a normal image:
and when I currently crop it just gets cropped from the middle, so the result is like this:
The request is to crop it so that the top part of the circle will be 12 px from the top:
So that the final image would look like this:
How can this be done using Swift 4.0?
Here what you need to do is first crop the original image into a square image from top with the margin you want (like 20) and then set image to your Image view.
Here's a extension you can write on UIImage class for cropping:
extension UIImage {
func getCroppedImage(with topMargin: CGFloat) -> UIImage? {
let heightWidth = size.height < size.width ? size.height : size.width
let x = (size.width - heightWidth)/2
let rect = CGRect(x: x, y: topMargin, width: heightWidth, height: heightWidth)
if let imageRef = cgImage?.cropping(to: rect) {
return UIImage(cgImage: imageRef)
}
return nil
}
}
Then before setting the image to UIImageView call this method for your Image like:
let image = UIImage(named: "test")
imageView.image = image?.getCroppedImage(with: 20)
Output:
This is the input image:
This is the Output:
fixed it by cropping the image prior to posting it using this function:
func cropToBounds(image: UIImage, width: CGFloat, height: CGFloat) -> UIImage {
let cgimage = image.cgImage!
let contextImage: UIImage = UIImage(cgImage: cgimage)
let contextSize: CGSize = contextImage.size
var posX: CGFloat = 0.0
var posY: CGFloat = 0.0
var cgwidth: CGFloat = width
var cgheight: CGFloat = height
// See what size is longer and create the center off of that
if contextSize.width > contextSize.height {
posX = ((contextSize.width - contextSize.height) / 2)
posY = 0
cgwidth = contextSize.height
cgheight = contextSize.height
} else {
posX = 0
posY = (( contextSize.width - contextSize.height) / 2)
cgwidth = contextSize.width
cgheight = contextSize.width
}
let rect: CGRect = CGRect(x: posX, y: posY, width: cgwidth, height: cgheight)
// Create bitmap image from context using the rect
let imageRef: CGImage = cgimage.cropping(to: rect)!
// Create a new image based on the imageRef and rotate back to the original orientation
let image: UIImage = UIImage(cgImage: imageRef, scale: image.scale, orientation: image.imageOrientation)
return image
}
I am using this library's cropping functions to crop image like Instagram does. (https://github.com/fahidattique55/FAImageCropper) And its cropping part of the code works like this.
private func captureVisibleRect() -> UIImage {
var croprect = CGRect.zero
let xOffset = (scrollView.imageToDisplay?.size.width)! / scrollView.contentSize.width;
let yOffset = (scrollView.imageToDisplay?.size.height)! / scrollView.contentSize.height;
croprect.origin.x = scrollView.contentOffset.x * xOffset;
croprect.origin.y = scrollView.contentOffset.y * yOffset;
let normalizedWidth = (scrollView?.frame.width)! / (scrollView?.contentSize.width)!
let normalizedHeight = (scrollView?.frame.height)! / (scrollView?.contentSize.height)!
croprect.size.width = scrollView.imageToDisplay!.size.width * normalizedWidth
croprect.size.height = scrollView.imageToDisplay!.size.height * normalizedHeight
let toCropImage = scrollView.imageView.image?.fixImageOrientation()
let cr: CGImage? = toCropImage?.cgImage?.cropping(to: croprect)
let cropped = UIImage(cgImage: cr!)
return cropped }
But the problem is for example i have a photo with (800(W)*600(H)) size, and i want to crop it with full width by using full zoom out.This function calculates croprect variable (800(W)*800(H)) correctly. But after this part of the code let cr: CGImage? = toCropImage?.cgImage?.cropping(to: croprect) the cr's resolution becomes (800(W)*600(H)). How can i transform this to square image by filling the empty parts of it with white color?
You can square the image after this process by using the answer in this link. How to draw full UIImage inside a square with white color on the edge
This is the Swift 3 version of it.
private func squareImageFromImage(image: UIImage) -> UIImage{
var maxSize = max(image.size.width,image.size.height)
var squareSize = CGSize.init(width: maxSize, height: maxSize)
var dx = (maxSize - image.size.width) / 2.0
var dy = (maxSize - image.size.height) / 2.0
UIGraphicsBeginImageContext(squareSize)
var rect = CGRect.init(x: 0, y: 0, width: maxSize, height: maxSize)
var context = UIGraphicsGetCurrentContext()
context?.setFillColor(UIColor.white.cgColor)
context?.fill(rect)
rect = rect.insetBy(dx: dx, dy: dy)
image.draw(in: rect, blendMode: CGBlendMode.normal, alpha: 1.0)
var squareImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return squareImage!
}
I suggest you use UIGraphicsContext to draw a rectangle with the intended width and height, filling it with the desired color. Then draw the cropped image on it.
I haven't tested this but this should work for what you want.
I have omitted other parts of your code to focus on the essentials.
....
let context: CGContext? = UIGraphicsGetCurrentContext()
let rect = CGRect(x: 0, y: 0, width: width, height: height)
let color = UIColor.white
color.setFill()
context?.fill(rect)
let cr: CGImage? = toCropImage?.cgImage?.cropping(to: croprect)
let cropped = UIImage(cgImage: cr!)
context?.draw(cropped, in: rect)
let newImage: UIImage? = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
Replace width and height with the desired width and height.
Simple extensions for Cropping images in different ways
I you want to crop from the center use cropAspectFill and if you want to keep full image and want to make it square then use cropAspectFit
Objective-C solution
#implementation UIImage (crop)
- (UIImage *)cropAspectFill {
CGFloat minSize = MIN(self.size.height, self.size.width);
CGSize squareSize = CGSizeMake(minSize, minSize);
// Get our offset to center the image inside our new square frame
CGFloat dx = (minSize - self.size.width) / 2.0f;
CGFloat dy = (minSize - self.size.height) / 2.0f;
UIGraphicsBeginImageContext(squareSize);
CGRect rect = CGRectMake(0, 0, minSize, minSize);
// Adjust the rect to be centered in our new image
rect = CGRectInset(rect, dx, dy);
[self drawInRect:rect blendMode:kCGBlendModeNormal alpha:1.0];
UIImage *squareImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return squareImage;
}
- (UIImage *)cropAspectFit {
// Get a square that the image will fit into
CGFloat maxSize = MIN(self.size.height, self.size.width);
CGSize squareSize = CGSizeMake(maxSize, maxSize);
// Get our offset to center the image inside our new square frame
CGFloat dx = (maxSize - self.size.width) / 2.0f;
CGFloat dy = (maxSize - self.size.height) / 2.0f;
UIGraphicsBeginImageContext(squareSize);
CGRect rect = CGRectMake(0, 0, maxSize, maxSize);
// Adjust the rect to be centered in our new image
rect = CGRectInset(rect, dx, dy);
[self drawInRect:rect blendMode:kCGBlendModeNormal alpha:1.0];
UIImage *squareImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return squareImage;
}
#end
Swift solution
extension UIImage {
func cropAspectFill() -> UIImage {
let minSize = min(size.height, size.width)
let squareSize = CGSize(width: minSize, height: minSize)
// Get our offset to center the image inside our new square frame
let dx = (minSize - size.width) / 2.0
let dy = (minSize - size.height) / 2.0
UIGraphicsBeginImageContext(squareSize)
let rect = CGRect(x: 0, y: 0, width: minSize, height: minSize)
// Adjust the rect to be centered in our new image
let centeredRect = rect.insetBy(dx: dx, dy: dy)
draw(in: centeredRect, blendMode: .normal, alpha: 1.0)
let squareImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return squareImage!
}
func cropAspectFit() -> UIImage {
// Get a square that the image will fit into
let maxSize = min(size.height, size.width)
let squareSize = CGSize(width: maxSize, height: maxSize)
// Get our offset to center the image inside our new square frame
let dx = (maxSize - size.width) / 2.0
let dy = (maxSize - size.height) / 2.0
UIGraphicsBeginImageContext(squareSize)
let rect = CGRect(x: 0, y: 0, width: maxSize, height: maxSize)
// Adjust the rect to be centered in our new image
let centeredRect = rect.insetBy(dx: dx, dy: dy)
draw(in: centeredRect, blendMode: .normal, alpha: 1.0)
let squareImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return squareImage!
}
}
I want to crop images from the center with a specific width and height. I found this code in a SO issue but this method resize the image then it crop it. I want to only get my image cropped and not resized. I tried modifying this code but I can't get the result that I want.
//cropImage
func cropToBounds(image: UIImage, width: Double, height: Double) -> UIImage {
let contextImage: UIImage = UIImage(cgImage: image.cgImage!)
let contextSize: CGSize = contextImage.size
var posX: CGFloat = 0.0
var posY: CGFloat = 0.0
var cgwidth: CGFloat = CGFloat(width)
var cgheight: CGFloat = CGFloat(height)
// See what size is longer and create the center off of that
if contextSize.width > contextSize.height {
posX = ((contextSize.width - contextSize.height) / 2)
posY = 0
cgwidth = contextSize.height
cgheight = contextSize.height
} else {
posX = 0
posY = ((contextSize.height - contextSize.width) / 2)
cgwidth = contextSize.width
cgheight = contextSize.width
}
let rect: CGRect = CGRect(x: posX, y: posY, width: cgwidth, height: cgheight)
// Create bitmap image from context using the rect
let imageRef: CGImage = contextImage.cgImage!.cropping(to: rect)!
// Create a new image based on the imageRef and rotate back to the original orientation
let image: UIImage = UIImage(cgImage: imageRef, scale: image.scale, orientation: image.imageOrientation)
return image
}
How can I do that?
It should be:
func cropImage(toRect rect:CGRect) -> UIImage? {
var rect = rect
rect.origin.y = rect.origin.y * self.scale
rect.origin.x = rect.origin.x * self.scale
rect.size.width = rect.width * self.scale
rect.size.height = rect.height * self.scale
guard let imageRef = self.cgImage?.cropping(to: rect) else {
return nil
}
let croppedImage = UIImage(cgImage:imageRef)
return croppedImage
}
Make sure image will be cropped in the center.
Include largest crop zone possible given by the width and height.
extension UIImage {
func crop(width: CGFloat = 60, height: CGFloat = 60) -> UIImage {
let scale = min(self.size.width / width, self.size.height / height)
let x = self.size.width > self.size.height
? (self.size.width - width * scale) / 2
: 0
let y = self.size.width > self.size.height
? 0
: (self.size.height - height * scale) / 2
let cropZone = CGRect(x: x, y: y, width: width * scale, height: height * scale)
guard let image: CGImage = self.cgImage?.cropping(to: cropZone) else { return UIImage() }
return UIImage(cgImage: image)
}
I am using this to display image which ratio is different from my frame, so I center it and crop the sides.
extension UIImage {
func cropTo(size: CGSize) -> UIImage {
guard let cgimage = self.cgImage else { return self }
let contextImage: UIImage = UIImage(cgImage: cgimage)
var cropWidth: CGFloat = size.width
var cropHeight: CGFloat = size.height
if (self.size.height < size.height || self.size.width < size.width){
return self
}
let heightPercentage = self.size.height/size.height
let widthPercentage = self.size.width/size.width
if (heightPercentage < widthPercentage) {
cropHeight = size.height*heightPercentage
cropWidth = size.width*heightPercentage
} else {
cropHeight = size.height*widthPercentage
cropWidth = size.width*widthPercentage
}
let posX: CGFloat = (self.size.width - cropWidth)/2
let posY: CGFloat = (self.size.height - cropHeight)/2
let rect: CGRect = CGRect(x: posX, y: posY, width: cropWidth, height: cropHeight)
// Create bitmap image from context using the rect
let imageRef: CGImage = contextImage.cgImage!.cropping(to: rect)!
// Create a new image based on the imageRef and rotate back to the original orientation
let cropped: UIImage = UIImage(cgImage: imageRef, scale: self.scale, orientation: self.imageOrientation)
return cropped
}
}
I have a UIImageView which shows an UIImage.
The UIImage may change to other UIImage in different size, and the position and the size of the UIImage inside will change according according to it.
My Problem is that i'm trying add a view that will be at the end of the UIImage (which change all the time) and all I can get is the frame of the UIImageView (which stay full screen all the time).
How can i get the "frame" of current showing UIImage ?
Swift 4.2 & 5.0
func calculateRectOfImageInImageView(imageView: UIImageView) -> CGRect {
let imageViewSize = imageView.frame.size
let imgSize = imageView.image?.size
guard let imageSize = imgSize else {
return CGRect.zero
}
let scaleWidth = imageViewSize.width / imageSize.width
let scaleHeight = imageViewSize.height / imageSize.height
let aspect = fmin(scaleWidth, scaleHeight)
var imageRect = CGRect(x: 0, y: 0, width: imageSize.width * aspect, height: imageSize.height * aspect)
// Center image
imageRect.origin.x = (imageViewSize.width - imageRect.size.width) / 2
imageRect.origin.y = (imageViewSize.height - imageRect.size.height) / 2
// Add imageView offset
imageRect.origin.x += imageView.frame.origin.x
imageRect.origin.y += imageView.frame.origin.y
return imageRect
}
Swift 3.0
// MARK: - Create Rect
func calculateRectOfImageInImageView(imageView: UIImageView) -> CGRect {
let imageViewSize = imageView.frame.size
let imgSize = imageView.image?.size
guard let imageSize = imgSize, imgSize != nil else {
return CGRect.zero
}
let scaleWidth = imageViewSize.width / imageSize.width
let scaleHeight = imageViewSize.height / imageSize.height
let aspect = fmin(scaleWidth, scaleHeight)
var imageRect = CGRect(x: 0, y: 0, width: imageSize.width * aspect, height: imageSize.height * aspect)
// Center image
imageRect.origin.x = (imageViewSize.width - imageRect.size.width) / 2
imageRect.origin.y = (imageViewSize.height - imageRect.size.height) / 2
// Add imageView offset
imageRect.origin.x += imageView.frame.origin.x
imageRect.origin.y += imageView.frame.origin.y
return imageRect
}
For Swift < 3.0
Here is the above method in Swift. Again, assuming that contentMode is set to .ScaleAspectFit If there is no image on the given imageView CGRectZero will be returned.
func calculateRectOfImageInImageView(imageView: UIImageView) -> CGRect {
let imageViewSize = imageView.frame.size
let imgSize = imageView.image?.size
guard let imageSize = imgSize where imgSize != nil else {
return CGRectZero
}
let scaleWidth = imageViewSize.width / imageSize.width
let scaleHeight = imageViewSize.height / imageSize.height
let aspect = fmin(scaleWidth, scaleHeight)
var imageRect = CGRect(x: 0, y: 0, width: imageSize.width * aspect, height: imageSize.height * aspect)
// Center image
imageRect.origin.x = (imageViewSize.width - imageRect.size.width) / 2
imageRect.origin.y = (imageViewSize.height - imageRect.size.height) / 2
// Add imageView offset
imageRect.origin.x += imageView.frame.origin.x
imageRect.origin.y += imageView.frame.origin.y
return imageRect
}
The following will answer your question, assuming your UIImageView used UIViewContentModeAspectFit:
You have to regard the image sizing of the image inside UIImageView. This depends on how you set the contentMode. According your description, I assume you are using UIViewContentModeAspectFit. The resulting image will also be centered in the UIImageView so you also have to consider this for the calculation.
-(CGRect )calculateClientRectOfImageInUIImageView:(UIImageView *)imgView
{
CGSize imgViewSize=imgView.frame.size; // Size of UIImageView
CGSize imgSize=imgView.image.size; // Size of the image, currently displayed
// Calculate the aspect, assuming imgView.contentMode==UIViewContentModeScaleAspectFit
CGFloat scaleW = imgViewSize.width / imgSize.width;
CGFloat scaleH = imgViewSize.height / imgSize.height;
CGFloat aspect=fmin(scaleW, scaleH);
CGRect imageRect={ {0,0} , { imgSize.width*=aspect, imgSize.height*=aspect } };
// Note: the above is the same as :
// CGRect imageRect=CGRectMake(0,0,imgSize.width*=aspect,imgSize.height*=aspect) I just like this notation better
// Center image
imageRect.origin.x=(imgViewSize.width-imageRect.size.width)/2;
imageRect.origin.y=(imgViewSize.height-imageRect.size.height)/2;
// Add imageView offset
imageRect.origin.x+=imgView.frame.origin.x;
imageRect.origin.y+=imgView.frame.origin.y;
return(imageRect);
}
For a better illustration of the differences between the three content modes, see below:
I recommend using built in function AVMakeRectWithAspectRatio.
func AVMakeRectWithAspectRatioInsideRect(_ aspectRatio: CGSize, _ boundingRect: CGRect) -> CGRect
Parameters:
aspectRatio:
The width and height ratio (aspect ratio) you want to maintain.
boundingRect:
The bounding rectangle you want to fit into.
Return Value
Returns a scaled CGRect that maintains the aspect ratio specified by aspectRatio that fits within bounding Rect.
let boundingBox = AVMakeRectWithAspectRatioInsideRect(backgroundImage.size, frame)
Based on the wonderfully simple solution from Janusz, here's what I did:
let visibleRect = AVMakeRect(aspectRatio: CGSize(width: image.size.width, height: image.size.height), insideRect: self.frame)
if visibleRect.contains(point) {
// Do something great here...
}
Swift 3.0
I know its quite late but might help someone in future. Its very simple and inbuilt solution provided by iOS. Just need to:
import AVFoundation
let imageRect = AVMakeRect(aspectRatio: image.size, insideRect: self.imageView.bounds)