Draw UIImage in Rectangle with background color if it doesn't fit in a rectangle? - swift

I'm new to this topic so I want to ask how I can achieve this following behavior: I would like to create a image with a specific size. If the original Image doesn't fit in a square I would like to fill the sides with a color, so I want to draw that image in a rectangle with a color and want to store it in a new UIImage variable.
To resize a Image I found something like this. But how can I fit the image in a Rectangle with a specific color and create a new Image that can be stored?
UIGraphicsBeginImageContextWithOptions(size, false, self.scale)
defer { UIGraphicsEndImageContext() }
draw(in: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
return UIGraphicsGetImageFromCurrentImageContext()

One possible way would be to create an extension method to UIImage as you started in your sample code.
Then create a new image of the desired size and fill the background color with UIRectFill.
The next step would be to calculate the new size so that the image content is scaled to fit into the image by taking the aspect ratio into account:
let scale = min(size.width / originalSize.width, size.height / originalSize.height)
let newSize = CGSize(width: originalSize.width * scale, height: originalSize.height * scale)
let origin = CGPoint(x: (size.width - newSize.width) / 2, y: (size.height - newSize.height) / 2)
Then you basically just need to draw the image into the rectangle that results from the origin and the actual size of the image inside the background area.
Completely, the extension method could then look something like this:
func image(size: CGSize, background: UIColor) -> UIImage {
UIGraphicsBeginImageContextWithOptions(size, false, self.scale)
defer { UIGraphicsEndImageContext() }
background.setFill()
let completeRect = CGRect(origin: .zero, size: size)
UIRectFill(completeRect)
let originalSize = self.size
let scale = min(size.width / originalSize.width, size.height / originalSize.height)
let newSize = CGSize(width: originalSize.width * scale, height: originalSize.height * scale)
let origin = CGPoint(x: (size.width - newSize.width) / 2, y: (size.height - newSize.height) / 2)
let imageRect = CGRect(origin: origin, size: newSize)
draw(in: imageRect, blendMode: .normal, alpha: 1.0)
return UIGraphicsGetImageFromCurrentImageContext() ?? UIImage()
}
Self-Contained Complete Example
Finally a self-contained complete example for testing:
import UIKit
class ViewController: UIViewController {
private let image1 = UIImage(named: "country")
private let image2 = UIImage(named: "regensburg")
override func viewDidLoad() {
super.viewDidLoad()
let imageView1 = createImageView()
imageView1.image = image1?.image(size: CGSize(width: 200, height: 200), background: .blue)
let imageView2 = createImageView()
imageView2.image = image2?.image(size: CGSize(width: 200, height: 200), background: .blue)
NSLayoutConstraint.activate([
imageView1.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 24),
imageView1.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 24),
imageView1.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -24),
imageView2.topAnchor.constraint(equalTo: imageView1.bottomAnchor, constant: 24),
imageView2.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 24),
imageView2.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -24),
imageView2.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -24),
imageView2.widthAnchor.constraint(equalTo: imageView1.widthAnchor),
imageView2.heightAnchor.constraint(equalTo: imageView1.heightAnchor)
])
}
private func createImageView() -> UIImageView {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
imageView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(imageView)
return imageView
}
}
extension UIImage {
func image(size: CGSize, background: UIColor) -> UIImage {
UIGraphicsBeginImageContextWithOptions(size, false, self.scale)
defer { UIGraphicsEndImageContext() }
background.setFill()
let completeRect = CGRect(origin: .zero, size: size)
UIRectFill(completeRect)
let originalSize = self.size
let scale = min(size.width / originalSize.width, size.height / originalSize.height)
let newSize = CGSize(width: originalSize.width * scale, height: originalSize.height * scale)
let origin = CGPoint(x: (size.width - newSize.width) / 2, y: (size.height - newSize.height) / 2)
let imageRect = CGRect(origin: origin, size: newSize)
draw(in: imageRect, blendMode: .normal, alpha: 1.0)
return UIGraphicsGetImageFromCurrentImageContext() ?? UIImage()
}
}
The output of the above code is then:
The specified actual image size is square. Accordingly, there are corresponding vertical or horizontal stripes at the edges of the image in the selected background color (blue).

