Averaging the color of pixels with Accelerate - ios

Yes, I know about using CIAreaAverate CIFilter to get the average color of pixels.
I am trying to create some alternative using Accelerate Framework to see if I can come with something faster.
I am rendering a CIImage to a context. For that purpose I have this CIImage extension...
let device: MTLDevice = MTLCreateSystemDefaultDevice()!
let context = CIContext.init(mtlDevice: device, options: [.workingColorSpace: kCFNull])
let w = self.extent.width
let h = self.extent.height
let size = w * h * 4
var bitmap = [UInt8](repeating: 0, count:Int(size))
context.render(self,
toBitmap: &bitmap,
rowBytes: 4 * Int(w),
bounds: self.extent,
format: .BGRA8,
colorSpace: nil)
At this point I have bitmap containing the BGRA bytes interleaved.
To get the average of R, G and B, all I have to do is something like this:
var averageBlue : Int = 0
for x in stride(from:0, through: bitmap.count-4, by: 4) {
let value = bitmap[Int(x)]
averageBlue += Int(value)
}
averageBlue /= numberOfPixels
but this for loop is slow as hell, as expected.
I was thinking about using some Accelerate function like
vDSP_meanvD(bitmap, 2, &r, vDSP_Length(numberOfPixels))
but this function requires bitmap to be an array of UnsafePointer<Double>...
I could convert bitmap to that, but that would require a for loop, that is slow...
Is there any way to extract those R, G and B pixels and have their individual averages using some accelerate stuff going on?

You can convert bitmap to single-precision floating-point values using vDSP_vfltu8(_:_:_:_:_:) :
let bitmap: [UInt8] = [1, 10, 50, 0,
2, 20, 150, 5,
3, 30, 250, 10]
//Blue
var blueFloats = [Float](repeating: 0, count: bitmap.count/4)
vDSP_vfltu8(bitmap,
vDSP_Stride(4),
&blueFloats,
vDSP_Stride(1),
vDSP_Length(blueFloats.count))
And then use vDSP_meanv(_:_:_:_:) :
var blue: Float = 0
vDSP_meanv(blueFloats,
vDSP_Stride(1),
&blue,
vDSP_Length(blueFloats.count))
print("blue =", blue) //2.0
As to the reds :
//Red
var redFloats = [Float](repeating: 0, count: bitmap.count/4)
vDSP_vfltu8(UnsafePointer.init(bitmap).advanced(by: 2),
vDSP_Stride(4),
&redFloats,
vDSP_Stride(1),
vDSP_Length(redFloats.count))
var red: Float = 0
vDSP_meanv(redFloats,
vDSP_Stride(1),
&red,
vDSP_Length(redFloats.count))
print("red =", red) //150.0

Like ielyamani’s said, you can use vDSP_vfltu8 to build that buffer of Float efficiently.
But rather than striding through that array four times, you can also use cblas_sgemv (or cblas_sgemm) to calculate all four averages in a single call:
let pixelCount: Int = width * height
let channelsPerPixel: Int = 4
let m: Int32 = Int32(channelsPerPixel)
let n: Int32 = Int32(pixelCount)
let lda = m
var a = [Float](repeating: 0, count: pixelCount * channelsPerPixel)
vDSP_vfltu8(pixelBuffer, vDSP_Stride(1), &a, vDSP_Stride(1), vDSP_Length(pixelCount * channelsPerPixel))
var x = [Float](repeating: 1 / Float(pixelCount), count: pixelCount)
var y = [Float](repeating: 0, count: channelsPerPixel)
cblas_sgemv(CblasColMajor, CblasNoTrans, m, n, 1, &a, lda, &x, 1, 1, &y, 1)
print(y)

Related

OpenCV crossword grid parser

