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 ?
Related
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).
I have a camera which provides me UIImage with resolution of 1920x1080, I have implemented a system where I can add UITextViews as subviews and my goal is to add them to the original UIImage and export a final Image of the resolution of 1920x1080.
So the procedure I have been thinking about is this:
I add upperView over view.frame which is responsible of drawing all the textViews present on screen.
Then I want to add this view over the original UIImage scaling it proportionally
This is the code I implemented in order to do it:
func passingAndDrawingTextViews(textViews : [UITextView], viewPassed : UIView, inImage : UIImage) -> UIImage{
let scale = UIScreen.main.scale
UIGraphicsBeginImageContextWithOptions(viewPassed.frame.size, false, scale)
viewPassed.draw(CGRect(x: 0, y: 0, width: viewPassed.frame.width, height: viewPassed.frame.height))
//adding each textView
textViews.forEach { (textView) in
let textColor = textView.textColor
let textFont = UIFont(name: textView.font!.fontName, size: textView.font!.pointSize)
let textAlignment = textView.textAlignment
//alignment of the text
let paragraphStyle: NSMutableParagraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = textAlignment
let textFontAttributes = [
NSAttributedString.Key.font: textFont,
NSAttributedString.Key.foregroundColor: textColor,
NSAttributedString.Key.paragraphStyle : paragraphStyle,
]
let rect = CGRect(x: textView.frame.minX + textView.textContainer.lineFragmentPadding, y: textView.frame.minY + textView.textContainerInset.top, width: textView.frame.width, height: textView.frame.height)
textView.text.draw(in: rect, withAttributes: textFontAttributes as [NSAttributedString.Key : Any])
}
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
UIGraphicsBeginImageContext(inImage.size)
let rect = CGRect(x: 0, y: 0, width: inImage.size.width, height: inImage.size.height)
inImage.draw(in: CGRect(x: 0, y: 0, width: inImage.size.width, height: inImage.size.height))
newImage!.draw(in: rect)
let result = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return result!
}
In the function I respectively pass the list of textViews, the upperView, and the originalImage.
But the problem is that the textView I get are stretched on the x axis. I don't know why this happens.
What I get:
What I want
the rect you are passing in this line :
newImage!.draw(in: rect)
is actually the size of screen it self so the image will strech to fill this rect.
so make this :
let rect = CGRect(x: 0, y: 0, width: inImage.size.width, height: inImage.size.height)
the size of the image you are passing in.
I'm trying to place an icon (in form of an image) next to a text in a UILabel. The icons are imported into the assets in al three sizes and are not blurry at all when I simply place them in a normal UIImageView.
However, within the NSTextAttachment they suddenly become extremely blurry and are too big, as well.
I already tried several things on my own and also tried nearly every snippet I could find online - nothing helps. This is what I'm left over with:
func updateWinnableCoins(coins: Int){
let attachImg = NSTextAttachment()
attachImg.image = resizeImage(image: #imageLiteral(resourceName: "geld"), targetSize: CGSize(width: 17.0, height: 17.0))
attachImg.setImageHeight(height: 17.0)
let imageOffsetY:CGFloat = -3.0;
attachImg.bounds = CGRect(x: 0, y: imageOffsetY, width: attachImg.image!.size.width, height: attachImg.image!.size.height)
let attchStr = NSAttributedString(attachment: attachImg)
let completeText = NSMutableAttributedString(string: "")
let tempText = NSMutableAttributedString(string: "You can win " + String(coins) + " ")
completeText.append(tempText)
completeText.append(attchStr)
self.lblWinnableCoins.textAlignment = .left;
self.lblWinnableCoins.attributedText = completeText;
}
func resizeImage(image: UIImage, targetSize: CGSize) -> (UIImage) {
let newRect = CGRect(x: 0, y: 0, width: targetSize.width, height: targetSize.height).integral
UIGraphicsBeginImageContextWithOptions(targetSize, false, 0)
let context = UIGraphicsGetCurrentContext()
// Set the quality level to use when rescaling
context!.interpolationQuality = CGInterpolationQuality.default
let flipVertical = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: targetSize.height)
context!.concatenate(flipVertical)
// Draw into the context; this scales the image
context?.draw(image.cgImage!, in: CGRect(x: 0.0,y: 0.0, width: newRect.width, height: newRect.height))
let newImageRef = context!.makeImage()! as CGImage
let newImage = UIImage(cgImage: newImageRef)
// Get the resized image from the context and a UIImage
UIGraphicsEndImageContext()
return newImage
}
extension NSTextAttachment {
func setImageHeight(height: CGFloat) {
guard let image = image else { return }
let ratio = image.size.width / image.size.height
bounds = CGRect(x: bounds.origin.x, y: bounds.origin.y, width: ratio * height, height: height)
}
}
And this is how it looks:
The font size of the UILabel is 17, so I set the text attachment to be 17 big, too. When I set it to 9, it fits, but it's still very blurry.
What can I do about that?
I'm trying to Resize Image by preserving aspect ratio.But there is this white space surrounding the resized image.
extension NSImage {
func resizeTo(width: CGFloat, height: CGFloat) -> NSImage {
let ratioX = width / size.width
let ratioY = height / size.height
let ratio = ratioX < ratioY ? ratioX : ratioY
let newHeight = size.height * ratio
let newWidth = size.width * ratio
let canvasSize = CGSize(width: width, height: CGFloat(ceil(width/size.width * size.height)))
let img = NSImage(size: canvasSize)
img.lockFocus()
let context = NSGraphicsContext.current()
context?.imageInterpolation = .high
draw(in: NSRect(origin: .zero, size: NSSize(width: newWidth,height: newHeight)), from: NSRect(origin: .zero, size: size) , operation: .copy, fraction: 1)
img.unlockFocus()
return img
}
}
What I'm I doing wrong?
First you are picking the smallest ratio from ratioX and ratioY, but later you create canvas using ratioX (of size width, height * ratioX). I'd say you need to create canvas using newWidth and newHeight.
let canvasSize = CGSize(width: newWidth, height: newHeight)
Additionally, be aware that this script will resize in both directions, i.e. will increase the size of small images.
In my app I would like to use UITableViewRowAction with image instead of title text. I set background image using:
let edit = UITableViewRowAction(style: .Normal, title: "Edit") { action, index in
self.indexPath = indexPath
self.performSegueWithIdentifier("toEdit", sender: self)
}
edit.backgroundColor = UIColor(patternImage: UIImage(named: "edit")!)
However image appears many times.
How can I fix this to have only one image in row?
The problem is that the image used as pattern won't fit the space, It will be repeated in order to fill it.
One option to have a non-repeated image is to
use a UITableViewCell with fixed height
use image that fits that height
I have wrote a subclass of UITableViewRowAction to help you calculating the length of the title and you just pass the size of rowAction and the image.
class CustomRowAction: UITableViewRowAction {
init(size: CGSize, image: UIImage, bgColor: UIColor) {
super.init()
// calculate actual size & set title with spaces
let defaultTextPadding: CGFloat = 15
let defaultAttributes = [ NSFontAttributeName: UIFont.systemFont(ofSize: 18)] // system default rowAction text font
let oneSpaceWidth = NSString(string: " ").size(attributes: defaultAttributes).width
let titleWidth = size.width - defaultTextPadding * 2
let numOfSpace = Int(ceil(titleWidth / oneSpaceWidth))
let placeHolder = String(repeating: " ", count: numOfSpace)
let newWidth = (placeHolder as NSString).size(attributes: defaultAttributes).width + defaultTextPadding * 2
let newSize = CGSize(width: newWidth, height: size.height)
title = placeHolder
// set background with pattern image
UIGraphicsBeginImageContextWithOptions(newSize, false, UIScreen.main.nativeScale)
let context = UIGraphicsGetCurrentContext()!
context.setFillColor(bgColor.cgColor)
context.fill(CGRect(origin: .zero, size: newSize))
let originX = (newWidth - image.size.width) / 2
let originY = (size.height - image.size.height) / 2
image.draw(in: CGRect(x: originX, y: originY, width: image.size.width, height: image.size.height))
let patternImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
backgroundColor = UIColor(patternImage: patternImage)
}
}
You can see my project: CustomSwipeCell for more detail.