3

I have a large Tilesheet (over 1000 tiles in one picture) like this: enter image description here

Each tile is 64x64 and I want to split the picture. in this example I would be 4 tiles and put in an array. How can I do this? with different sizes? I need some magic (math) or better an idea how can I get the right position to crop in the loop.

Some fast coded Code:

func cropImage(image: UIImage, tileSize: Int) -> [UIImage]? {
    if Int(image.size.height) % 64 != 0 && Int(image.size.width) % 64 != 0 {
        return nil
    }

    let hCount = Int(image.size.height) / tileSize
    let wCount = Int(image.size.width) / tileSize

    var tiles:[UIImage] = []

    for i in 0...hCount {
        for p in 0...wCount {
            let temp:CGImage = image.cgImage!.cropping(to: CGRect.zero)!
            tiles.append(UIImage(cgImage: temp))
        }
    }
    return tiles
}
Lirf
  • 111
  • 12
  • Welcome to SO. This is not a code writing service. Please edit your question and add what you have tried to solve your issue. Tip you will need to use cropping method in your cgimage. Just pass the origin and size of your crop. – Leo Dabus Feb 06 '17 at 20:32
  • Just wanted a pseudo code.. Edited the question – Lirf Feb 06 '17 at 20:48
  • I already gave you the pseudo code. The code would look like `yourImage.cgImage?.cropping(to: cropRectangle)` that will result in a cropped cgImage. Just use UIImage(cgImage:) initializer to get an UIImage from it. – Leo Dabus Feb 06 '17 at 20:51
  • 1
    CGRect zero has no size – Leo Dabus Feb 06 '17 at 20:53
  • I know. look at the question pls – Lirf Feb 06 '17 at 20:54
  • try `let topLeftArea = CGRect(x: 0, y: 0, width: floor(image.size.width/2), height: floor(image.size.height/2))` if you want to split it in 4 – Leo Dabus Feb 06 '17 at 20:54
  • I don't want to split in 4, if the parameter tileSize would be 32, I would split the image in 8 – Lirf Feb 06 '17 at 20:56
  • Just what you asked in your original question. you will need to calculate the origin (x,y) of every tile and pass the size of your tiles. You will probably need to deal with small tiles at the right column and bottom row of your image – Leo Dabus Feb 06 '17 at 20:57

4 Answers4

3

A lot of good ideas here. Hat tips to both Matt and Lirf. Just thought I would share what I came up with. This is implemented as an extension to UIImage and handles rectangular tiles.

extension UIImage {

    func extractTiles(with tileSize: CGSize) -> [UIImage]? {
        let verticalCount = Int(size.height / tileSize.height)
        let horizontalCount = Int(size.width / tileSize.width)

        var tiles = [UIImage]()

        for verticalIndex in 0...verticalCount - 1 {
            for horizontalIndex in 0...horizontalCount - 1 {
                let imagePoint = CGPoint(x: CGFloat(horizontalIndex) * tileSize.width * -1,
                                         y: CGFloat(verticalIndex) * tileSize.height * -1)
                UIGraphicsBeginImageContextWithOptions(tileSize, false, 0.0)
                draw(at: imagePoint)
                if let newImage = UIGraphicsGetImageFromCurrentImageContext() {
                    tiles.append(newImage)
                }
                UIGraphicsEndImageContext()
            }
        }

        return tiles
    }
}
picciano
  • 22,341
  • 9
  • 69
  • 82
2

On every pass through the loop, you need two pieces of information:

  1. What is the size of the tile? This will be same for all tiles; you only need to calculate it once, before the loop.

  2. What is the origin of the tile? This will be different for each tile. You will cycle both through the x-count and the y-count, and in the innermost loop you'll calculate the origin for this tile (i.e. with this x and y), based on the tile size which you already know.

Now you know the exact rect of the original image that is to be used for this tile. The rest is straightforward.

However, I would suggest giving some thought to the actual way in which you crop to that rect. You are proposing to crop by calling CGImage's cropping(to:). However, in a similar situation, that is not what I do. I make a UIImage by drawing the original UIImage at the negative of the desired origin into an image graphics context of the desired size.

matt
  • 515,959
  • 87
  • 875
  • 1,141
1
func splitImage(row : Int , column : Int){

    let oImg = userImage.image

    let height =  (userImage.image?.size.height)! /  CGFloat (row) //height of each image tile
    let width =  (userImage.image?.size.width)!  / CGFloat (column)  //width of each image tile

    let scale = (userImage.image?.scale)! //scale conversion factor is needed as UIImage make use of "points" whereas CGImage use pixels.

    imageArr = [[UIImage]]() // will contain small pieces of image
    for y in 0..<row{
        var yArr = [UIImage]()
        for x in 0..<column{

            UIGraphicsBeginImageContextWithOptions(
                CGSize(width:width, height:height),
                false, 0)
            let i =  oImg?.cgImage?.cropping(to:  CGRect.init(x: CGFloat(x) * width * scale, y:  CGFloat(y) * height * scale  , width: (width * scale) , height: (height * scale)) )

            let newImg = UIImage.init(cgImage: i!)

            yArr.append(newImg)

            UIGraphicsEndImageContext();
        }
        imageArr.append(yArr)
    }
}
S.S.D
  • 1,579
  • 2
  • 12
  • 23
0

I make the Code working based on the Answer of Matt for the next readers:

func cropImage(image: UIImage, tileSize: Int) -> [UIImage]? {
        let hCount = Int(image.size.height) / tileSize
        let wCount = Int(image.size.width) / tileSize

        var tiles:[UIImage] = []

        for i in 0...hCount-1 {
            for p in 0...wCount-1 {
                let rect = CGRect(x: p*tileSize, y: i*tileSize, width: tileSize, height: tileSize)
                let temp:CGImage = image.cgImage!.cropping(to: rect)!
                tiles.append(UIImage(cgImage: temp))
            }
        }
        return tiles
    }

Parameter image: The tilesheet (original image)

Parameter tileSize: The tile size (length or width)

Lirf
  • 111
  • 12
  • But you may find that by cropping that way you have changed the size of the tiles because you have thrown away the `scale` information inherent in the UIImage. That is one reason I advised you against going from UIImage to CGImage just to crop. – matt Feb 06 '17 at 23:35