I have try to crop UIImage to given rect but its not crop regular size
the rect size is come from scrollView
here is the reference code:
guard let view = imageCrop else { print("Error"); return }
let normalizedX = view.contentOffset.x / view.contentSize.width
let normalizedY = view.contentOffset.y / view.contentSize.height
let normalizedWidth = view.frame.width / view.contentSize.width
let normalizedHeight = view.frame.height / view.contentSize.height
let cropRect = CGRect(x: normalizedX, y: normalizedY,
width: normalizedWidth, height: normalizedHeight)
cropImage(image: imageCrop.image, cropRect: cropRect)
func cropImage(imageToCrop:UIImage, toRect rect:CGRect) -> UIImage{
let imageRef:CGImage = imageToCrop.cgImage!.cropping(to: rect)!
let cropped:UIImage = UIImage(cgImage:imageRef)
return cropped
}
Solved.
var cropArea:CGRect{
get{
let factor = imageCrop.image!.size.width/view.frame.width
let scale = 1/imageCrop.zoomScale
let imageFrame = imageCrop.frame
let x = (imageCrop.contentOffset.x + imageCrop.frame.origin.x - imageFrame.origin.x) * scale * factor
let y = (imageCrop.contentOffset.y + imageCrop.frame.origin.y - imageFrame.origin.y) * scale * factor
let width = imageCrop.frame.size.width * scale * factor
let height = imageCrop.frame.size.height * scale * factor
return CGRect(x: x, y: y, width: width, height: height)
}
}
Related
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.
I am trying to change the NSLayout constraint constant by getting the imageview content position as CGRECT. But the content position i am getting by using contentRect is different.
Here is the extension i am using to get the content position.
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)
}
override func viewDidLoad(){
super.viewDidLoad()
imageVw.image = imagePassed
imageVw.backgroundColor = colorPassed
setStyle(toTextField: topText)
setStyle(toTextField: bottomText)
let ht = imageVw.contentRect.minY + imageVw.contentSize.height
topConstraint.constant = self.view.frame.height - ht
self.view.layoutIfNeeded()
print("contentRect value \(imageVw.contentRect)")
print("point \(imageVw.contentRect.minY)")
print("image height \(imageVw.contentSize)")
}
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 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
}
}
How can I increase the size of a CGRect by a certain percent value? Should I use some form of CGRectInset to do it?
Example:
Assume I have a CGRect: {10, 10, 110, 110}
I want to increase its size (retaining the same center point) by 20% to:
{0, 0, 120, 120}
You can use CGRectInset if you like:
double pct = 0.2;
CGRect newRect = CGRectInset(oldRect, -CGRectGetWidth(oldRect)*pct/2, -CGRectGetHeight(oldRect)*pct/2);
To decrease the size, remove the -s.
Side note: A CGRect that is 20% bigger than {10, 10, 100, 100} is {0, 0, 120, 120}.
Edit: If the intention is to increase by area, then this'll do it (even for rectangles that aren't square):
CGFloat width = CGRectGetWidth(oldRect);
CGFloat height = CGRectGetHeight(oldRect);
double pct = 1.2; // 20% increase
double newWidth = sqrt(width * width * pct);
double newHeight = sqrt(height * height * pct);
CGRect newRect = CGRectInset(oldRect, (width-newWidth)/2, (height-newHeight)/2);
In Swift:
func increaseRect(rect: CGRect, byPercentage percentage: CGFloat) -> CGRect {
let startWidth = CGRectGetWidth(rect)
let startHeight = CGRectGetHeight(rect)
let adjustmentWidth = (startWidth * percentage) / 2.0
let adjustmentHeight = (startHeight * percentage) / 2.0
return CGRectInset(rect, -adjustmentWidth, -adjustmentHeight)
}
let rect = CGRectMake(0, 0, 10, 10)
let adjusted = increaseRect(rect, byPercentage: 0.1)
// -0.5, -0.5, 11, 11
In ObjC:
- (CGRect)increaseRect:(CGRect)rect byPercentage:(CGFloat)percentage
{
CGFloat startWidth = CGRectGetWidth(rect);
CGFloat startHeight = CGRectGetHeight(rect);
CGFloat adjustmentWidth = (startWidth * percentage) / 2.0;
CGFloat adjustmentHeight = (startHeight * percentage) / 2.0;
return CGRectInset(rect, -adjustmentWidth, -adjustmentHeight);
}
CGRect rect = CGRectMake(0,0,10,10);
CGRect adjusted = [self increaseRect:rect byPercentage:0.1];
// -0.5, -0.5, 11, 11
Sure, using CGRectInset works:
CGRect someRect = CGRectMake(10, 10, 100, 100);
someRect = CGRectInset(someRect, someRect.size.width * -0.2, someRect.size.height * -0.2);
Swift 4 extension inspired by several of the answers here with simplified calculations:
extension CGRect {
func scaleLinear(amount: Double) -> CGRect {
guard amount != 1.0, amount > 0.0 else { return self }
let ratio = ((1.0 - amount) / 2.0).cgFloat
return insetBy(dx: width * ratio, dy: height * ratio)
}
func scaleArea(amount: Double) -> CGRect {
return scaleLinear(percent: sqrt(amount))
}
func scaleLinear(percent: Double) -> CGRect {
return scaleLinear(amount: percent / 100)
}
func scaleArea(percent: Double) -> CGRect {
return scaleArea(amount: percent / 100)
}
}
Usage is simply:
rect.scaleLinear(percent: 120.0) OR (amount: 1.2)
rect.scaleArea(percent: 120.0) OR (amount: 1.2)
If you are interested in trying my testing methods:
/// Testing
extension CGRect {
var area: CGFloat { return width * height }
var center: CGPoint { return CGPoint(x: origin.x + width/2, y: origin.y + height/2)
}
func compare(_ r: CGRect) {
let centered = center.x == r.center.x && center.y == r.center.y
print("linear = \(r.width / width), area = \(r.area / area) centered \(centered)")
}
static func ScaleTest() {
let rect = CGRect(x: 17, y: 24, width: 200, height: 100)
let percent = 122.6
rect.compare(rect.scaleLinear(percent: percent))
rect.compare(rect.scaleArea(percent: percent))
}
}
I'm using CGRect > insetBy in my Swift code
https://developer.apple.com/documentation/coregraphics/cgrect/1454218-insetby
With this, your percent value will be the scaleX as my example.
let dx = rectWidth*scaleX
let dy = rectHeight*scaleX
let rectangle = CGRect(x: rectX,
y: rectY,
width: rectWidth,
height: rectHeight).insetBy(dx: -dx, dy: -dy)
use positive value to scale down
use negative value to scale up
Using Swift you can retain the center point (relative to the source Rect) and increase/decrease the size as follows using an extension:
extension CGRect {
func centerAndAdjustPercentage(percentage p: CGFloat) -> CGRect {
let x = self.origin.x
let y = self.origin.y
let w = self.width
let h = self.height
let newW = w * p
let newH = h * p
let newX = (w - newW) / 2
let newY = (h - newH) / 2
return CGRect(x: newX, y: newY, width: newW, height: newH)
}
}
let newRect = oldRect.centerAndAdjustPercentage(percentage: 0.25)
let scale = percent / 100
let newRect = oldRect.applying(CGAffineTransform(scaleX: scale, y: scale))
Beware that this approach also will change x and y of rectangle.