Sweeper
Sweeper

Reputation: 273850

How to resize a UIImage without antialiasing?

I am developing an iOS board game. I am trying to give the board a kind of "texture".

What I did was I created this very small image (really small, be sure to look carefully):

enter image description here

And I passed this image to the UIColor.init(patternImage:) initializer to create a UIColor that is this image. I used this UIColor to fill some square UIBezierPaths, and the result looks like this:

enter image description here

All copies of that image lines up perfectly and they form many diagonal straight lines. So far so good.

Now on the iPad, the squares that I draw will be larger, and the borders of those squares will be larger too. I have successfully calculated what the stroke width and size of the squares should be, so that is not a problem.

However, since the squares are larger on an iPad, there will be more diagonal lines per square. I do not want that. I need to resize the very small image to a bigger one, and that the size depends on the stroke width of the squares. Specifically, the width of the resized image should be twice as much as the stroke width.

I wrote this extension to resize the image, adapted from this post:

extension UIImage {
    func resized(toWidth newWidth: CGFloat) -> UIImage {

        let scale = newWidth / size.width
        let newHeight = size.height * scale
        UIGraphicsBeginImageContextWithOptions(CGSize(width: newWidth, height: newHeight), false, 0)
        self.draw(in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight))
        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return newImage!
    }
}

And called it like this:

// this is the code I used to draw a single square
let path = UIBezierPath(rect: CGRect(origin: point(for: Position(x, y)), size: CGSize(width: squareLength, height: squareLength)))
UIColor.black.setStroke()
path.lineWidth = strokeWidth
// this is the line that's important!
UIColor(patternImage: #imageLiteral(resourceName: 

"texture").resized(toWidth: strokeWidth * 2)).setFill() path.fill() path.stroke()

Now the game board looks like this on an iPhone:

enter image description here

You might need to zoom in the webpage a bit to see what I mean. The board now looks extremely ugly. You can see the "borders" of each copy of the image. I don't want this. On an iPad though, the board looks fine. I suspect that this only happens when I downsize the image.

I figured that this might be due to the antialiasing that happens when I use the extension. I found this post and this post about removing antialiasing, but the former seems to be doing this in a image view while I am doing this in the draw(_:) method of my custom GameBoardView. The latter's solution seems to be exactly the same as what I am using.

How can I resize without antialiasing? Or on a higher level of abstraction, How can I make my board look pretty?

Upvotes: 2

Views: 752

Answers (2)

Fattie
Fattie

Reputation: 12278

class Ruled: UIView {

    override func draw(_ rect: CGRect) {

        let T: CGFloat = 15     // desired thickness of lines
        let G: CGFloat = 30     // desired gap between lines
        let W = rect.size.width
        let H = rect.size.height

        guard let c = UIGraphicsGetCurrentContext() else { return }
        c.setStrokeColor(UIColor.orange.cgColor)
        c.setLineWidth(T)

        var p = -(W > H ? W : H) - T
        while p <= W {

            c.move( to: CGPoint(x: p-T, y: -T) )
            c.addLine( to: CGPoint(x: p+T+H, y: T+H) )
            c.strokePath()
            p += G + T + T
        }
    }
}

Enjoy.

Note that you would, obviously, clip that view.

If you want to have a number of them on the screen or in a pattern, just do that.


To clip to a given rectangle:

The class above simply draws it the "size of the UIView".

However, often, you want to draw a number of the "boxes" actually within the view, at different coordinates. (A good example is for a calendar).

enter image description here

Furthermore, this example explicitly draws "both stripes" rather than drawing one stripe over the background color:

func simpleStripes(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat) {
     
    let stripeWidth: CGFloat = 20.0 // whatever you want
    let m = stripeWidth / 2.0
    
    guard let c = UIGraphicsGetCurrentContext() else { return }
    c.setLineWidth(stripeWidth)
    
    let r = CGRect(x: x, y: y, width: width, height: height)
    let longerSide = width > height ? width : height
    
    c.saveGState()
    c.clip(to: r)
        
        var p = x - longerSide
        while p <= x + width {
            
            c.setStrokeColor(pale blue)
            c.move( to: CGPoint(x: p-m, y: y-m) )
            c.addLine( to: CGPoint(x: p+m+height, y: y+m+height) )
            c.strokePath()
            
            p += stripeWidth
            
            c.setStrokeColor(pale gray)
            c.move( to: CGPoint(x: p-m, y: y-m) )
            c.addLine( to: CGPoint(x: p+m+height, y: y+m+height) )
            c.strokePath()
            
            p += stripeWidth
        }
        
    c.restoreGState()
}

Upvotes: 2

Tm Goyani
Tm Goyani

Reputation: 404

extension UIImage {

    func ResizeImage(targetSize: CGSize) -> UIImage
    {
        let size = self.size

        let widthRatio  = targetSize.width  / self.size.width
        let heightRatio = targetSize.height / self.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)
        self.draw(in: rect)
        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return newImage!
    }
}

Upvotes: 1

Related Questions