Cropping an image from the top in Swift - swift

I am trying to crop this image, which is a SKSpriteNode:
I am trying to crop this image from the top, so that I maintain the bottom semi circle of this shape. For instance, it'd be cropped to this:
So I use these two methods to accomplish this task:
func recalculateScore() {
currentScore -= decreaseRate
let image = UIImage(cgImage: (vial.texture?.cgImage())!)
vial.texture = SKTexture(image: cropBottomImage(image: image))
}
func cropBottomImage(image: UIImage) -> UIImage {
let height = CGFloat(image.size.height / 3)
let rect = CGRect(x: 0, y: image.size.height - height, width: image.size.width, height: height)
return cropImage(image: image, toRect: rect)
}
func cropImage(image:UIImage, toRect rect:CGRect) -> UIImage {
let imageRef:CGImage = image.cgImage!.cropping(to: rect)!
let croppedImage:UIImage = UIImage(cgImage:imageRef)
return croppedImage
}
However, this leads to this result:
It is as if it was being compressed. I think my issue might be in this line:
let rect = CGRect(x: 0, y: image.size.height - height, width: image.size.width, height: height)
Does the CGRect coordinate of (0,0) lie within the top most left corner? I am a bit confused on what the x and y parameters for the CGRect mean?

Resize your sprite, what is happening is the cropped texture is stretching to fill the sprite, and since you only crop vertically, it will only stretch vertically
func recalculateScore() {
currentScore -= decreaseRate
let image = UIImage(cgImage: (vial.texture?.cgImage())!)
vial.texture = SKTexture(image: cropBottomImage(image: image))
vial.size = vial.texture.size
}

Related

How can I use CGRect() dynamically?

I have the following scene example where I can crop an image based on the selection (red square).
That square has dynamic Height and Width - base on this fact I want to use the selected Height and Width to crop what is inside of the Red square.
The function that I am using for cropping is from Apple developer and looks like this:
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
}
Now. I want to use the given Height and Width to crop that selection.
let croppedImage = cropImage(image!, toRect: CGRect(x:?? , y:?? , width: ??, height: ??), viewWidth: ??, viewHeight: ??)
What should I fill in these parameters in order to crop the image based on the above dynamic selection?
Ok, since you just have info of width and height of the cropping shape. You'll need to calculate the x and y by yourself.
First, let's consider these information:
// let's pretend this is a sample of size that your crop tool provides to you
let cropSize = CGSize(width: 120, height: 260)
Next, you'll need to obtain the display size (width and height) of your image. Display size here is the frame's size of your image, not the size of the image itself.
// again, lets pretend it's just a frame size of your image
let imageSize = CGSize(width: 320, height: 480)
With this info, you can obtain the x and y necessary to compose a CGRect and then, provide to a cropping function you desire.
let x = (imageSize.width - cropSize.width) / 2
let y = (imageSize.height - cropSize.height) / 2
So now, you can create a rectangle to crop your image like this:
let cropRect = CGRect(x: x, y: y, width: cropSize.width, height: cropSize.height)
With cropRect you can use on both cropping or cropImage functions mentioned in your question.
Ok, let's assume that your image is in imageView, wich is located somewhere in your screen. The rect is a variable where your selected frame (related to the imageView.frame) is stored. So the result is:
let croppedImage = cropImage(image!, toRect: rect, viewWidth: imageView.width, viewHeight: imageView.height)
I've used the info from all of your answers and especially #matt's comment and this is the final solution.
Using the input values that my red square returned, I've adapted the original Crop function to this one:
func cropImage(_ inputImage: UIImage, width: Double, height: Double) -> UIImage?
{
let imsize = inputImage.size
let ivsize = UIScreen.main.bounds.size
var scale : CGFloat = ivsize.width / imsize.width
if imsize.height * scale < ivsize.height {
scale = ivsize.height / imsize.height
}
let croppedImsize = CGSize(width:height/scale, height:width/scale)
let croppedImrect =
CGRect(origin: CGPoint(x: (imsize.width-croppedImsize.width)/2.0,
y: (imsize.height-croppedImsize.height)/2.4),
size: croppedImsize)
let r = UIGraphicsImageRenderer(size:croppedImsize)
let croppedIm = r.image { _ in
inputImage.draw(at: CGPoint(x:-croppedImrect.origin.x, y:-croppedImrect.origin.y))
}
return croppedIm
}

Cropping CGRect from AVCapturePhotoOutput (resizeAspectFill)

