Search code examples
arraysswiftimagecropuint8array

Crop byte array image in swift


I am trying to crop image over UInt8 array. Here is the way I've tried:

func cropImageArray(byteArray: [UInt8], sourceWidth: Int32, bitsPerPixel: Int32, rect: Int32Rect) -> [UInt8] {
    var blockSize = bitsPerPixel / 8
    
    var outputPixels = [UInt8]()
    
    for _ in 0..<(rect.width * rect.height * blockSize) {
        outputPixels.append(UInt8(0.0))
    }
    
    for line in Int32(0) ..< rect.height {
        var sourceIndex = (((rect.y + line) * sourceWidth) + rect.x) * blockSize
        var destinationIndex = (line * rect.width) * blockSize
        
        for i in Int32(0) ..< Int32(rect.width * blockSize) {
            outputPixels[Int(destinationIndex + i)] = byteArray[Int(sourceIndex + i)]
        }
    }
    return outputPixels
}

It doesn't work properly. It gives broken image. Do you have an idea?

Edit: Here is the way I obtain byte array.

let img: UIImage! = UIImage(named: "example")
let d: Data! = img.pngData()
let ptr = UnsafeMutablePointer<UInt8>.allocate(capacity: d.count)
d.withUnsafeBytes { (buff) -> Void in
        ptr.initialize(from: buff.bindMemory(to: UInt8.self).baseAddress!, count: d.count)
}
let byteArray = UnsafeMutableBufferPointer(start: ptr, count: d.count).map({$0})

Edit2: The function I've used as a solution because of the performance is high.


Solution

  • So after converting to PNG, you can't just cut some bytes, thinking they are pixels, as PNG format contains signature, and each byte is not just encoding of RGB. You have 3 options:

    1. Simpler. Obtain the actual source - either CIImage or CGImage from UIImage, and crop it using CIImage.cropped(to:) or CGImage.cropping(to:). The linked pages contain the sample code for each. Note that UIImage will contain either CGImage as its source, or CIImage, never the both. Also coordinate system on CGImage and CIImage is different, so you will need to figure out the proper coordinates to crop. Here someone came up with universal method it seems, but I didn't try it, so not sure if it's correct.

    2. If you really want to manipulate your image on pixel level, you can use the Accelerate framework. It allows you to create image buffer from CGImage (and you can always convert CIImage to CGImage if needed). And then that buffer (vImage_Buffer can be manipulated as a raw image, which is what you are trying to do above. Some examples are available here. They don't show the crop, but some close examples. Accelerate is very performant, so if you have a graphic app, may make sense to learn and use it.

    3. If you really want to crop PNG image, you need to learn its format from the link I posted at the top of the answer, and then do it that way. Usually this is not a good way, since you are not getting a simplicity of the first method, or a good performance of the second.