1

I am translating to Swift some Python image processing tasks, which in Python only requires about 30 lines of code.

As I just want to obtain a simple command-line tool with no UI graphics, I'm trying to keep minimum dependence on Apple's high-level interface frameworks.

My Python code looks a bit like that:

from PIL import Image
import numpy

# Load a 16bit grayscale image and convert it to raw data array

img = Image.open("mylocaldir/Gray16.png")
(sizex,sizey) = img.size
inputpix = numpy.array(img.getdata()).astype(numpy.uint16).reshape((sizey,sizex))

# Here, do whatever processing to fill a RGB raw data array.

outputpix = numpy.zeros((sizey,sizex,3),numpy.uint8)
# ...
# ...
# ...

# Write the array back as a jpg file

img = Image.frombytes("RGB",(sizex,sizey),outputpix.reshape(sizex*sizey*3),"raw")
img.save("mylocaldir/OutputRGB.jpg")

Not so familiar with Apple's frameworks, I am struggling to figure out how to implement that in a way as simple as possible. Should a CGImage be used? Or is there any simpler object allowing image file I/O?

Could anybody help me getting the most streamlined Swift version of the Python code written above?

Develoop
  • 11
  • 1
  • Why do you not want to depend on high level frameworks, when you are using PIL in your python code? – Sweeper Feb 17 '22 at 15:44
  • See [this](https://stackoverflow.com/questions/33768066/get-pixel-data-as-array-from-uiimage-cgimage-in-swift) and [this](https://stackoverflow.com/questions/62362763/create-random-pixel-images-in-swift/62363476#62363476) for how to convert pixel arrays from/to `CGImage`. – Sweeper Feb 17 '22 at 15:50
  • Thanks for the links! I finally managed to write two functions for loading and saving 16bit png files from a UInt16 array (assuming predefined size). Unfortunately, there's still a problem: Repeated calls to the write function cause the memory to fill up. Please look at my answer below. – Develoop Mar 03 '22 at 06:30

1 Answers1

0

Here is what I came up for loading or saving png images from a UInt16 array of predefined size, only using CoreGraphics.

Sorry, as a Swift beginner with a C++ background, I might not organise my code in the best way!

func LoadPNG(pixels: inout [UInt16], sizx: Int, sizy: Int, name: String) -> Bool {
    if FileManager.default.fileExists(atPath: name) {
        let data : Data? = try? Data(contentsOf: URL(fileURLWithPath: name, isDirectory: false))
        if data != nil {
            if let cgprovider = CGDataProvider(data: data! as CFData) {
                if let cgimg = CGImage(pngDataProviderSource: cgprovider, decode: nil, shouldInterpolate: false, intent: .defaultIntent) {
                    if (cgimg.width, cgimg.height, cgimg.bitsPerComponent, cgimg.bitsPerComponent) == (sizx, sizy, 16, 16) {
                        let cgspace = CGColorSpaceCreateDeviceGray()
                        let cgcontext = CGContext(data: &pixels, width: sizx, height: sizy, bitsPerComponent: cgimg.bitsPerComponent, bytesPerRow: cgimg.bytesPerRow, space: cgspace, bitmapInfo: cgimg.bitmapInfo.rawValue)
                        cgcontext?.draw(cgimg, in: CGRect(x: 0.0, y: 0.0, width: CGFloat(sizx), height: CGFloat(sizy)))
                        return true
                    }
                }
            }
        }
    }
    return false
}

func SavePNG(pixels: inout [UInt16], sizx: Int, sizy: Int, name: String) -> Bool {
    if let cgprovider = CGDataProvider(data: NSData(bytes:&pixels, length: MemoryLayout<UInt16>.size*pixels.count)) {
        let cgspace = CGColorSpaceCreateDeviceGray()
        let cginfo = CGBitmapInfo(rawValue:CGImageAlphaInfo.none.rawValue)
        if let cgimg = CGImage(width: sizx, height: sizy, bitsPerComponent: 16, bitsPerPixel: 16, bytesPerRow: MemoryLayout<UInt16>.size*sizx, space: cgspace, bitmapInfo:cginfo, provider: cgprovider, decode: nil, shouldInterpolate: false, intent: .defaultIntent) {
            let cfdata: CFMutableData = CFDataCreateMutable(nil, 0)
            if let destination = CGImageDestinationCreateWithData(cfdata, kUTTypePNG as CFString, 1, nil) {
                CGImageDestinationAddImage(destination, cgimg, nil)
                if CGImageDestinationFinalize(destination) {
                    let data = cfdata as Data
                    try? data.write(to:URL(fileURLWithPath: name, isDirectory: false))
                    return true
                }
            }
        }
    }
    return false
}

Unfortunately, there is a problem with this code: Repeated use will fill up the memory! Is there any resource that should have been manually released?

Develoop
  • 11
  • 1
  • I think you should `CFRelease` the `cfdata` and probably also `destination`. If you use `CGImageDestinationCreateWithURL`, you don’t need an intermediate data buffer. See [this](https://stackoverflow.com/a/40371604) and the [create rule](https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFMemoryMgmt/Concepts/Ownership.html#//apple_ref/doc/uid/20001148-103029) – Sweeper Mar 03 '22 at 07:44