I have found the following problem and unfortunatly other posts have not helped me to a working solution.
I have a simple app that shows the camera preview (AVCaptureVideoPreviewLayer) where the video gravity has been set to resizeAspectFill (videoGravity = .resizeAspectFill).
From my understanding this only streches the image in the width to make to fill the screen.
On my preview layer I also have applied a CGRect as a mask with fixed x, y, width and height.
Now once I take a photo i'm trying to crop that exact rectangle out of the image. For my understanding i'm supposed to use some kind of math to convert the CGRect to the same aspect ratio as the image that I get from the AVCapturePhotoOutput method but it never seems to crop correctly in the width.
private func cropImage(image: UIImage) {
let rect = CGRect(x: 25, y: 150, width: 325, height: 230)
let scale = CGAffineTransform(scaleX: 1/self.view.frame.width, y: 1/self.view.frame.height)
let flip = CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: -1)
let bounds = rect.applying(scale).applying(flip)
let topLeft = bounds.topLeft.scaled(to: image.size)
let topRight = bounds.topRight.scaled(to: image.size)
let bottomLeft = bounds.bottomLeft.scaled(to: image.size)
let bottomRight = bounds.bottomRight.scaled(to: image.size)
var ciImage = CIImage(image: image.forceSameOrientation())!
ciImage = ciImage.applyingFilter("CIPerspectiveCorrection", parameters: [
"inputTopLeft": CIVector(cgPoint: bottomLeft),
"inputTopRight": CIVector(cgPoint: bottomRight),
"inputBottomLeft": CIVector(cgPoint: topLeft),
"inputBottomRight": CIVector(cgPoint: topRight)
])
let context = CIContext()
let cgImage = context.createCGImage(ciImage, from: ciImage.extent)
let output = UIImage(cgImage: cgImage!)
let vc = PreviewViewController()
vc.imageView.image = output
self.present(vc, animated: true, completion: nil)
}
So again, basically it does crop at the correct height but its only the width that does not seem to go well.
Image example of what I would want to capture.
https://imgur.com/a/8GryEgX
As you can see the bounding box in the top left stops after the "Q" button.
Result:
https://imgur.com/FwKRWxK
As you can see in this image, it does crop correctly in the height however if we take a look at the top left it also includes half of the button to the left of the "Q" (Tab button)
Any help towards the solution would be appreciated!
I managed to solve the issue with this code.
private func cropToPreviewLayer(from originalImage: UIImage, toSizeOf rect: CGRect) -> UIImage? {
guard let cgImage = originalImage.cgImage else { return nil }
// This previewLayer is the AVCaptureVideoPreviewLayer which the resizeAspectFill and videoOrientation portrait has been set.
let outputRect = previewLayer.metadataOutputRectConverted(fromLayerRect: rect)
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))
if let croppedCGImage = cgImage.cropping(to: cropRect) {
return UIImage(cgImage: croppedCGImage, scale: 1.0, orientation: originalImage.imageOrientation)
}
return nil
}
usage of the piece of code for my case:
let rect = CGRect(x: 25, y: 150, width: 325, height: 230)
let croppedImage = self.cropToPreviewLayer(from: image, toSizeOf: rect)
self.imageView.image = croppedImage

Add chess board to transparent parts of an UIImageView

I have an UIImageView, which shows an UIImage, having content mode as Aspect Fit.
I need to indicate transparent areas of the images with a chessboard.
How to obtain squares of the same size, no matter the size of the image?
extension UIImage {
func withChessboard() -> UIImage? {
var image: UIImage?
let l = 4
UIGraphicsBeginImageContextWithOptions(size, false, scale)
let imageRect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
if let context = UIGraphicsGetCurrentContext() {
context.setFillColor(UIColor.lightGray.cgColor)
for row in 0 ..< Int(size.width) {
for col in 0 ..< Int(size.height) {
if (row + col) % 2 == 0 {
context.fill(CGRect(x: col * l, y: row * l, width: l, height: l))
}
}
}
draw(in: imageRect, blendMode: .normal, alpha: 1.0)
image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
return nil
}
}
Now I have this ( first image being smaller )
and this:
EDIT, using the solution suggested by Trent, I have obtained this. I need the tiles to cover just the size of the image, not the whole imageview.
Looks like this existing post has a clean answer to your solution.
Basically show you how to create a pattern with UIColor and set that as the backgroundColor of the UIImageView.
Let me know if that helps! If not, I'd like to hear why.

Swift: CropView for UIImageView

I'm looking for a tutorial to tell me how to create a frame to crop the photo, as the picture below
thanks
As a first step please look into implementing a UIView that resizes using the corners/handles. This post should help
Once we have a resizable UIView, then using UIView.frame's origin and size to crop the image, like so:
extension UIImage {
func crop(size: CGSize, offset: CGPoint, scale: CGFloat = 1.0) -> UIImage? {
let rect = CGRect(x: offset.x * scale, y: offset.y * scale, width: size.width * scale, height: size.height * scale)
if let cropped = self.cgImage?.cropping(to: rect) {
return UIImage(cgImage: cropped)
}
return nil
}
}
let croppedImage = image.crop(size: resizableView.frame.size, offset: resizableView.frame.origin)

How to take a snapshot from UIImage of a UIImageView that is scaleAspectFit mode?

I have an image in a UIImageView:
imageView.contentMode = .scaleAspectFit
imageView.backgroundColor = .red
because of .scaleAspectFit the image view has some red borders and thats OK:
User can added some UIView like label or images over the imageView.
In final step I used the following code to save edited image and user can share it or save it to photo library:
private func generateImage() -> UIImage? {
var finalImage: UIImage?
UIGraphicsBeginImageContextWithOptions(CGSize(width: imageView.frame.size.width, height: imageView.frame.size.height), true, 0)
imageView.drawHierarchy(in: CGRect(x: 0, y: 0, width: imageView.frame.size.width, height: imageView.frame.size.height), afterScreenUpdates: true)
finalImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
The problem is that the finalImage still has the red borders from imageView.
You can get CGRect of the UIImage displayed in the UIImageView in AspectFit content mode. Please create extension of UIImageView like this,
extension UIImageView {
var contentClippingRect: 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)
}
}
You can now use imageView.contentClippingRect to read how read the position and size of the image inside.
You have to do minor changes in your method, call your function with appropriate bounds as contentClippingRect.
Let me know in case of any queries.
UPDATE
Please try this UIImageView+Extension, this might help you. It is in Objective-C code, convert it in Swift.
You can try this as well,
let image = #imageLiteral(resourceName: "Cat03")
let x: CGRect = AVMakeRect(aspectRatio: image.size, insideRect: imageView1.frame)
print(x)
Above code gives you size perfectly.