Get origin of image after aspect fit [duplicate] - ios

This question already has answers here:
How to know the image size after applying aspect fit for the image in an UIImageView
(20 answers)
Closed 6 years ago.
I want to get the CGRect value of the image after it is being "aspect fitted" onto the screen. All the solutions I found online only gives the CGSize. However, I want the origin so that I can draw a canvas on top of that image to perform drawing only on top of the image rather than the whole imageView.
Thanks,

let rect = CGRectIntegral(AVMakeRectWithAspectRatioInsideRect(imageView.image!.size, imageView.bounds))

Try to execute below code
var img=UIImageView(frame: CGRectMake(50, 50, 100, 100))
var imageOrigin=img.frame.origin //This will give you image x and y position
print(img.frame.origin) // this will print (50.0, 50.0)

Related

Removing statusbar from screenshot on iOS

Im trying to remove the top part of an image by cropping, but the result is unexpected.
The code used:
extension UIImage {
class func removeStatusbarFromScreenshot(_ screenshot:UIImage) -> UIImage {
let statusBarHeight = 44.0
let newHeight = screenshot.size.height - statusBarHeight
let newSize = CGSize(width: screenshot.size.width, height: newHeight)
let newOrigin = CGPoint(x: 0, y: statusBarHeight)
let imageRef:CGImage = screenshot.cgImage!.cropping(to: CGRect(origin: newOrigin, size: newSize))!
let cropped:UIImage = UIImage(cgImage:imageRef)
return cropped
}
}
My logic is that I need to make the image smaller in heigh by 44px and move the origin y by 44px, but it ends up only creating an image much smaller of the top left corner.
The only way that I get it to work as expected is by multiplying the width by 2 and height by 2.5 in newSize, but that also double the size of the image produced..
Which anyways doesnt make much sense.. can someone help make it work without using magic values?
There are two main problems with what you're doing:
A UIImage has a scale (usually tied to resolution of your device's screen), but a CGImage does not.
Different devices have different "status bar" heights. In general, what you want to cut off from the top is not the status bar but the safe area. The top of the safe area is where your content starts.
Because of this:
You are wrong to talk about 44 px. There are no pixels here. Pixels are physical atomic illuminations on your screen. In code, there are points. Points are independent of the scale (and the scale is the multiplier between points and pixels).
You are wrong to talk about the number 44 itself as if it were hard-coded. You should get the top of the safe area instead.
By crossing into the CGImage world without taking scale into account, you lose the scale information, because CGImage knows nothing of scale.
By crossing back into the UIImage world without taking scale into account, you end up with a UIImage with a resolution of 1, which may not be the resolution of the original UIImage.
The simplest solution is not to do any of what you are doing. First, get the height of the safe area; call it h. Then just draw the snapshot image into a graphics image context that is the same scale as your image (which, if you play your cards right, it will be automatically), but is h points shorter than the height of your image — and draw it with its y origin at -h, thus cutting off the safe area. Extract the resulting image and you're all set.
Example! This code comes a view controller. First, I'll take a screenshot of my own device's current screen (this view controller's view) as my app runs:
let renderer = UIGraphicsImageRenderer(size: view.bounds.size)
let screenshot = renderer.image { context in
view.layer.render(in: context.cgContext)
}
Now, I'll cut the safe area off the top of that screenshot:
let h = view.safeAreaInsets.top
let size = screenshot.size
let r = UIGraphicsImageRenderer(
size: .init(width: size.width, height: size.height - h)
)
let result = r.image { _ in
screenshot.draw(at: .init(x: 0, y: -h))
}
Experimentation will confirm that this works perfectly on every device, regardless of whether it has a bezel and regardless of its screen resolution: the top of the resulting image, result, is the top of your actual content.

swift get image in aspect fill from original image [duplicate]

This question already has answers here:
How to crop a UIImageView to a new UIImage in 'aspect fill' mode?
(2 answers)
Closed 4 years ago.
The problem I am facing is that the image taken from the camera is larger then the one shown in the live view. I have the camera view setup as Aspect Fill.
So the image that I get from the camera is about 4000x3000 and the view that shows the live feed from the camera is 375x800 (fullscreen iPhoneX size) so how do I transform/cut out part of the image from the image gotten from the camera to be the same as the one shown in the live view, so I can further manipulate the image (draw over it).
As far as I understand the Aspect Fill property clips the image that cannon't be shown in the view. But that clip does not happen on X = 0 and y = 0 it happens somewhere in the middle of the image. So how do i get that X and Y on the original image so that i can crop out exactly that part out.
I hope I explained well enough.
EDIT:
To give more context and some code snipets to make it easier to understand the issue.
Setting up my camera with the .resizeAspectFill gravity.
cameraPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
cameraPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
cameraPreviewLayer?.connection?.videoOrientation = AVCaptureVideoOrientation.portrait
cameraPreviewLayer?.frame = self.captureView.frame
self.captureView.layer.addSublayer(cameraPreviewLayer!)
which is displayed in the live view (captureView) that has the size of
375x818 (width: 375 and height: 818).
Then I get the image from that camera on button click and the size of that image is:
3024x4032 (width: 3024 and height: 4032)
So what i want to do is crop the image from the camera to be the same as the one in the live view (captureView) that is set to AspectFill type.
As you already state, content mode option Aspect fill tries to fill up the live view and you are also right that it crops some rectangle from center (cropping top-bottom or left-right depending upon the image size and the image view size)
For generic solution there are two possible case
The image needed to be cropped along the height to fit the image view (proportional drawing height is smaller)
The image needed to be cropped along the width to fit the image view (proportional drawing width is smaller)
Considering your size notation is 4000x3000 (height = 4000, width = 3000 a portrait image) and your drawing canvas size is 375X800 (height = 375, width = 800), then your cropping would be height wise while setting the content mode Aspect Fill.
So cropping will be done from X=0 but the Y would be somewhat positive. So lets calculate the Y
let propotionalHeight = 4000 / 3000 * 800
let allowedHeight = 375
let topBottomCroppedHeight = proportionalHeight - allowedHeight
let croppedYPosition = topBottomCroppedHeight / 2
So here you got your Y value. and the height would be the height of the canvas / live view where you are rendering. Please replace these values with your variables.
If you are interested in how all the contentMode works can dive into here. All the contentMode supported by UIImageView is simulated here.
Happy coding.
UPDATE
one thing i forgot to mention that, this calculated croppedYPosition is for smaller proportion image. If you want to use this value for the original 4000X3000 image you have to scale this up for the original value as following
let originalYPosition = (croppedYPosition / 375) * 4000
Use originalYPosition to crop from the original image of size 4000X3000.

iOS - Swift - Clickable Regions on a UIImage

I am working on a project where I have to plot points in specific regions on an image that represents the human body. In Interface Builder, I have set up a container UIView, which takes up most of the vertical center of the main view. In that container view, I placed a UIImageView and set the graphic in IB. The graphic is much larger than both the UIImageView and the container UIView, more specifically, it’s taller. The ContentMode of the UIImageView is set to AspectFit because I want the image to not show as bigger than the container.
The code creates several CGRect instances which are regions where user taps mean something. When the user taps on the container view, code is used to determine if the point is within one of the regions and if it is, a dot is drawn in the center of that region.
The problem is that when I run the app on certain simulators, the region rectangles are not in the right place on the image. For example, when I run the app on an iPhone X, the rectangle region that is in place for the head looks fine. When I run the app on an iPhone XR, the rectangle region is off to the left of the head.
I am using coordinates to define the region rectangles that are based on where, for example, the human head is in the image. I feel like this is not the right way to do this since AspectFit for the ContentMode of the image is most likely causing the image to be scaled to maintain aspect.
Bottom line is that I want a rectangle to be in the right place and size no matter how the image scales. No sure if how I am doing it makes sense, so hope that some suggestions come in that offer a better way to do this.
Update 1: The UIImageView is pinned to the surrounding UIView, so it's width and height are as big as the container. Since the image is skinnier than the UIImageView, the image appears centered in it. In the attached images, the purple background is the UIImageView showing the topmost UIView's background color.
Update 2: I checked the scale for both width and height and found they are different. The width scale factor is 1.36565656565 and the height scale factor is 2.104. I tried the formulas given with both scale factors given by Sweeper and no luck.
You just need to do some maths.
On the original image, identify the region the user can tap. Note down its x, y, w, h, relative to the image.
Figure out how much the image shrank in the image view. Since you said the image is taller than the image view, the image underwent a scale factor of imageViewHeight / imageHeight. We'll now refer to this as scaleFactor.
The region's Y coordinate must have also gone down by scaleFactor, so you multiply regionY by scaleFactor to get newY.
The region's width and height will do the same thing, so multiply them by scaleFactor and get newWidth and newHeight.
The X coordinate of the region, relative to the image view, is a bit tricky. You need to account for the amount of empty space that the image view has created by scaling down the image. This emptySpace is calculated by (imageViewWidth - newWidth) / 2. Then to calculate the new region's X coordinate relative to the image view, you do emptySpace + X * scaleFactor.
Now the rect (newX, newY, newWidth, newHeight) is the region relative to the image view that the user can tap!
I made this code for a rectangle in the position exactly the same on every device.
To make the check if it's a x phone I do in the viewDidLoad and set the constrains to what I want and added tab bar size.
#IBOutlet weak var tabMenu: NSLayoutConstraint!
#IBOutlet weak var topConstraint: NSLayoutConstraint!
#IBOutlet weak var bottomConstraint: NSLayoutConstraint!
#IBOutlet weak var backgroundImg: UIImageView!
#IBOutlet weak var rectangleImg: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Check if it's a iPhone X
if #available(iOS 11, *) {
let safeArea = UIApplication.shared.delegate?.window??.safeAreaInsets
// If its a x phone i use safe instead of superView
guard let safe = safeArea else { return }
if safe.bottom > 0 {
topConstraint.constant = safe.top
bottomConstraint.constant = safe.bottom + tabMenu.constant
}
}
}
override func viewDidAppear(_ animated: Bool) {
// Here I set the rectangle to 36% of the width and height (change to what you want)
rectangleImg.frame = CGRect(x: 0, y: 0, width: backgroundImg.frame.width * 0.36,
height: backgroundImg.frame.height * 0.36)
// Last I put the rectangle in the center of background image
rectangleImg.center.x = backgroundImg.center.x
rectangleImg.center.y = backgroundImg.center.y
}
Hope this code could help!
The root of my problem is that I had the UIImageView's four sides pinned to its container view. When the width of the device changed, it correctly scaled the image, but it caused the graphic to be "stretched". This caused the width of the head, for example, to increase. I have solved this for now by locking the width of the image so the coordinates I come up with from the original image remain intact no matter what the device.
For those reading this, it may not sound like much of a solution, but I have to go with this since I have a pretty aggressive deadline. I have tested it on multiple device simulators and it works. I may have to revisit this in the future, but for now, it is working.
I also used this question's answer to rework the code: create a clickable body diagram

