I am making an application that automatically enters a number when a photo is selected using UIKit and Swift.
The picture above is the most perfect situation and result.
The problem arises in a situation like the picture below.
The problem arises when working with photos of different resolutions. The text is large in some photos and small in others.
The problem I was thinking of is that the font size seems to be different when giving pictures with different resolutions because the font size is given as a fixed value.
// add text in photo
func textToImage(drawText text: String, inImage image: UIImage, fontSize: CGFloat, atPoint point: CGPoint) -> UIImage {
let textStrokeWidth: Double = -4.0
let textStrokeColor: UIColor = UIColor.black
let textFillColor: UIColor = UIColor.white
let textFont = UIFont(name: "Helvetica Bold", size: fontSize)!
UIGraphicsBeginImageContextWithOptions(image.size, false, 1)
let textFontAttributes = [
NSAttributedString.Key.strokeColor: textStrokeColor,
NSAttributedString.Key.strokeWidth : textStrokeWidth,
NSAttributedString.Key.font: textFont,
NSAttributedString.Key.foregroundColor: textFillColor,
] as [NSAttributedString.Key : Any]
image.draw(in: CGRect(origin: CGPoint.zero, size: image.size))
let rect = CGRect(origin: point, size: image.size)
text.draw(in: rect, withAttributes: textFontAttributes)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
How can I find the scale factor that corresponds to all resolutions and calculate the font size of the same ratio even if different photos are input?
You don't necessarily have to create text this way. I would like to know more about how to solve this problem.
CodePudding user response:
try to get the dynamic font size based on the image width and image height.
let fontSize = (image.size.width/image.size.height) * 50
CodePudding user response:
One approach -- with a few drawbacks -- but might work for you.
The general idea...
- define a "base" font - for example, your "Helvetica Bold" at
150
points - get the size of the string using that font
- calculate an aspect-fit rectangle for that size to fit in the image size
- use the difference in resulting size to "scale" the base font point size
Here are the steps:
// left/right and top/bottom min space
let padding: CGFloat = 20.0
// maximum rect for the text
let maxRect: CGRect = CGRect(origin: .zero, size: theImage.size).insetBy(dx: padding, dy: padding)
// get size of theString (single-line) using our baseFont
let baseSZ: CGSize = baseFont.sizeOfString(string: theString, constrainedToWidth: .greatestFiniteMagnitude)
// aspect-ratio rect that fits inside inset rect
let fitRect: CGRect = AVMakeRect(aspectRatio: baseSZ, insideRect: maxRect)
// "scaled" font size
let newPointSize: CGFloat = fitRect.height / baseSZ.height * baseFont.pointSize
// create a new font at calculated point size
guard let newFont: UIFont = UIFont(name: baseFont.fontName, size: newPointSize) else { return }
let newIMG: UIImage = drawTextOnImage(theString, withFont: newFont, inRect: fitRect, textColor: .yellow, onImage: theImage)
So, using this complete code for a sample app:
class ShareVC: UIViewController {
let tfImage = UITextField()
let tfText = UITextField()
let tfWidth = UITextField()
let tfHeight = UITextField()
let imgView = UIImageView()
var baseFont: UIFont!
override func viewDidLoad() {
super.viewDidLoad()
// base font - let's use 100-pointSize
guard let tf = UIFont(name: "Helvetica Bold", size: 100.0) else { return }
baseFont = tf
[tfImage, tfText, tfWidth, tfHeight].forEach { v in
v.borderStyle = .roundedRect
v.textAlignment = .center
v.autocapitalizationType = .none
v.autocorrectionType = .no
}
imgView.contentMode = .scaleAspectFit
imgView.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
imgView.clipsToBounds = true
// horizontal stack view for width/height fields and button
let hStack = UIStackView()
hStack.spacing = 8
hStack.distribution = .fillEqually
var label = UILabel()
label.textAlignment = .right
label.text = "width:"
hStack.addArrangedSubview(label)
hStack.addArrangedSubview(tfWidth)
label = UILabel()
label.textAlignment = .right
label.text = "height:"
hStack.addArrangedSubview(label)
hStack.addArrangedSubview(tfHeight)
let btn = UIButton()
btn.setTitle("Show", for: [])
btn.setTitleColor(.white, for: .normal)
btn.setTitleColor(.lightGray, for: .highlighted)
btn.backgroundColor = .systemRed
btn.layer.cornerRadius = 8
hStack.addArrangedSubview(btn)
let stack = UIStackView()
stack.axis = .vertical
stack.spacing = 4
label = UILabel()
label.text = "Image Name:"
label.font = .systemFont(ofSize: 14.0, weight: .light)
stack.addArrangedSubview(label)
stack.addArrangedSubview(tfImage)
label = UILabel()
label.text = "Text to render:"
label.font = .systemFont(ofSize: 14.0, weight: .light)
stack.addArrangedSubview(label)
stack.addArrangedSubview(tfText)
stack.addArrangedSubview(hStack)
stack.addArrangedSubview(imgView)
stack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stack)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
stack.topAnchor.constraint(equalTo: g.topAnchor, constant: 8.0),
stack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
stack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
imgView.heightAnchor.constraint(equalTo: imgView.widthAnchor, multiplier: 2.0 / 3.0),
])
// assign button target/action
btn.addTarget(self, action: #selector(showImage(_:)), for: .touchUpInside)
// add a tap gesture to the image view so we can
// toggle its .contentMode
let t = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
imgView.addGestureRecognizer(t)
imgView.isUserInteractionEnabled = true
// some starting text in the text fields
tfText.text = "Text : Number 1"
tfWidth.text = "300"
tfHeight.text = "300"
//tfImage.text = "bkg640x360"
}
@objc func handleTap(_ sender: UITapGestureRecognizer) {
// toggle image view .contentMode between
// .scaleAspectFit and .center
imgView.contentMode = imgView.contentMode == .scaleAspectFit ? .center : .scaleAspectFit
}
@objc func showImage(_ sender: UIButton) {
// make sure we entered a string to render
guard let theString = tfText.text, !theString.isEmpty else { return }
var theImage: UIImage!
// if we entered an image name, and it can be loaded
if let imgName = tfImage.text, !imgName.isEmpty,
let realImage = UIImage(named: imgName) {
// use the loaded image
theImage = realImage
} else {
// let's generate a solid-color image at the entered size
// make sure we have valid width and height values entered
guard let sW = tfWidth.text, !sW.isEmpty, let dW = Double(sW),
let sH = tfHeight.text, !sH.isEmpty, let dH = Double(sH),
dW > 0.0, dH > 0.0
else { return }
// size of image to generate
let targetSize: CGSize = CGSize(width: CGFloat(dW), height: CGFloat(dH))
// create a solid-color image of targetSize
// for actual use, we'd be using a UIImage from somewhere
theImage = UIGraphicsImageRenderer(size: targetSize).image { ctx in
UIColor.systemBlue.setFill()
ctx.fill(CGRect(origin: .zero, size: targetSize))
}
}
view.endEditing(true)
// left/right and top/bottom min space
let padding: CGFloat = 20.0
// maximum rect for the text
let maxRect: CGRect = CGRect(origin: .zero, size: theImage.size).insetBy(dx: padding, dy: padding)
// get size of theString (single-line) using our baseFont
let baseSZ: CGSize = baseFont.sizeOfString(string: theString, constrainedToWidth: .greatestFiniteMagnitude)
// aspect-ratio rect that fits inside inset rect
let fitRect: CGRect = AVMakeRect(aspectRatio: baseSZ, insideRect: maxRect)
// "scaled" font size
let newPointSize: CGFloat = fitRect.height / baseSZ.height * baseFont.pointSize
// create a new font at calculated point size
guard let newFont: UIFont = UIFont(name: baseFont.fontName, size: newPointSize) else { return }
let newIMG: UIImage = drawTextOnImage(theString, withFont: newFont, inRect: fitRect, textColor: .yellow, onImage: theImage)
// update the image view
imgView.image = newIMG
}
func drawTextOnImage(_ str: String, withFont: UIFont, inRect: CGRect, textColor: UIColor, onImage: UIImage) -> UIImage {
let imgSZ: CGSize = CGSize(width: onImage.width, height: onImage.height)
let renderer = UIGraphicsImageRenderer(size: imgSZ)
let img = renderer.image { ctx in
// draw the image
onImage.draw(at: .zero)
// centered text
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
let attrs: [NSAttributedString.Key : Any] = [
.font: withFont,
.foregroundColor: textColor,
.paragraphStyle: paragraphStyle,
]
// draw the string
str.draw(with: inRect, options: .usesLineFragmentOrigin, attributes: attrs, context: nil)
}
return img
}
}
We can enter an image name to load, or generate solid-blue images with the entered dimensions, and draw the string at the calculated font size:
Note: this is a starting point. You'll notice that the calculated text rect and position is based on the font, not the specific characters.
So, we can get this:
If you need the text vertically centered based on the actual characters, you'll want to calculate the glyph bounding box and offset the drawing.