sesc360 sesc360 - 4 months ago 19
Swift Question

Looping through pixels in pictures

I have written the following function:

class func convertImageToBlackAndWhite(#data:UnsafeMutablePointer<UInt32>, inputWidth:UInt32, inputHeight:UInt32) -> UnsafeMutablePointer<UInt32> {
var currentPixel = UnsafeMutablePointer<UInt32>()
currentPixel = data

for (var i = 0; i < Int(inputHeight); i++) {
for (var j = 0; j < Int(inputWidth); j++) {
var color:UInt32 = currentPixel.memory

// Average of RGB = greyscale
var averageColor:UInt32 = (R(color) + G(color) + B(color)) / 3
var updatedColor:UInt32 = RGBAMake(averageColor, g: averageColor, b: averageColor, a: A(color))
currentPixel.memory = updatedColor
currentPixel++
}
}

var outputData = UnsafeMutablePointer<UInt32>((calloc(UInt(inputHeight*inputWidth), UInt(sizeof(UInt32)))))
outputData = currentPixel
return outputData
}


I want to return the outputData to display it on screen. Therefore I created within another function this code:

class func processUsingPixels(#inputImage:UIImage) -> UIImage {

var inputCGImage:CGImageRef = inputImage.CGImage
var colorSpace:CGColorSpaceRef = CGColorSpaceCreateDeviceRGB()
var inputWidth:UInt = CGImageGetWidth(inputCGImage)
var inputHeight:UInt = CGImageGetHeight(inputCGImage)
var bytesPerPixel:UInt = 4
var bitsPerComponent:UInt = 8
var inputBytesPerRow = bytesPerPixel * inputWidth

let bitmapInfo = CGBitmapInfo(CGBitmapInfo.ByteOrder32Big.rawValue | CGImageAlphaInfo.PremultipliedLast.rawValue)

let inputPixels = UnsafeMutablePointer<UInt32>(calloc(UInt(inputHeight*inputWidth), UInt(sizeof(UInt32))))

var outputData = UnsafeMutablePointer<UInt32>()
outputData = self.convertImageToBlackAndWhite(data: inputPixels, inputWidth: UInt32(inputWidth), inputHeight: UInt32(inputHeight))


let context:CGContextRef = CGBitmapContextCreate(outputData, inputWidth, inputHeight, bitsPerComponent, inputBytesPerRow, colorSpace, bitmapInfo)
CGContextDrawImage(context, CGRectMake(0, 0, CGFloat(inputWidth), CGFloat(inputHeight)), inputCGImage)


return inputImage
}


The outputData was the data I wanted now to display. I expected that the changes by the for-loop in "ConvertImageToBlackAndWhite" would be reflected, but no change at all happens.

Thanks to Rob I got some valuable insights before. but still this picture does not change at all.

my questions would be:

1) is it correct to create this output data var as a return value? Does it make sense at all?

2) I use this outputData Variable that has been returned to create a Context and Draw the context. But this has no effect. Do I need to create a new Image first? The return value of processusingPixels is an UIImage. But I thought the CGContextDrawImage would be sufficient. Is that wrong?

Rob Rob
Answer

You are initializing currentPixel inside the for loops. Do that before you start the loops, e.g.:

var currentPixel = inputPixels

for row in 0 ..< inputHeight {
    for column in 0 ..< inputWidth {
        var color: UInt32 = currentPixel.memory
        print(String(format:"%X", color))
        currentPixel++
    }
}

When you're done, you can create new image using CGBitmapContextCreateImage:

func processUsingPixels(inputImage inputImage: UIImage) -> UIImage {
    let inputCGImage = inputImage.CGImage
    let colorSpace = CGColorSpaceCreateDeviceRGB()
    let width = CGImageGetWidth(inputCGImage)
    let height = CGImageGetHeight(inputCGImage)
    let bytesPerPixel: UInt = 4
    let bitsPerComponent: UInt = 8
    let bytesPerRow = bytesPerPixel * width
    let bitmapInfo = CGBitmapInfo.ByteOrder32Big.rawValue | CGImageAlphaInfo.PremultipliedLast.rawValue

    let context:CGContextRef = CGBitmapContextCreate(nil, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo)
    CGContextDrawImage(context, CGRectMake(0, 0, CGFloat(width), CGFloat(height)), inputCGImage)

    let pixelBuffer = UnsafeMutablePointer<UInt32>(CGBitmapContextGetData(context))

    var currentPixel = pixelBuffer

    for row in 0 ..< inputHeight {
        for column in 0 ..< inputWidth {
            currentPixel.memory = grayScaleFromColor(currentPixel.memory)
            currentPixel++
        }
    }

    let outputCGImage = CGBitmapContextCreateImage(context)
    return UIImage(CGImage: outputCGImage)!
}

func grayScaleFromColor(color: UInt32) -> UInt32 {
    let r = Double(red(color))
    let g = Double(green(color))
    let b = Double(blue(color))
    let a = alpha(color)

    let gray = UInt8(0.2126 * r + 0.7152 * g + 0.0722 * b)

    return rgba(red: gray, green: gray, blue: gray, alpha: a)
}

Note, I removed calloc from the above. If you keep that in your routine, make sure to free it.


Implicit in the original question were functions to extract RGBA values from the UInt32 color, and also to create a color from the RGBA values. For the sake of completeness, I'll include some sample renditions:

func red(color: UInt32) -> UInt8 {
    return UInt8(color & 255)
}

func green(color: UInt32) -> UInt8 {
    return UInt8((color >> 8) & 255)
}

func blue(color: UInt32) -> UInt8 {
    return UInt8((color >> 16) & 255)
}

func alpha(color: UInt32) -> UInt8 {
    return UInt8((color >> 24) & 255)
}

func rgba(#red: UInt8, green: UInt8, blue: UInt8, alpha: UInt8) -> UInt32 {
    return UInt32(red) | (UInt32(green) << 8) | (UInt32(blue) << 16) | (UInt32(alpha) << 24)
}

Personally, I don't really like playing around with this shifting of bits, so I'd naturally be inclined to use a struct (like I did in this answer; that used PremultipliedFirst rather than the PremultipliedLast used in the question here, but the concept would be the same). But access/manipulate the buffer any way you want.

Comments