Home > Blockchain >  How to calculate scale of font size by resolution in Swift
How to calculate scale of font size by resolution in Swift

Time:10-23

I am making an application that automatically enters a number when a photo is selected using UIKit and Swift.

enter image description here

The picture above is the most perfect situation and result.

The problem arises in a situation like the picture below.

enter image description here

The problem arises when working with photos of different resolutions. The text is large in some photos and small in others.

enter image description here

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!
}

enter image description here

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:

enter image description here

enter image description here enter image description here

enter image description here enter image description here

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:

enter image description here enter image description here

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.

  • Related