Reputation: 43
My code converts a colored NSImage into grayscale. It then converts the grayscale into pure black and white image. Is there a way to create a filter which can specify how to convert the gray pixels into black / white? For example - 1. If pixel value > 100, turn it into black. 2. Else turn the pixel into white.
What filter settings can I use to achieve this?
Updated with CIKernel custom filter:
func applyCustomCIFilter( path: String )->NSImage{
let url = URL(fileURLWithPath: path)
let origImage = CIImage(contentsOf: url)!
// Convert image into grayscale
let grayImage = CIFilter(name: "CIPhotoEffectNoir", parameters: [kCIInputImageKey: origImage])?.outputImage
print("convert into black and white")
// custom filter to convert light gray to white and dark gray to black
let replaceGrayKernel = CIColorKernel( source:
"kernel vec4 replaceGrayWithBlackOrWhite(sampler grayImage) {" +
"if( sample(grayImage, samplerCoord(grayImage)).rgb > 0.7 ){" +
"return vec4(0.0,0.0,0.0,1.0);" +
"}" +
"else" +
"{" +
"return vec4(1.0,1.0,1.0,1.0);" +
"}" +
"}"
)
//Apply custom filter to grayscale image.
//ERROR: blackAndWhiteImage is nil causing runtime failure
let blackAndWhiteImage = replaceGrayKernel?.apply(extent: (grayImage!.extent), arguments: [grayImage as Any])
//Convert CIImage to NSImage
let rep = NSCIImageRep(ciImage: blackAndWhiteImage!)
let nsImage = NSImage(size: rep.size)
nsImage.addRepresentation(rep)
return nsImage;
}
Upvotes: 1
Views: 1727
Reputation:
You had 2 mistakes in your kernel code. Here's the correct code:
let kernel = CIColorKernel( source:
"kernel vec4 replaceGrayWithBlackOrWhite(__sample s) {" +
"if (s.r > 0.25 && s.g > 0.25 && s.b > 0.25) {" +
" return vec4(0.0,0.0,0.0,1.0);" +
"} else {" +
" return vec4(1.0,1.0,1.0,1.0);" +
"}" +
"}"
)
The change from sampler
to __ sample
is because a CIColorKernel
, which is optimized for working with one pixel at a time, just passes into the kernel that pixel. Thus, there's also no need for calling sample(samplerCoord())
. CIWarpKernel
, and CIKernel
use sampler
, as you send into the kernel a RIO (region of interest) and those kernels can access surrounding pixels... think blur effects.
The second change was to the if statement. if (s.rgb > 0.7)
is comparing a vec3
(or three floats) to a single float. I had to play around with values once I corrected these and think that (a) using AND (&&
) instead or OR (||
) along with (b) lowering the threshold to 0.25 makes for a closer black/white image. Play around yourself with this to see what you want.
I've created a small Swift 5 project (using a hard-coded image) that uses this kernel.
There's no comments in it and it contains various extensions (and a subclass of GLKView
) because I gleaned things from a production project. Besides focusing on your kernel code issue, one word of warning about the code - it contains several force-unwraps that should be removed for "production-ready" code.
Upvotes: 2