I am experimenting with OpenCV.js and attempting to get it to parse a Crossword Grid.
I have a couple of samples of Crossword grids that I am working with:
I would like to be able to parse the grid into an array that reads something like this:
[[-1, 1, -1, 2, 0, ...],[-1, 1, -1, 2, 0, ...]]
where:
-1 = "black square"
0 = "white square"
n > 0 = numbered square (where n is the number itself).
I am using opencv.js but cannot quite get the results I am looking for.
Here is a list of functions I have written to parse the crosswords:
function convertImageToRBGA(input, width, height, save = true) {
let output = new cv.Mat();
cv.cvtColor(input, output, cv.COLOR_RGB2RGBA, 0);
if (save) saveImage(width, height, output, "output-grey.jpg");
return output;
}
function convertImageToGrayscale(input, width, height, save = true) {
let output = new cv.Mat();
cv.cvtColor(input, output, cv.COLOR_RGB2GRAY, 0);
if (save) saveImage(width, height, output, "output-grey.jpg");
return output;
}
function adaptiveThresholdImage(input, width, height, save = true) {
let output = new cv.Mat();
//cv.threshold(input, output, 128, 255, cv.THRESH_BINARY | cv.THRESH_OTSU);
cv.adaptiveThreshold(input, output, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 5, 2);
if (save) saveImage(width, height, output, "output-adaptive-threshold.jpg");
return output;
}
function denoiseImage(input, width, height, save = true) {
let output = new cv.Mat();
cv.fastNlMeansDenoising(input, output, 7);
if (save) saveImage(width, height, output, "output-denoised.jpg");
return output;
}
function thresholdImage(input, width, height, type, save = true) {
let output = new cv.Mat();
cv.threshold(input, output, 128, 255, cv.THRESH_BINARY_INV);
if (save) saveImage(width, height, output, "output-threshold.jpg");
return output;
}
function morphOpenImage(input, width, height, save = true) {
let output = new cv.Mat();
let M = cv.Mat.ones(5, 5, cv.CV_8U);
let anchor = new cv.Point(-1, -1);
// You can try more different parameters
cv.morphologyEx(input, output, cv.MORPH_OPEN, M, anchor, 1, cv.BORDER_CONSTANT, cv.morphologyDefaultBorderValue());
if (save) saveImage(width, height, output, "output-morph-open.jpg");
return output;
}
function medianBlurImage(input, width, height, save = true) {
let output = new cv.Mat();
cv.medianBlur(input, output, 3);
if (save) saveImage(width, height, output, "output-median-blur.jpg");
return output;
}
function blurImage(input, width, height, kunit = 3, name = "output-blur", save = true) {
let output = new cv.Mat();
// let M = cv.Mat.eye(3, 3, cv.CV_32FC1);
// let anchor = new cv.Point(-1, -1);
// cv.filter2D(input, output, cv.CV_8U, M, anchor, 0, cv.BORDER_DEFAULT);
let anchor = new cv.Point(-1, -1);
let ksize = new cv.Size(kunit, kunit);
cv.blur(input, output, ksize, anchor, cv.BORDER_DEFAULT);
if (save) saveImage(width, height, output, `${name}.jpg`);
return output;
}
function gaussianBlur(input, width, height, name = "output-gaussian-blur", save = true) {
let output = new cv.Mat();
let ksize = new cv.Size(5, 5);
cv.GaussianBlur(input, output, ksize, 0, 0, cv.BORDER_DEFAULT);
if (save) saveImage(width, height, output, `${name}.jpg`);
return output;
}
function cannyImage(input, width, height, save = true) {
let output = new cv.Mat();
cv.Canny(input, output, 50, 150, 3, false);
if (save) saveImage(width, height, output, "output-canny.jpg");
return output;
}
function sharpenImage(input, width, height, name = "output-sharpen", save = true) {
// change kernal
let kernel = cv.matFromArray(3, 3, cv.CV_32FC1, [0, -1, 0, -1, 5, -1, 0, -1, 0]);
let anchor = new cv.Point(-1, -1);
let output = new cv.Mat();
cv.filter2D(input, output, cv.CV_8U, kernel, anchor, 0, cv.BORDER_CONSTANT);
saveImage(width, height, output, `${name}.jpg`);
return output;
}
function detectHoughLinesInImage(input, width, height, save = true) {
let output = cv.Mat.zeros(input.rows, input.cols, cv.CV_8UC3);
let lines = new cv.Mat();
cv.HoughLines(input, lines, 1, Math.PI / 180, 30, 0, 0, 0, Math.PI);
// draw lines
for (let i = 0; i < lines.rows; ++i) {
let rho = lines.data32F[i * 2];
console.log(rho);
let theta = lines.data32F[i * 2 + 1];
let a = Math.cos(theta);
let b = Math.sin(theta);
let x0 = a * rho;
let y0 = b * rho;
let startPoint = { x: x0 - 1000 * b, y: y0 + 1000 * a };
let endPoint = { x: x0 + 1000 * b, y: y0 - 1000 * a };
cv.line(output, startPoint, endPoint, [255, 0, 0, 255]);
}
lines.delete();
if (save) saveImage(width, height, output, "output-lines.jpg");
return output;
}
function detectHoughLinesPInImage(input, width, height, save = true) {
let output = cv.Mat.zeros(input.rows, input.cols, cv.CV_8UC3);
let lines = new cv.Mat();
let color = new cv.Scalar(255, 0, 0);
cv.HoughLinesP(input, lines, 1, Math.PI / 180, 2, 0, 0);
for (let i = 0; i < lines.rows; ++i) {
let startPoint = new cv.Point(lines.data32S[i * 4], lines.data32S[i * 4 + 1]);
let endPoint = new cv.Point(lines.data32S[i * 4 + 2], lines.data32S[i * 4 + 3]);
cv.line(output, startPoint, endPoint, color);
}
lines.delete();
if (save) saveImage(width, height, output, "output-lines.jpg");
return output;
}
function dilateImage(input, width, height, save = true) {
let M = cv.Mat.ones(5, 5, cv.CV_8U);
let anchor = new cv.Point(-1, -1);
let output = new cv.Mat();
cv.dilate(input, output, M, anchor, 1, cv.BORDER_CONSTANT, cv.morphologyDefaultBorderValue());
if (save) saveImage(width, height, output, "output-dilated.jpg");
return output;
}
function erodeImage(input, width, height, save = true) {
let M = cv.Mat.eye(5, 5, cv.CV_8U);
let anchor = new cv.Point(-1, -1);
let output = new cv.Mat();
cv.erode(input, output, M, anchor, 1, cv.BORDER_CONSTANT, cv.morphologyDefaultBorderValue());
if (save) saveImage(width, height, output, "output-eroded.jpg");
return output;
}
The above functions are not exclusive as there are other functions that can drawContours, etc. I haven't included them here for brevity but can do if necessary. Also, the above functions are a product of experimenting but, so far, the best results I have had is by using only a subset of them (shown below).
Apart from there being certain parameters that can be adjusted, the general issue appears to be the order that the functions.
The simplest order I can come up with is this:
mat = convertImageToGrayscale(src, width, height);
mat = gaussianBlur(mat, width, height);
mat = sharpenImage(mat, width, height);
mat = cannyImage(mat, width, height);
cv.findContours(mat, contours, hierarchy, cv.RETR_CCOMP, cv.CHAIN_APPROX_SIMPLE);
Which produces this output (in order):
The canny image looks good but when I run the cv.findContours function, this is what I get:
I have added a couple of functions to determine the colour of the ROI and add a letter value to indicate that.
I just can't get the findContours function to read all of the white squares (I believe if I can read all of the white squares, I can use some basic cartesian Math to work out the rest of the grid).
The numbers within the squares seem to cause the canny function some issues when it comes to reading the squares.
Not sure at this point if I need to start complicating things with erosion/dilation functions or even get into thresholding.
But I feel that with the way the Canny function is reading the grid, it feels like I am halfway there but can't quite get the contours I need.
Any help here would be apprciated.
I agree, the Canny output does look fine, I wouldn't think you would need to do anything more with the image to enable findContours to get all the squares. Have you tried using the opencv function 'drawContours'? If used correctly, that can help you see what findContours is doing.
In addition, you are using 'RETR_CCOMP', which, according to the docs:
"retrieves all of the contours and organizes them into a two-level hierarchy. At the top level, there are external boundaries of the components. At the second level, there are boundaries of the holes"
Are you using the hierarchy system from findContours already? And if so, perhaps some of those squares are being incorrectly classified in the hierarchy by findContours.