Related

Unable to change image size in my draw function

In my meme application, I want the user to be able to put text on image. To make this possible I am using UIGraphics. In my scene, I have an image drawn on the background and a text showing on the foreground.
Here is my code for generating the so said image :
func GenerateMemeFrom(image: UIImage, topText: NSString, bottomText: NSString) -> UIImage {
let scale: CGFloat = 4
let size = CGSize(width: UIScreen.main.bounds.size.width * scale, height: UIScreen.main.bounds.size.height * scale)
UIGraphicsBeginImageContext(size)
let textAttributes: [NSAttributedString.Key : Any] = [
NSAttributedString.Key.font: UIFont(name: "HelveticaNeue-CondensedBlack", size: 200)!,
NSAttributedString.Key.foregroundColor: UIColor.white,
]
let ratio = UIScreen.main.bounds.size.width / image.size.width
let imageSize = CGSize(width: UIScreen.main.bounds.size.width * scale, height: image.size.height * ratio * scale)
image.draw(in: CGRect(origin: CGPoint.zero, size: imageSize))
let point = CGPoint(x: UIScreen.main.bounds.size.width * scale / 2, y: 100)
let rect = CGRect(origin: point, size: image.size)
topText.draw(in: rect, withAttributes: textAttributes)
let generatedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return generatedImage!
}
Here is what I have on my screen : Image
But whether I put scale equal to 2 or 4, it changes nothing in the application. I want to be able to change size based on screen width. Furthermore, I want the image to be centered. Is there any way to do so ?

How can one position navigation bar item in Swift?

I have a navigation bar that currently only has a back button. I am trying to add an image button on the right side of the navigation bar but the image I am using is larger than the navigation bar and ends up covering the back button and gets positioned strangely.
This is the code:
let mapBtn = UIButton(type: .system)
mapBtn.setImage(#imageLiteral(resourceName: "map-1"), for: .normal)
mapBtn.frame = CGRect(x: 0,y: 0,width: 5,height: 5)
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: mapBtn)
This is an image of what is happening:
https://imgur.com/a/kzcwbGK
Is there anyway to add a constraint to the mapBtn to make it stick to the right side as it should be?
Try to resize your image
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 = 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)
if let newImage = UIGraphicsGetImageFromCurrentImageContext(){
UIGraphicsEndImageContext()
return newImage
}else{
return nil
}
}
let mapBtn = UIButton(type: .system)
let img = UIImage(named: "map-1")
let resizedImage = resizeImage(image: img, targetSize: CGSize(width: 5.0, height: 5.0)
mapBtn.frame = CGRect(x: 0,y: 0,width: 5,height: 5)
mapBtn.setImage(resizedImage, for: .normal)
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: mapBtn)
set this.
mapBtn.imageView?.contentMode = .scaleAspectFit

Resize UIImage Keep Image Quality in Swift

I have created an image from UIView using UIGraphicsGetCurrentContext. It's work fine but when I resized that image to larger size its be blurred with bad quality. Can it possibility to keep quality when resize? I have tried many ways but not work.
- code image:
func image(with view: UIView) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)
defer { UIGraphicsEndImageContext() }
if let context = UIGraphicsGetCurrentContext() {
view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
context.setAllowsAntialiasing(true)
context.setShouldAntialias(true)
let image = UIGraphicsGetImageFromCurrentImageContext()
return image
}
return nil
}
code I have used to resize, this extension of UIImage
func resizedImage(newSize: CGSize) -> UIImage {
// Guard newSize is different
guard self.size != newSize else { return self }
let aspect_ratio = self.size.width / self.size.height
var image_w = newSize.height * aspect_ratio
let finalSize = CGSize(width: image_w, height: newSize.height)
UIGraphicsBeginImageContextWithOptions(finalSize, false, 0.0)
self.draw(in: CGRect(x: 0, y: 0, width: (CGFloat)(finalSize.width), height: (CGFloat)(newSize.height)))
let newImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return newImage
}
and the example image:image
Thanks
Try this:
extension UIImage {
func resizeImage(targetSize: CGSize) -> UIImage {
let size = self.size
let widthRatio = targetSize.width / size.width
let heightRatio = targetSize.height / size.height
let newSize = widthRatio > heightRatio ? CGSize(width: size.width * heightRatio, height: size.height * heightRatio) : CGSize(width: size.width * widthRatio, height: size.height * widthRatio)
let rect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)
UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0)
self.draw(in: rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
}