Swift: Size vs bounds vs frame [duplicate]

This question already has answers here:
Cocoa: What's the difference between the frame and the bounds?
(12 answers)
Closed 5 years ago.
I'm making a simple game in Swift, and I was trying to find a good way to get the center of the game scene. At the moment I'm doing this:
CGPoint(x: size.width / 2, y: size.height / 2)
Seems simple enough. However, when checking, I found these four things also exist as part of the GameScene class:
self.frame
self.size
self.view.bounds
self.view.frame
I'm quite confused. Which one am I supposed to use? They all look the same to me. What's the difference?
The frame is the location in the views superview, so if it's a 100x100 square at 100 pixels from the left and the top its {100, 100, 100 100}.
The bounds is the rect, but ignoring the position in a superview. A bound-rects origin will always be 0,0. So the view in the example above would be {0,0,100,100}
Size is is just the same as bounds.size, {100,100}

Getting actual view size in Swift / IOS

Even after reading several posts in frame vs view I still cannot get this working. I open Xcode (7.3), create a new game project for IOS. On the default scene file, right after addChild, I add the following code:
print(self.view!.bounds.width)
print(self.view!.bounds.height)
print(self.frame.size.width)
print(self.frame.size.height)
print(UIScreen.mainScreen().bounds.size.width)
print(UIScreen.mainScreen().bounds.size.height)
I get following results when I run it for iPhone 6s:
667.0
375.0
1024.0
768.0
667.0
375.0
I can guess that first two numbers are Retina Pixel size at x2. I am trying to understand why frame size reports 1024x768 ?
Then I add following code to resize a simple background image to fill the screen:
self.anchorPoint = CGPointMake(0.5,0.5)
let theTexture = SKTexture(imageNamed: "intro_screen_phone")
let theSizeFromBounds = CGSizeMake(self.view!.bounds.width, self.view!.bounds.height)
let theImage = SKSpriteNode(texture: theTexture, color: SKColor.clearColor(), size: theSizeFromBounds)
I get an image smaller than the screen size. Image is displayed even smaller if I choose landscape mode.
I tried multiplying bounds width/height with two, hoping to get actual screen size but then the image gets too big. I also tried frame size which makes the image slightly bigger than the screen.
Main reason for my confusion, besides lack of knowledge is the fact that I've seen this exact example on a lesson working perfectly. Either I am missing something obvious or ?
The frame is given as 1024x768 because it is defined in points, not pixels.
If you want your scene to be the same size as your screen, before the scene is presented in your GameViewController, before:
skView.presentScene(scene)
use this:
scene.size = self.view.frame.size
which will make the scene the exact size of the screen.
Then you could easily make an image fill the scene like so:
func addBackground() {
let bgTexture = SKTexture(imageNamed: "NAME")
let bgSprite = SKSpriteNode(texture: bgTexture, color: SKColor.clearColor(), size: scene.size)
bgSprite.anchorPoint = CGPoint(x: 0, y: 0)
bgSprite.position = self.frame.origin
self.addChild(bgSprite)
}
Also, you may want to read up on the difference between a view's bounds and it's frame.

Resources