1
votes

I want to change a single, specified colour in my SpriteKit sprite to alpha, leaving all other colours unaffected. I don't think that there is an 'easy' way to do this and that a custom shader might be required.

Does anyone know how to do this in Swift or Objective-C?

Here's the image - I design it with a black background as it's easier to see but in my program, I want the background to be transparent.

enter image description here

If I ry to edit the image with the background already set as alpha, then the package I'm using (Pixaki) show this as white-and-grey checks, so it looks like this:

enter image description here

1
Can you update your question with images which show original sprite / desired output ?Whirlwind
Sample image added...Steve Ives
The way I would do this without changing the eyes is to create one that is transparent and white that is just the body. You can then set the sprites color and blendFactor. This will change the white to whatever color you want. You can then add a new layer for the eyes. Not sure if you classify that as 'easy' so I didn't post as an answer =)Skyler Lauren
Yes, but most graphics packages display 'transparent' as grey-and-white checks, which is really hard to edit a white sprite against :-)Steve Ives
You do not want to do this in code, you should set up some process that will convert the specific color to alpha if you are adamant about including it in your xcode project with the color. Here is a post that may help you. stackoverflow.com/questions/17516624/…Knight0fDragon

1 Answers

0
votes
  1. You really should not perform preparing in code those assets which can be processed beforehand.

Here is sample code which does what you want though.

It is based on this answer(bascially just converted to Swift with some modifications). Credits go there.

// color - source color, which is must be replaced
// withColor - target color
// tolerance - value in range from 0 to 1
func replaceColor(color:SKColor, withColor:SKColor, image:UIImage, tolerance:CGFloat) -> UIImage{

    // This function expects to get source color(color which is supposed to be replaced) 
    // and target color in RGBA color space, hence we expect to get 4 color components: r, g, b, a
    assert(CGColorGetNumberOfComponents(color.CGColor) == 4 && CGColorGetNumberOfComponents(withColor.CGColor) == 4,
           "Must be RGBA colorspace")

    // Allocate bitmap in memory with the same width and size as source image
    let imageRef = image.CGImage
    let width = CGImageGetWidth(imageRef)
    let height = CGImageGetHeight(imageRef)

    let colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB)!

    let bytesPerPixel = 4
    let bytesPerRow = bytesPerPixel * width;
    let bitsPerComponent = 8
    let bitmapByteCount = bytesPerRow * height

    let rawData = UnsafeMutablePointer<UInt8>.alloc(bitmapByteCount)

    let context = CGBitmapContextCreate(rawData, width, height, bitsPerComponent, bytesPerRow, colorSpace,
                                        CGImageAlphaInfo.PremultipliedLast.rawValue | CGBitmapInfo.ByteOrder32Big.rawValue)


    let rc = CGRect(x: 0, y: 0, width: width, height: height)

    // Draw source image on created context
    CGContextDrawImage(context, rc, imageRef)


    // Get color components from replacement color
    let withColorComponents = CGColorGetComponents(withColor.CGColor)
    let r2 = UInt8(withColorComponents[0] * 255)
    let g2 = UInt8(withColorComponents[1] * 255)
    let b2 = UInt8(withColorComponents[2] * 255)
    let a2 = UInt8(withColorComponents[3] * 255)

    // Prepare to iterate over image pixels
    var byteIndex = 0

    while byteIndex < bitmapByteCount {

        // Get color of current pixel
        let red:CGFloat = CGFloat(rawData[byteIndex + 0])/255
        let green:CGFloat = CGFloat(rawData[byteIndex + 1])/255
        let blue:CGFloat = CGFloat(rawData[byteIndex + 2])/255
        let alpha:CGFloat = CGFloat(rawData[byteIndex + 3])/255

        let currentColor = SKColor(red: red, green: green, blue: blue, alpha: alpha);

        // Compare two colors using given tolerance value
        if compareColor(color, withColor: currentColor , withTolerance: tolerance) {

            // If the're 'similar', then replace pixel color with given target color
            rawData[byteIndex + 0] = r2
            rawData[byteIndex + 1] = g2
            rawData[byteIndex + 2] = b2
            rawData[byteIndex + 3] = a2
        }

        byteIndex = byteIndex + 4;
    }

    // Retrieve image from memory context
    let imgref = CGBitmapContextCreateImage(context)
    let result = UIImage(CGImage: imgref!)

    // Clean up a bit
    rawData.destroy()

    return result
}

func compareColor(color:SKColor, withColor:SKColor, withTolerance:CGFloat) -> Bool {

    var r1: CGFloat = 0.0, g1: CGFloat = 0.0, b1: CGFloat = 0.0, a1: CGFloat = 0.0;
    var r2: CGFloat = 0.0, g2: CGFloat = 0.0, b2: CGFloat = 0.0, a2: CGFloat = 0.0;

    color.getRed(&r1, green: &g1, blue: &b1, alpha: &a1);
    withColor.getRed(&r2, green: &g2, blue: &b2, alpha: &a2);

    return fabs(r1 - r2) <= withTolerance &&
        fabs(g1 - g2) <= withTolerance &&
        fabs(b1 - b2) <= withTolerance &&
        fabs(a1 - a2) <= withTolerance;
}

You can adjust it and create command line tool or use it directly in your code.

Here is sample project: https://github.com/andrey-str/soa-replace-a-color-colour-in-a-skspritenode

Disclaimer: code may leaking, I'm newbie in Swift.

  1. And here is a simpler way :-)

    $ brew install imagemagick
    $ convert B2CkX.png -fuzz 90% -transparent black result.png
    

90% - is a tolerance value, result looks the same as with code above

Good luck!