How to apply an inverse text mask in Swift?

I am trying programmatically create a layer with transparent text. Everything I try doesn't seem to work. My end goal is to create an inner shadow on text.
Instead of a circle as in the code below I want text.
let view = UIView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
view.backgroundColor = .white
// MASK
let blackSquare = UIView(frame: CGRect(x: 100, y: 100, width: 200, height: 200))
blackSquare.backgroundColor = .black
view.addSubview(blackSquare)
let maskLayer = CAShapeLayer()
// Create a path with the rectangle in it.
let path = CGMutablePath()
path.addArc(center: CGPoint(x: 100, y: 100), radius: 50, startAngle: 0.0, endAngle: 2.0 * .pi, clockwise: false)
path.addRect(CGRect(x: 0, y: 0, width: blackSquare.frame.width, height: blackSquare.frame.height))
maskLayer.backgroundColor = UIColor.black.cgColor
maskLayer.path = path;
maskLayer.fillRule = kCAFillRuleEvenOdd
blackSquare.layer.mask = maskLayer
maskLayer.masksToBounds = false
maskLayer.shadowRadius = 4
maskLayer.shadowOpacity = 0.5
maskLayer.shadowOffset = CGSize(width: 5, height: 5)
view.addSubview(blackSquare)
I'm also able to use text as a mask but I'm unable to invert the mask. Any help is appreciated.
EDIT:
I figured it out based on Rob's answer as suggested by Josh. Here's my playground code.
import Foundation
import UIKit
import PlaygroundSupport
// view
let view = UIView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
view.backgroundColor = .black
// button
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
button.setTitle("120", for: .normal)
button.setTitleColor(.white, for: .normal)
button.titleLabel?.font = UIFont(name: "AvenirNextCondensed-UltraLight", size: 200)
view.addSubview(button)
addInnerShadow(button: button)
func addInnerShadow(button: UIButton) {
// text
let text = button.titleLabel!.text!
// get context
UIGraphicsBeginImageContextWithOptions(button.bounds.size, true, 0)
let context = UIGraphicsGetCurrentContext()
context?.scaleBy(x: 1, y: -1)
context?.translateBy(x: 0, y: -button.bounds.size.height)
let font = button.titleLabel!.font!
// draw the text
let attributes = [
NSAttributedStringKey.font: font,
NSAttributedStringKey.foregroundColor: UIColor.white
]
let size = text.size(withAttributes: attributes)
let point = CGPoint(x: (button.bounds.size.width - size.width) / 2.0, y: (button.bounds.size.height - size.height) / 2.0)
text.draw(at: point, withAttributes: attributes)
// capture the image and end context
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
// create image mask
let cgimage = image?.cgImage!
let bytesPerRow = cgimage?.bytesPerRow
let dataProvider = cgimage?.dataProvider!
let bitsPerPixel = cgimage?.bitsPerPixel
let width = cgimage?.width
let height = cgimage?.height
let bitsPerComponent = cgimage?.bitsPerComponent
let mask = CGImage(maskWidth: width!, height: height!, bitsPerComponent: bitsPerComponent!, bitsPerPixel: bitsPerPixel!, bytesPerRow: bytesPerRow!, provider: dataProvider!, decode: nil, shouldInterpolate: false)
// create background
UIGraphicsBeginImageContextWithOptions(button.bounds.size, false, 0)
UIGraphicsGetCurrentContext()!.clip(to: button.bounds, mask: mask!)
view.backgroundColor!.setFill()
UIBezierPath(rect: button.bounds).fill()
let background = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
let backgroundView = UIImageView(image: background)
// add shadows
backgroundView.layer.shadowOffset = CGSize(width: 2, height: 2)
backgroundView.layer.shadowRadius = 2
backgroundView.layer.shadowOpacity = 0.75
button.addSubview(backgroundView)
}
PlaygroundPage.current.liveView = view
Whilst not exactly the same, please refer to the answer provided here by Rob who answered a similar question:
How do I style a button to have transparent text?
This should get you started at the very least...
Stumbled on a possible solution that I've updated to proper syntax:
func mask(withRect rect: CGRect, inverse: Bool = false) {
let path = UIBezierPath(rect: rect)
let maskLayer = CAShapeLayer()
if inverse {
path.append(UIBezierPath(rect: self.view.bounds))
maskLayer.fillRule = kCAFillRuleEvenOdd
}
maskLayer.path = path.cgPath
self.view.layer.mask = maskLayer
}
You'll obviously need to pick parts out to see if it works for you.

