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

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.

Related

Draw UIImage with scale and translate

I am trying to draw a "zoomed-in" portion of UIImage and can't find a way to do that...
Say I have an image sized 1000x1000, and I zoom-in to the top left (x:0, y:0 width: 500, height: 500), and I want to draw that portion into another 1000x1000 image.
I am using UIGraphicsImageRenderer but UIImage draw method doesn't accept any source rect, only destination rect (and it draws the entire image).
I was able to achieve what I want by specifying a larger rect in the draw method, but that crashes when the zoom-in level is big (out-of-memory).
This is what I tried:
let srcImg: UIImage = {some UIImage sized 1000x1000}
let renderer = UIGraphicsImageRenderer(size: CGSize(width: 1000, height: 1000))
let scaled = renderer.image { ctx in
srcImg.draw(in: CGRect(origin: CGPoint.zero, size: CGSize(width: 2000, height: 2000)))
}
Basically I am trying to achieve something like the drawImage API of HTML5 canvas, which gets both src and dst rectangles...
Thanks.
Use the UIImage extension from this answer: https://stackoverflow.com/a/28513086/6257435
If I understand your question correctly -- you want to clip the top-left 500x500 pixels from a 1000x1000 image, and scale that up to 1000x1000 -- here is a simple example using that extension:
if let img1000x1000 = UIImage(named: "img1000x1000") {
if let topLeft500x500 = img1000x1000.cropped(to: CGRect(x: 0, y: 0, width: 500, height: 500)) {
if let new1000x1000 = topLeft500x500.filled(to: CGSize(width: 1000, height: 1000)) {
// do something with new 1000x1000 image
print()
}
}
}

UIImage convert to data and back changes size?

I create a picture programmatically, convert it to data and back and get different pictures.
let image1: UIImage = {
let size = CGSize(width: 50, height: 50)
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
UIGraphicsBeginImageContextWithOptions(size, false, 0)
UIColor.black.setFill()
UIRectFill(rect)
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}()
let data = UIImagePNGRepresentation(image1)!
let image2 = UIImage(data: data)!
print(image1.size) // (50.0, 50.0)
print(image2.size) // (100.0, 100.0)
Please explain what happens and how to solve the problem. Thank you!
The "culprit" line:
UIGraphicsBeginImageContextWithOptions(size, false, 0)
Looking at the doc of UIGraphicsBeginImageContextWithOptions(), for the last parameter (scale)
scale The scale factor to apply to the bitmap. If you specify a value of 0.0, the scale factor is set to the scale factor of the
device’s main screen.
If your device is Retina (*2), then the scale factor will be 2.

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.

Frame and Image in specific aspect Ratio

I created empty gray UIImage, using below code
let size = CGSize(width: 212, height: 332)
UIGraphicsBeginImageContextWithOptions(size, true, 0)
UIColor.gray.setFill()
UIRectFill(CGRect(x: 0, y: 0, width: size.width, height: size.height))
let backgroundImage2: UIImage? = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
It shows output as
Now I need to put UIImage on specific area in this UIImage. as shown in below Image. Say top, left, right should be 30 pixels, and bottom more than that, say 200 pixels. maintaining inner image aspect ratio.
Use two image views (either UIImageView or GLKView), making the "image" a subview of the "gray background" view. After positioning the "image" correctly, merge the two images into one.
Here's an extension to UIView that I use:
extension UIView {
public func createImage() -> UIImage {
UIGraphicsBeginImageContextWithOptions(
CGSize(width: self.frame.width, height: self.frame.height), true, 1)
self.layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}

Image blurred slightly when drawing in drawRect (Swift)

I am building a circle crop function in Swift. I pretty much have everything working however when I save the cropped photo it is slightly blurry:
Not sure if it is visible here or not but on my iPhone I can see a difference, slight blurring. I'm not zooming in more than the actual size of the image. I am using a UIScrollView with max zoom factor set to 1.0. The crop code is:
func didTapOk() {
let scale = scrollView.zoomScale
let newSize = CGSize(width: image!.size.width*scale, height: image!.size.height*scale)
let offset = scrollView.contentOffset
UIGraphicsBeginImageContext(CGSize(width: 240, height: 240))
let circlePath = UIBezierPath(ovalInRect: CGRect(x: 0, y: 0, width: 240, height: 240))
circlePath.addClip()
var sharpRect = CGRect(x: -offset.x, y: -offset.y, width: newSize.width, height: newSize.height)
sharpRect = CGRectIntegral(sharpRect)
image?.drawInRect(sharpRect)
let finalImage = UIGraphicsGetImageFromCurrentImageContext()
UIImageWriteToSavedPhotosAlbum(finalImage, nil, nil, nil)
}
Here I am trying to use CGRectIntegral to improve the result but it doesn't seem to make any difference. Any pointers on what I could do to improve this?
What's happening is your crop is blurring because it hasn't accounted for the scale of your screen (whether you're using a retina display etc).
Use UIGraphicsBeginImageContextWithOptions(CGSize(width: 240, height: 240), true, 0) to account for the retina screen. Check here for more details on how to use this function.
I suspect that, although the accepted answer above is working for you, it is only working due to the screen scale being the same as the image scale.
As you are not rendering your image from the screen itself (you're rendering from a UIImage to another UIImage), the screen scale should be irrelevant.
You should instead be passing in the scale of the image when you create your context, like so:
UIGraphicsBeginImageContextWithOptions(CGSize(width: 240, height: 240), false, image.scale)
Here's a way in Swift 3 to disable any interpolation.
UIGraphicsBeginImageContextWithOptions(imageSize, false, UIScreen.main.scale)
let context = UIGraphicsGetCurrentContext()
context?.interpolationQuality = CGInterpolationQuality.none
Additionally, since this is often for pixel drawing purposes, you may want to add:
// Ensure the image is not drawn upside down
context?.saveGState()
context?.translateBy(x: 0.0, y: maxHeight)
context?.scaleBy(x: 1.0, y: -1.0)
// Make the drawing
context?.draw(yourImage, in: CGRect(x: 0, y: 0, width: maxWidth, height: maxHeight))
// Restore the GState of the context
context?.restoreGState()

Resources