CGContext.init() -- NULL color space no longer allowed

TL;DR: In legacy Obj-C code, the color space param value was NULL. That is not allowed in the Swift equivalent. What value to use?
I have inherited code that reads:
unsigned char pixel[1] = {0};
CGContextRef context = CGBitmapContextCreate(
pixel,1, 1, 8, 1, NULL, (CGBitmapInfo)kCGImageAlphaOnly
);
The port to Swift 4 CGContext is straightforward, except for that NULL color space value. Using a plausible value, I am getting nil back from CGContext.init?(). My translation is:
var pixelValue = UInt8(0)
var pixel = Data(buffer: UnsafeBufferPointer(start:&pixelValue, count:1))
let context = CGContext(
data : &pixel,
width : 1,
height : 1,
bitsPerComponent: 8,
bytesPerRow : 1,
space : CGColorSpace(name:CGColorSpace.genericRGBLinear)!,
bitmapInfo : CGImageAlphaInfo.alphaOnly.rawValue
)! // Returns nil; unwrapping crashes
Q: What is the appropriate value for space? (The value I provide is not returning nil; it's the CGContext() call itself.
Setting the environment variable CGBITMAP_CONTEXT_LOG_ERRORS yields an error log like this:
Assertion failed: (0), function get_color_model_name,
file /BuildRoot/Library/Caches/com.apple.xbs/Sources/Quartz2D_Sim/
Quartz2D-1129.2.1/CoreGraphics/API/CGBitmapContextInfo.c, line 210.
For some more backstory, the context was used to find the alpha value of a single pixel in a UIImage in the following way:
unsigned char pixel[1] = {0};
CGContextRef context = CGBitmapContextCreate(pixel,1, 1, 8, 1, NULL, (CGBitmapInfo)kCGImageAlphaOnly);
UIGraphicsPushContext(context);
[image drawAtPoint:CGPointMake(-point.x, -point.y)];
UIGraphicsPopContext();
CGContextRelease(context);
CGFloat alpha = pixel[0]/255.0;
(I do have possible alternatives for finding alpha, but in the interest of leaving legacy code alone, would like to keep it this way.)
I recently worked with similar topic, maybe this code sample will help someone:
let image = UIImage(named: "2.png")
guard let cgImage = image?.cgImage else {
fatalError()
}
let width = cgImage.width
let height = cgImage.height
//CGColorSpaceCreateDeviceGray - 1 component, 8 bits
//i.e. 1px = 1byte
let bytesPerRow = width
let bitmapByteCount = width * height
let bitmapData: UnsafeMutablePointer<UInt8> = .allocate(capacity: bitmapByteCount)
defer {
bitmapData.deallocate()
}
bitmapData.initialize(repeating: 0, count: bitmapByteCount)
guard let context = CGContext(data: bitmapData, width: width, height: height,
bitsPerComponent: 8, bytesPerRow: bytesPerRow,
space: CGColorSpaceCreateDeviceGray(), bitmapInfo: CGImageAlphaInfo.alphaOnly.rawValue) else {
fatalError()
}
//draw image to context
var rect = CGRect(x: 0, y: 0, width: width, height: height)
context.draw(cgImage, in: rect)
// Enumerate through all pixels
for row in 0..<height {
for col in 0..<width {
let alphaValue = bitmapData[row * width + col]
if alphaValue != 0 {
//visible pixel
}
}
}
Here’s how to determine whether a pixel is transparent:
let info = CGImageAlphaInfo.alphaOnly.rawValue
let pixel = UnsafeMutablePointer<UInt8>.allocate(capacity:1)
defer {
pixel.deinitialize(count: 1)
pixel.deallocate()
}
pixel[0] = 0
let sp = CGColorSpaceCreateDeviceGray()
let context = CGContext(data: pixel,
width: 1, height: 1, bitsPerComponent: 8, bytesPerRow: 1,
space: sp, bitmapInfo: info)!
UIGraphicsPushContext(context)
im.draw(at:CGPoint(-point.x, -point.y))
UIGraphicsPopContext()
let p = pixel[0]
let alpha = Double(p)/255.0
let transparent = alpha < 0.01
For the record, here is how I wound up doing it. It hasn't (yet) misbehaved, so on the principle of "If it ain't broke, don't fix it" I'll leave it. (I have added self for clarity.) But you can be sure that I will paste Matt's code right in there, in case I need it in the future. Thanks Matt!
// Note that "self" is a UIImageView; "point" is the point under consideration.
let im = self.image!
// TODO: Why is this clamping necessary? We get points outside our size.
var x = point.x
var y = point.y
if x < 0 { x = 0 } else if x > im.size.width - 1 { x = im.size.width - 1 }
if y < 0 { y = 0 } else if y > im.size.height - 1 { y = im.size.height - 1 }
let screenWidth = self.bounds.width
let intrinsicWidth = im.size.width
x *= im.scale * intrinsicWidth/screenWidth
y *= im.scale * intrinsicWidth/screenWidth
let pixData = im.cgImage?.dataProvider?.data
let data = CFDataGetBytePtr(pixData!)
let pixIndex = Int(((Int(im.size.width*im.scale) * Int(y)) + Int(x)) * 4)
let r = data?[pixIndex]
let g = data?[pixIndex + 1]
let b = data?[pixIndex + 2]
let α = data?[pixIndex + 3]
let red = CGFloat(r!)/255
let green = CGFloat(g!)/255
let blue = CGFloat(b!)/255
let alpha = CGFloat(α!)/255

Swift 2.2 - Count Black Pixels in UIImage

I need to count all the black pixels in UIImage. I have found a code that could work however it is written in Objective-C. I have tried to convert it in swift but I get lots of errors and I cannot find the way of fix them.
Whats the best way to do this using Swift?
Simple Image
Objective-C:
/**
* Structure to keep one pixel in RRRRRRRRGGGGGGGGBBBBBBBBAAAAAAAA format
*/
struct pixel {
unsigned char r, g, b, a;
};
/**
* Process the image and return the number of pure red pixels in it.
*/
- (NSUInteger) processImage: (UIImage*) image
{
NSUInteger numberOfRedPixels = 0;
// Allocate a buffer big enough to hold all the pixels
struct pixel* pixels = (struct pixel*) calloc(1, image.size.width * image.size.height * sizeof(struct pixel));
if (pixels != nil)
{
// Create a new bitmap
CGContextRef context = CGBitmapContextCreate(
(void*) pixels,
image.size.width,
image.size.height,
8,
image.size.width * 4,
CGImageGetColorSpace(image.CGImage),
kCGImageAlphaPremultipliedLast
);
if (context != NULL)
{
// Draw the image in the bitmap
CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, image.size.width, image.size.height), image.CGImage);
// Now that we have the image drawn in our own buffer, we can loop over the pixels to
// process it. This simple case simply counts all pixels that have a pure red component.
// There are probably more efficient and interesting ways to do this. But the important
// part is that the pixels buffer can be read directly.
NSUInteger numberOfPixels = image.size.width * image.size.height;
while (numberOfPixels > 0) {
if (pixels->r == 255) {
numberOfRedPixels++;
}
pixels++;
numberOfPixels--;
}
CGContextRelease(context);
}
free(pixels);
}
return numberOfRedPixels;
}
Much faster is to use Accelerate's vImageHistogramCalculation to get a histogram of the different channels in your image:
let img: CGImage = CIImage(image: image!)!.cgImage!
let imgProvider: CGDataProvider = img.dataProvider!
let imgBitmapData: CFData = imgProvider.data!
var imgBuffer = vImage_Buffer(data: UnsafeMutableRawPointer(mutating: CFDataGetBytePtr(imgBitmapData)), height: vImagePixelCount(img.height), width: vImagePixelCount(img.width), rowBytes: img.bytesPerRow)
let alpha = [UInt](repeating: 0, count: 256)
let red = [UInt](repeating: 0, count: 256)
let green = [UInt](repeating: 0, count: 256)
let blue = [UInt](repeating: 0, count: 256)
let alphaPtr = UnsafeMutablePointer<vImagePixelCount>(mutating: alpha) as UnsafeMutablePointer<vImagePixelCount>?
let redPtr = UnsafeMutablePointer<vImagePixelCount>(mutating: red) as UnsafeMutablePointer<vImagePixelCount>?
let greenPtr = UnsafeMutablePointer<vImagePixelCount>(mutating: green) as UnsafeMutablePointer<vImagePixelCount>?
let bluePtr = UnsafeMutablePointer<vImagePixelCount>(mutating: blue) as UnsafeMutablePointer<vImagePixelCount>?
let rgba = [redPtr, greenPtr, bluePtr, alphaPtr]
let histogram = UnsafeMutablePointer<UnsafeMutablePointer<vImagePixelCount>?>(mutating: rgba)
let error = vImageHistogramCalculation_ARGB8888(&imgBuffer, histogram, UInt32(kvImageNoFlags))
After this runs, alpha, red, green, and blue are now histograms of the colors in your image. If red, green, and blue each only have count in the 0th spot, while alpha only has count in the last spot, your image is black.
If you want to not even check multiple arrays, you can use vImageMatrixMultiply to combine your different channels:
let readableMatrix: [[Int16]] = [
[3, 0, 0, 0]
[0, 1, 1, 1],
[0, 0, 0, 0],
[0, 0, 0, 0]
]
var matrix: [Int16] = [Int16](repeating: 0, count: 16)
for i in 0...3 {
for j in 0...3 {
matrix[(3 - j) * 4 + (3 - i)] = readableMatrix[i][j]
}
}
vImageMatrixMultiply_ARGB8888(&imgBuffer, &imgBuffer, matrix, 3, nil, nil, UInt32(kvImageNoFlags))
If you stick this in before the histograming, your imgBuffer will be modified in place to average the RGB in each pixel, writing the average out to the B channel. As such, you can just check the blue histogram instead of all three.
(btw, the best description of vImageMatrixMultiply I've found is in the source code, like at https://github.com/phracker/MacOSX-SDKs/blob/2d31dd8bdd670293b59869335d9f1f80ca2075e0/MacOSX10.7.sdk/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vImage.framework/Versions/A/Headers/Transform.h#L21)
I ran into a similar issue now, where I needed to determine if an image was 100% black. The following code will return the number of pure black pixels it finds in an image.
However, if you want to bump the threshold up, you can change the compare value, and allow it to tolerate a wider range of possible colors.
import UIKit
extension UIImage {
var blackPixelCount: Int {
var count = 0
for x in 0..<Int(size.width) {
for y in 0..<Int(size.height) {
count = count + (isPixelBlack(CGPoint(x: CGFloat(x), y: CGFloat(y))) ? 1 : 0)
}
}
return count
}
private func isPixelBlack(_ point: CGPoint) -> Bool {
let pixelData = cgImage?.dataProvider?.data
let pointerData: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)
let pixelInfo = Int(((size.width * point.y) + point.x)) * 4
let maxValue: CGFloat = 255.0
let compare: CGFloat = 0.01
if (CGFloat(pointerData[pixelInfo]) / maxValue) > compare { return false }
if (CGFloat(pointerData[pixelInfo + 1]) / maxValue) > compare { return false }
if (CGFloat(pointerData[pixelInfo + 2]) / maxValue) > compare { return false }
return true
}
}
You call this with:
let count = image.blackPixelCount
The one caveat is that this is a very slow process, even on small images.

