Create Command Line tool

Hi,
I'm totally new to swift and haven't really needed it before but today I encountered a very specific problem and I need help developing a command line tool in Swift. It is probably trivial but still, it's easier to ask. Here is what I want it to do:

From the commandline, run the tool with the filepath to an image as an input argument. The program should then convert the image to grayscale, and resize it to exactly 8 by 8 pixels and return a string with the color of each pixel. Like this 128,123,245,166...

As I said a very specific task and probably quite trivial. I know how to do it in Python and PHP but not in Swift. Any help would be appreciated.

What have you tried, and what exactly are you having difficulties with?

If you're running on a Mac, I'd suggest importing CoreGraphics for image processing. Perhaps this link will help you.

I've tried with copying and pasting from various sizes and this is how far I got so far.

import Foundation
import CoreImage

let url = URL.init(fileURLWithPath: "test.jpg")

if let inputImage = CIImage(contentsOf: url) {
    let resizeFilter = CIFilter(name:"CILanczosScaleTransform")!
    let targetSize = NSSize(width:8, height:8)
    let scale = targetSize.height / (inputImage?.extent.height)!
    let aspectRatio = targetSize.width/((inputImage?.extent.width)! * scale)

    // Apply resizing
    resizeFilter.setValue(inputImage, forKey: kCIInputImageKey)
    resizeFilter.setValue(scale, forKey: kCIInputScaleKey)
    resizeFilter.setValue(aspectRatio, forKey: kCIInputAspectRatioKey)
    let outputImage = resizeFilter.outputImage
    
    
    // use the CIImage here
        // save the modified image to outputURL
    }
    exit(EXIT_SUCCESS)

CoreImage can be a bit of an awkward API for this, but it's fine.

First, you'll need to create a CIContext. You can use that to render the image to a chunk of memory and read the pixel values from there. You can also use the CIContext to write a file of the modified image.

Thanks, I'll look into CIContext but I have another issue as well. Xcode complains that "Cannot use optional chaining on non-optional value of type 'CIImage'" at the let scale and let aspectRatio rows. Any ideas?

This is optional chaining. If inputImage is not nil, it accesses the extent property; otherwise it propagates the nil.

In this case, you don't need it, because if let inputImage = ... ensures that inputImage will not be nil.

The same applies to the ! (force unwrap) operator at the end of those lines. They trigger a runtime error if the preceding value is nil, but the value won't be nil due to the if let.

You should consider reading The Swift Programming Language.

I agree I should probably read about the swift programming language. I was hoping that I would not need it since I have in this case encountered a very special edge case that force me to create this tool and I don't think that I will need swift for anything more than this, t least not now.

First I was going to write it in c++ but then I realised that I hadn't really done anything in c++ since 1994 and the gaps were painfully obvious.

Well Karl,
Believe it or not but thanks to you pointing me in the right direction I have now created the tool needed. It's probably a bit rough but it does what it is supposed to do. Thank you.

import Foundation
import CoreImage

let url = URL.init(fileURLWithPath: "test.png")


if let inputImage = CIImage(contentsOf: url) {
    let resizeFilter = CIFilter(name:"CILanczosScaleTransform")!
    let targetSize = NSSize(width:8, height:8)
    let scale = targetSize.height / (inputImage.extent.height)
    let aspectRatio = targetSize.width/((inputImage.extent.width) * scale)

    // Apply resizing
    resizeFilter.setValue(inputImage, forKey: kCIInputImageKey)
    resizeFilter.setValue(scale, forKey: kCIInputScaleKey)
    resizeFilter.setValue(aspectRatio, forKey: kCIInputAspectRatioKey)
    let outputImage = resizeFilter.outputImage
    
    let context = CIContext(options: nil)
    
    var pixelBuffer: CVPixelBuffer?
    let attrs = [kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue,
                 kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue] as CFDictionary
    let width:Int = Int(8)
    let height:Int = Int(8)
    CVPixelBufferCreate(kCFAllocatorDefault,
                        width,
                        height,
                        kCVPixelFormatType_32BGRA,
                        attrs,
                        &pixelBuffer)
    
    context.render(outputImage!, to: pixelBuffer!)
    
    CVPixelBufferLockBaseAddress(pixelBuffer!, .readOnly)
    
    for x in 0...7 {
        for y in 0...7 {
            let test = pixelFrom(x:x, y:y, movieFrame:pixelBuffer!)
            let grey = (Int(test.0)+Int(test.1)+Int(test.2))/3
            print (grey)
        }
    }
    
    
    // write the output in the same color space as the input; fallback to sRGB if it can't be determined
   // let outputColorSpace = CGColorSpace(name: CGColorSpace.sRGB)

    //let dest = URL.init(fileURLWithPath: "tada.jpeg")
    
    //try context.writeJPEGRepresentation(of: outputImage!, to: dest, colorSpace:(outputColorSpace ?? CGColorSpace(name: CGColorSpace.sRGB))!)
    // use the CIImage here
        // save the modified image to outputURL
}
exit(EXIT_SUCCESS)

func pixelFrom(x: Int, y: Int, movieFrame: CVPixelBuffer) -> (UInt8, UInt8, UInt8) {
    let baseAddress = CVPixelBufferGetBaseAddress(movieFrame)
    
    let bytesPerRow = CVPixelBufferGetBytesPerRow(movieFrame)
    let buffer = baseAddress!.assumingMemoryBound(to: UInt8.self)
    
    let index = x*4 + y*bytesPerRow
    let b = buffer[index]
    let g = buffer[index+1]
    let r = buffer[index+2]
    
    return (r, g, b)
}
1 Like