Crop UIImage keeping the same Aspect Ratio - ios

I am working on an app, where you can edit photos, these photos are displayed as scaleAspectFill. When I know want to save the photo the edited things are not in the right position. (The UIImageView name is ImageView).
This is why I tried to crop my image, but I don't really know which values I really should use, I tried many possibilities.
My Code Is:
func crop() -> UIImage {
let x:CGFloat = ImageView.frame.maxX
let y:CGFloat = ImageView.frame.maxY
let width:CGFloat = ImageView.frame.width
let height:CGFloat = ImageView.frame.height
let croppedCGImage = ImageView.image?.cgImage?.cropping(to: CGRect(x: x, y: y, width: width, height: height))
let croppedImage = UIImage(cgImage: croppedCGImage!)
return croppedImage
}
I tried it with maxX/maxY too this is why it is written there.
Update
So I edited the image and now I want to save it, but now is really small, so it would be nice if you would help
this is the first image, how it should be
https://i.stack.imgur.com/91RBX.jpg
and this is the result
https://i.stack.imgur.com/gpDpY.png

Related

Adding Text View to Image on iPhone X "squeezes" image on output

I currently having a block of code that is trying to add a text view on top of an image, with the ultimate goal to save down the new image with the overlaid text. Here is the code to do that:
class func addText(label: UITextView,imageSize: CGSize, image: UIImage) -> UIImage {
let scale = UIScreen.main.scale
UIGraphicsBeginImageContextWithOptions(CGSize(width: imageSize.width, height: imageSize.height), false, scale)
let currentView = UIView.init(frame: CGRect(x: 0, y: 0, width: imageSize.width, height: imageSize.height))
let currentImage = UIImageView.init(image: image)
currentImage.frame = CGRect(x: 0, y: 0, width: imageSize.width, height: imageSize.height)
currentView.addSubview(currentImage)
currentView.addSubview(label)
currentView.layer.render(in: UIGraphicsGetCurrentContext()!)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return img!
}
And it is called like below (The image is just a standard 1920x1080 image taken by the phone's camera):
self.imageToEdit.image = UIImage.addText(label: textView, imageSize: UIScreen.main.bounds.size, image: self.imageToEdit.image!)
This works great when I test when an iPhone 6s, but when I test on an iPhone X, it "squeezes" the sides of the image so faces and other features become skinnier on the image that is returned by addText.
I have a hunch it is due to the image being extended up through the notch of the iPhone X which is causing some type of scaling/aspect fill, but I'm not sure where to begin looking.
Does anyone know how to stop the "squeezing" from happening in iPhone X (I am also guessing this is happening in all the other iPhone models that have a notch)
Thanks.
Just figured it out!
I needed to included this line:
currentImage.contentMode = .scaleAspectFill
in my addText func.
Because I was returning a new UIImageView I needed to make sure it had the same content mode as the original view.

Crop image captured with camera session

I am trying to crop image captured with camera session with specific rect of interest. For find proportional crop rect I am using previewLayer.metadataOutputRectConverted method. But after cropping i got wrong ratio.
Debug example:
(lldb) po rectOfInterest.width / rectOfInterest.height
0.7941176470588235
(lldb) po image.size.width / image.size.height
0.75
(lldb) po outputRect.width / outputRect.height
0.9444444444444444
(lldb) po Double(cropped.width) / Double(cropped.height)
0.7080152671755725
As you can see, I am expecting that cropped image ratio will be ~0.79 as rectOfInterest which I am using for cropping.
Method:
private func makeImageCroppedToRectOfInterest(from image: UIImage) -> UIImage {
let previewLayer = cameraController.previewLayer
let rectOfInterest = layoutLayer.layoutRect
let outputRect = previewLayer.metadataOutputRectConverted(fromLayerRect: rectOfInterest)
guard let cgImage = image.cgImage else {
return image
}
let width = CGFloat(cgImage.width)
let height = CGFloat(cgImage.height)
let cropRect = CGRect(x: outputRect.origin.x * width,
y: outputRect.origin.y * height ,
width: outputRect.size.width * width,
height: outputRect.size.height * height)
guard let cropped = cgImage.cropping(to: cropRect) else {
return image
}
return UIImage(cgImage: cropped, scale: image.scale, orientation: image.imageOrientation)
}
Target rect:
I'm not an expert, but I think you misinterpreted the metadataOutputRectConverted method usage.
There is no way your code could return a cropped image with the same aspect ratio since you are multiplying (what I think are) virtually unrelated numbers one to each other.
You can try this method
previewLayer.layerRectConverted(fromMetadataOutputRect: CGRect(x: 0, y: 0, width: 1, height: 1))
To get an idea on what the actual calculations made from the metadataOututRectConverted are.
I think you could explain better what you want to achieve, and maybe provide a sample project (or at the very least some more context on what are the actual images/rects you are using) to help us debug, if you want to have more help on this.
Thanks to #Enricoza I understand how to fix my problem. Here is code:
private func makeImageCroppedToRectOfInterest(from image: UIImage) -> UIImage {
let previewLayer = cameraController.previewLayer
let rectOfInterest = layoutLayer.layoutRect
let metadataOutputRect = CGRect(x: 0, y: 0, width: 1, height: 1)
let outputRect = previewLayer.layerRectConverted(fromMetadataOutputRect: metadataOutputRect)
guard let cgImage = image.cgImage else {
return image
}
let width = image.size.width
let height = image.size.height
let factorX = width / outputRect.width
let factorY = height / outputRect.height
let factor = max(factorX, factorY)
let cropRect = CGRect(x: (rectOfInterest.origin.x - outputRect.origin.x) * factor,
y: (rectOfInterest.origin.y - outputRect.origin.y) * factor,
width: rectOfInterest.size.width * factor,
height: rectOfInterest.size.height * factor)
guard let cropped = cgImage.cropping(to: cropRect) else {
return image
}
return UIImage(cgImage: cropped, scale: image.scale, orientation: image.imageOrientation)
}

How to position UI elements on background image relative to the image

I had I previuosly question here How to make full screen background image inside ScrollView and keep aspect ratio and get very good aswer from "ekscrypto", thank you again.
I have buttons with text on the image (1, 2, 3, 4, 5 etc.) previously i used hard coded X and Y coordinates to position UI elemnts (buttons) on the background image, this solution worked on all iPhone devices, not on iPads.
I changed my code according to the solution I received from "ekscrypto", and now of course this solution not work on any device.
On the image there is a road, and I need to arrange these buttons on this road. How can I properly position this buttons relative to the image, regardless of the device and image scale?
P.S. Ekscrypto also provided solution for the UI element positioning, but I don't understand how it works.
Here's how I currently attempt to create the buttons:
let imageOne = UIImage(named: "level1") as UIImage?
let levelOne = UIButton(type: UIButtonType.system)
levelOne.frame = CGRect.init(x: 10, y: 10, width: 100, height: 45)
levelOne.setImage(imageOne, for: .normal)
scrollView.addSubview(levelOne)
But the iPhone and iPad button positions and sizes should be different. How can I have them placed properly relative to the image?
Thank you so much ekscrypto, and sorry for delay with answer. Your code is working, and solves my problem, but there is a small problems.
Had to change this line let button = UIButton(type: .system) to .custom, or instead of background image you get button that is filled with blue color.
Button with background image is too big, specially on iPhone 5, changed let backgroundDesignHeight: CGFloat = 330.0 to 730 to make it smaller
All buttons are in same place on iPhone and iPads, except «plus devices» there is a small offset to the bottom(down) button should be slightly higher
On some devices background image on button are little bit blurry, this happened after I changed backgroundDesignHeight to 730
You can solve this a few ways:
Compute the final x/y position manually based on current screen dimension and "design" dimension
Manually create aspect-ratio based constraints and let iOS compute the final position for you
Assuming that your device/app orientation is constant (always landscape or always portrait) and is always fullscreen, it may be easier to compute it by hand.
First, you will need a helper function to resize your image:
private func scaledImage(named imageName: String, scale: CGFloat) -> UIImage? {
guard let image = UIImage(named: imageName) else { return nil }
let targetSize = CGSize(width: image.size.width * scale, height: image.size.height * scale)
return resizeImage(image: image, targetSize: targetSize)
}
// Adapted from https://stackoverflow.com/questions/31314412/how-to-resize-image-in-swift
private func resizeImage(image: UIImage, targetSize: CGSize) -> UIImage? {
let size = image.size
let widthRatio = targetSize.width / size.width
let heightRatio = targetSize.height / size.height
// Figure out what our orientation is, and use that to form the rectangle
var newSize: CGSize
if(widthRatio > heightRatio) {
newSize = CGSize(width: size.width * heightRatio, height: size.height * heightRatio)
} else {
newSize = CGSize(width: size.width * widthRatio, height: size.height * widthRatio)
}
// This is the rect that we've calculated out and this is what is actually used below
let rect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)
// Actually do the resizing to the rect using the ImageContext stuff
UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0)
image.draw(in: rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
Next, we will create a function to compute the location of the button, and the size of the button based on the image size:
private func positionScaledButton(x designX: CGFloat, y designY: CGFloat, imageName: String) -> UIButton {
// First, compute our "designScale"
let screenHeight = UIScreen.main.bounds.size.height
let backgroundDesignHeight: CGFloat = 330.0 // ** see below
let designScale = screenHeight / backgroundDesignHeight
// Create button
let button = UIButton(type: .custom)
// Get image to use, and scale it as required
guard let image = scaledImage(named: imageName, scale: designScale) else { return button }
button.frame = CGRect(x: designX * designScale, y: designY * designScale, width: image.size.width, height: image.size.height)
button.setImage(image, for: .normal)
scrollView.addSubview(button)
return button
}
** The value "330.0" in the code above refers to the height of the background image in the scroller, from which the x/y coordinates of the button were measured.
Assuming button's top-left corner should be at x: 10, y: 10, for image "levelOne":
let levelOneButton = positionScaledButton(x: 10, y: 10, imageName: "imageOne")
// To do: addTarget event handler
Use relative, rather than absolute points.
So, rather than saying button 1 is at (30, 150) in points, use the fraction of the screen size instead so it's at (0.0369, 0.4) - then use those fractions to create your auto layout constraints.

Image in button doesn't load, i only get a blue square

I am trying to add a button to a screen and edit it programmatically, but most of the edits don't respond.
This is the button code:
#IBOutlet weak var buttonTest: UIButton!
let screen = UIScreen.mainScreen().bounds
let buttonImg = ResizeImage(UIImage(named: "Blokker")!, targetSize: CGSize(width: 50, height: 50))
buttonTest.frame = CGRect(x: 0, y: screen.height - 300, width: screen.width * 0.5, height: screen.width * 0.5)
buttonTest.setImage(buttonImg, forState: .Normal)
buttonTest.setTitle("Test", forState: .Normal)
buttonTest.imageView?.contentMode = .ScaleAspectFit
view.addSubview(buttonTest)
The ResizeImage function works, i've used it in other view controllers already. The code for this is quoted below. The buttonTest.frame method doesn't work, the button will always stay in the same place without edits to the size. The image doesn't load either, in the place where the image should be, I can only see a blue square.
Does anyone know how to solve this problem? Thanks in advance!
The ResizeImage func:
func ResizeImage(image: UIImage, targetSize: CGSize) -> UIImage {
let size = image.size
let widthRatio = targetSize.width / image.size.width
let heightRatio = targetSize.height / image.size.height
// Figure out what our orientation is, and use that to form the rectangle
var newSize: CGSize
if(widthRatio > heightRatio) {
newSize = CGSizeMake(size.width * heightRatio, size.height * heightRatio)
} else {
newSize = CGSizeMake(size.width * widthRatio, size.height * widthRatio)
}
// This is the rect that we've calculated out and this is what is actually used below
let rect = CGRectMake(0, 0, newSize.width, newSize.height)
// Actually do the resizing to the rect using the ImageContext stuff
UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0)
image.drawInRect(rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
The first part of your question I already answered in comment
Use imageView.imageRenderingMode = .AlwaysOriginal if you want the image to be the same as the source
and imageView.imageRenderingMode = .AlwaysTemplate if you want the image to be the same color as its container (images with no alpha channel will be a colored square)
For the frame problem, why are you the following line? Remove it.
view.addSubview(buttonTest)
Buttontest already has a superview as it's an outlet from storyboard/xib. Also, do not change frame rect if you're designing the button using storyboard/xib. You should learn how to use constraints for this.
Using FruitAddict's comment:
let buttonImg = ResizeImage(UIImage(named: "Blokker")!, targetSize: CGSize(width: 50, height: 50)).imageWithRenderingMode(.AlwaysOriginal)
solved the image problem, for people who experience the same problem!
The frame problem hasn't changed so far..
according to me please use SDWebImage ThirdParty library please follow below link:-
https://github.com/rs/SDWebImage
or you can also install through pod also and use this library....
i show you below code using swift language:-
let imageurl: NSURL? = NSURL(string: "")
imageview1.sd_setImageWithURL(url, placeholderImage:UIImage(named:"Ahmedabad_BRTS.jpg"))

Circle image crop when using UIImagePickerController Swift

I need to crop images circle when importing from photo library, just like in the stock contacts app. I found a few solutions but all of them were in Objective-C. I found them hard to translate. Does anyone know a useful swift library or so?
this works for me
profileImage.image = UIImageVar
profileImage.layer.borderWidth = 1
profileImage.layer.borderColor = UIColor.blackColor().CGColor
profileImage.layer.cornerRadius = profileImage.frame.height/2
profileImage.clipsToBounds = true
If you're seeking something like Core Graphics, you can take the following for references.
let ctx = UIGraphicsGetCurrentContext()
let image = UIImage(named: "YourImageName")!
// Oval Drawing
let width = image.size.width
let height = image.size.height
let ovalPath = UIBezierPath(ovalInRect: CGRectMake(0, 0, width, height))
CGContextSaveGState(ctx)
ovalPath.addClip()
image.drawInRect(CGRectMake(0, 0, width, height))
CGContextRestoreGState(ctx)

Resources