fltman
1
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.
Karl
(👑🦆)
2
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.
fltman
3
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)
Karl
(👑🦆)
4
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.
fltman
5
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?
Karl
(👑🦆)
6
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.
fltman
7
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.
fltman
8
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