Apparent indices limit

Using SceneKit in swift I trying to build a custom 3D object (a terrain). To build a terrain I build a plane that I've divided in a number of horizontal and vertical section. With a small number or section everything is fine but with not so large number the app crash in some deep OpenGL function with a EXC_BAD_ACCESS.
Here is a simplified version of the terrain (yes it's just a plane) which don't exhibit the issue:
let width:Float = 12
let depth:Float = 12
let height:Float = 2
let nx = 6
let nz = 6
func build() -> SCNGeometry {
var vertices : [SCNVector3] = Array()
for i in 0..<(nx + 1) {
for j in 0..<(nz + 1) {
let x = (Float(i) / Float(nx)) * width - width/2
let z = (Float(j) / Float(nz)) * depth - depth/2
let y = Float(0)
vertices.append(SCNVector3(x:x, y:y, z:z))
}
}
var indices : [CInt] = []
for i in 0..<nx {
for j in 0..<nz {
indices.append(CInt(i + j * (nz+1)))
indices.append(CInt(i+1 + j * (nz+1)))
indices.append(CInt(i + (j+1)*(nz+1)))
indices.append(CInt(i+1 + j * (nz+1)))
indices.append(CInt(i+1 + (j+1)*(nz+1)))
indices.append(CInt(i + (j+1)*(nz+1)))
}
}
let data = NSData(bytes: vertices, length: sizeof(SCNVector3) * countElements(vertices))
let vertexSource = SCNGeometrySource(data: data, semantic: SCNGeometrySourceSemanticVertex, vectorCount: vertices.count, floatComponents: true, componentsPerVector: 3, bytesPerComponent: sizeof(Float), dataOffset: 0, dataStride: sizeof(SCNVector3))
let indexData = NSData(bytes: indices, length: sizeof(CInt) * countElements(indices))
let element = SCNGeometryElement(data: indexData, primitiveType: SCNGeometryPrimitiveType.Triangles, primitiveCount: indices.count, bytesPerIndex: sizeof(CInt))
return SCNGeometry(sources: [vertexSource], elements: [element])
}
Now change nx and nz to:
let nx = 8
let nz = 8
Crash
This seems very much linked with the number of indices but at ~300 I don't believe I should be hitting a limit.
Any suggestion, help or solution very much appreciated. Thanks.
The problem could be that you're passing primitiveCount: indices.count when creating the SCNGeometryElement rather than indices.count/3 (since there are three indices per triangle). I'm surprised there's no earlier bounds checking, but without that, you could certainly see a crash depending on the number of indices.

Get CIColorCube Filter Working In Swift

I am trying to get the CIColorCube filter working. However the Apple documents only provide a poorly explained reference example here:
// Allocate memory
const unsigned int size = 64;
float *cubeData = (float *)malloc (size * size * size * sizeof (float) * 4);
float rgb[3], hsv[3], *c = cubeData;
// Populate cube with a simple gradient going from 0 to 1
for (int z = 0; z < size; z++){
rgb[2] = ((double)z)/(size-1); // Blue value
for (int y = 0; y < size; y++){
rgb[1] = ((double)y)/(size-1); // Green value
for (int x = 0; x < size; x ++){
rgb[0] = ((double)x)/(size-1); // Red value
// Convert RGB to HSV
// You can find publicly available rgbToHSV functions on the Internet
rgbToHSV(rgb, hsv);
// Use the hue value to determine which to make transparent
// The minimum and maximum hue angle depends on
// the color you want to remove
float alpha = (hsv[0] > minHueAngle && hsv[0] < maxHueAngle) ? 0.0f: 1.0f;
// Calculate premultiplied alpha values for the cube
c[0] = rgb[0] * alpha;
c[1] = rgb[1] * alpha;
c[2] = rgb[2] * alpha;
c[3] = alpha;
c += 4; // advance our pointer into memory for the next color value
}
}
}
// Create memory with the cube data
NSData *data = [NSData dataWithBytesNoCopy:cubeData
length:cubeDataSize
freeWhenDone:YES];
CIColorCube *colorCube = [CIFilter filterWithName:#"CIColorCube"];
[colorCube setValue:#(size) forKey:#"inputCubeDimension"];
// Set data for cube
[colorCube setValue:data forKey:#"inputCubeData"];
So I have attempted to translate this over to Swift with the following:
var filter = CIFilter(name: "CIColorCube")
filter.setValue(ciImage, forKey: kCIInputImageKey)
filter.setDefaults()
var size: UInt = 64
var floatSize = UInt(sizeof(Float))
var cubeDataSize:size_t = size * size * size * floatSize * 4
var colorCubeData:Array<Float> = [
0,0,0,1,
0,0,0,1,
0,0,0,1,
0,0,0,1,
0,0,0,1,
0,0,0,1,
0,0,0,1,
0,0,0,1
]
var cubeData:NSData = NSData(bytesNoCopy: colorCubeData, length: cubeDataSize)
However I get an error when trying to create the cube data:
"Extra argument 'bytesNoCopy' in call"
Basically I am creating the cubeData wrong. Can you advise me on how to properly create the cubeData object in Swift?
Thanks!
Looks like you are after the chroma key filter recipe described here. Here's some code that works. You get a filter for the color you want to make transparent, described by its HSV angle:
func RGBtoHSV(r : Float, g : Float, b : Float) -> (h : Float, s : Float, v : Float) {
var h : CGFloat = 0
var s : CGFloat = 0
var v : CGFloat = 0
let col = UIColor(red: CGFloat(r), green: CGFloat(g), blue: CGFloat(b), alpha: 1.0)
col.getHue(&h, saturation: &s, brightness: &v, alpha: nil)
return (Float(h), Float(s), Float(v))
}
func colorCubeFilterForChromaKey(hueAngle: Float) -> CIFilter {
let hueRange: Float = 60 // degrees size pie shape that we want to replace
let minHueAngle: Float = (hueAngle - hueRange/2.0) / 360
let maxHueAngle: Float = (hueAngle + hueRange/2.0) / 360
let size = 64
var cubeData = [Float](repeating: 0, count: size * size * size * 4)
var rgb: [Float] = [0, 0, 0]
var hsv: (h : Float, s : Float, v : Float)
var offset = 0
for z in 0 ..< size {
rgb[2] = Float(z) / Float(size) // blue value
for y in 0 ..< size {
rgb[1] = Float(y) / Float(size) // green value
for x in 0 ..< size {
rgb[0] = Float(x) / Float(size) // red value
hsv = RGBtoHSV(r: rgb[0], g: rgb[1], b: rgb[2])
// the condition checking hsv.s may need to be removed for your use-case
let alpha: Float = (hsv.h > minHueAngle && hsv.h < maxHueAngle && hsv.s > 0.5) ? 0 : 1.0
cubeData[offset] = rgb[0] * alpha
cubeData[offset + 1] = rgb[1] * alpha
cubeData[offset + 2] = rgb[2] * alpha
cubeData[offset + 3] = alpha
offset += 4
}
}
}
let b = cubeData.withUnsafeBufferPointer { Data(buffer: $0) }
let data = b as NSData
let colorCube = CIFilter(name: "CIColorCube", withInputParameters: [
"inputCubeDimension": size,
"inputCubeData": data
])
return colorCube!
}
Then to get your filter call
let chromaKeyFilter = colorCubeFilterForChromaKey(hueAngle: 120)
I used 120 for your standard green screen.
I believe you want to use NSData(bytes: UnsafePointer<Void>, length: Int) instead of NSData(bytesNoCopy: UnsafeMutablePointer<Void>, length: Int). Make that change and calculate the length in the following way and you should be up and running.
let colorCubeData: [Float] = [
0, 0, 0, 1,
1, 0, 0, 1,
0, 1, 0, 1,
1, 1, 0, 1,
0, 0, 1, 1,
1, 0, 1, 1,
0, 1, 1, 1,
1, 1, 1, 1
]
let cubeData = NSData(bytes: colorCubeData, length: colorCubeData.count * sizeof(Float))

Resources