Search code examples
iosswiftsprite-kitrgbasktexture

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!


Solution

  • 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))