How to make UItabbar image rounded in swift programmatically

I want to create something like image as below
could you help me any code how to make it rounded
thanks for your help!
I think you need to
(Optional) Resize the image if image is bigger than standard bar button image size)
Make the image round
before using it as UItabBarItem image. Below is an extension for UIImage with these two functions:
extension UIImage{
var roundedImage: UIImage {
let rect = CGRect(origin:CGPoint(x: 0, y: 0), size: self.size)
UIGraphicsBeginImageContextWithOptions(self.size, false, 1)
UIBezierPath(
roundedRect: rect,
cornerRadius: self.size.height
).addClip()
self.draw(in: rect)
return UIGraphicsGetImageFromCurrentImageContext()!
}
func resizedImage(newWidth: CGFloat) -> UIImage {
let scale = newWidth / self.size.width
let newHeight = self.size.height * scale
UIGraphicsBeginImageContext(CGSize(width: newWidth, height: newHeight))
self.draw(in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
}
Example usage:
let barImage: UIImage = UIImage(named: "avatar_copy.png")!.resizedImage(newWidth: 40).roundedImage.withRenderingMode(.alwaysOriginal)
let roundTabBar = UITabBarItem(title: nil, image: barImage.withRenderingMode(.alwaysOriginal), selectedImage: barImage)
self.bottomTabbar.items = [roundTabBar]
I changed and added a new function to #firstinq answer. And this way works for me.
extension UIImage{
var roundMyImage: UIImage {
let rect = CGRect(origin:CGPoint(x: 0, y: 0), size: self.size)
UIGraphicsBeginImageContextWithOptions(self.size, false, 1)
UIBezierPath(
roundedRect: rect,
cornerRadius: self.size.height
).addClip()
self.draw(in: rect)
return UIGraphicsGetImageFromCurrentImageContext()!
}
func resizeMyImage(newWidth: CGFloat) -> UIImage {
let scale = newWidth / self.size.width
let newHeight = self.size.height * scale
UIGraphicsBeginImageContext(CGSize(width: newWidth, height: newHeight))
self.draw(in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
func squareMyImage() -> UIImage {
UIGraphicsBeginImageContext(CGSize(width: self.size.width, height: self.size.width))
self.draw(in: CGRect(x: 0, y: 0, width: self.size.width, height: self.size.width))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
}
In TabBarController I changed it to:
let barImage: UIImage = UIImage(named: "landingpage_image2")!.squareMyImage().resizeMyImage(newWidth: 40).roundMyImage.withRenderingMode(.alwaysOriginal)
self.tabBar.items?[1].image = barImage
let profileImg : UIImageView = {
let iv = UIImageView()
iv.translatesAutoresizingMaskIntoConstraints = false
iv.contentMode = .scaleAspectFill
iv.image = #imageLiteral(resourceName: "user")
iv.clipsToBounds = true
iv.layer.cornerRadius = 25
return iv
}()
// add constraint
override func viewDidLoad() {
super.viewDidLoad()
profileImg.leftAnchor.constraint(equalTo: view.leftAnchor, constant : 8).isActive = true
profileImg.topAnchor.constraint(equalTo: view.topAnchor , constant : 8).isActive = true
profileImg.widthAnchor.constraint(equalToConstant: 50).isActive = true
profileImg.heightAnchor.constraint(equalToConstant: 50).isActive = true
}