desperado desperado - 3 months ago 14
iOS Question

SKSpriteNode created from SKTexture(data:size:) issues with Alpha (Opacity)

I'm having a problem when creating an SKSpriteNode from pixelData.

Below is my code.

var pixelData: [UInt32] = [UInt32](count: 256, repeatedValue: 0xff0000ff)
pixelData.appendContentsOf([UInt32](count: 256, repeatedValue: 0xff00ff00))
pixelData.appendContentsOf([UInt32](count: 256, repeatedValue: 0xffff0000))
pixelData.appendContentsOf([UInt32](count: 256, repeatedValue: 0x000000ff))

let data = NSData(bytes: pixelData, length: pixelData.count * sizeof(UInt32))

let texture = SKTexture(data: data, size: CGSize(width: 32, height: 32))

let node = SKSpriteNode(texture: texture, size: CGSize(width: 32, height: 32))
node.position.x = self.frame.midX
node.position.y = self.frame.midY

self.addChild(node)


It went as I wish for the three bottom rows (which is the first 3 parts in the pixelData array,
0xff0000ff
,
0xff00ff00
,
0xffff0000
: Red, Green, and Blue).

However, I expected the top row to be totally transparent:
0x000000ff
, which should mean rgba(1, 0, 0, 0). But it appeared as this:



I don't know why this happen. Is my understanding of pixelData wrong? Or is it a bug or something in SpriteKit?

Thank you very much for your help!

Answer

From the documentation for the convenience method to convert pixel data to an SKTexture,

The color components should have been already multiplied by the alpha value.

Consequently, the value 0x000000ff is undefined, since the red component cannot be 0xff if the alpha value is 0. Also, it is somewhat non-intuitive to represent a pixel color with a hexadecimal value particularly when the components are in reversed order.

Alternatively, you can define a data structure to represent a color and then pre-multiply each color component by the alpha value during initialization:

struct Color {
    var red:UInt8
    var green:UInt8
    var blue:UInt8
    var alpha:UInt8

    // You can omit the parameters that have default values.
    init(red:UInt8=0, green:UInt8=0, blue:UInt8=0, alpha:UInt8=UInt8.max) {
        let alphaValue:Float = Float(alpha) / 255
        self.red = UInt8(round(Float(red) * alphaValue))
        self.green = UInt8(round(Float(blue) * alphaValue))
        self.blue = UInt8(round(Float(green) * alphaValue))
        self.alpha = alpha
    }
}

You can then define and initialize a pixelData array with the "same" values as your code by

    var pixelData:[Color] = [Color](count: 256, repeatedValue: Color(red: UInt8.max))
    pixelData += [Color](count:256, repeatedValue: Color(green: UInt8.max))
    pixelData += [Color](count:256, repeatedValue: Color(blue: UInt8.max))
    pixelData += [Color](count:256, repeatedValue: Color(red: UInt8.max, alpha: 0))

and create the NSData object with

    let data = NSData(bytes: pixelData, length: pixelData.count * sizeof(Color))