Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating an SKSpriteNode from the SF Symbols font, in a different color

I'm trying to create an SKSpriteNode with an image from the SF Symbols font, and while I can do it, I can't seem to make it any color other than black.

Here's my code:

let image = UIImage.init(systemName: "gear")
let colored = image!.withTintColor(.red)
let texture = SKTexture.init(image: colored)
let sprite = SKSpriteNode.init(texture: texture, size: CGSize.init(width: 32, height: 32))

Unfortunately, the resultant sprite always comes out in black (and not red).

What am I doing wrong?

like image 248
Jon Grant Avatar asked Jan 25 '23 09:01

Jon Grant


2 Answers

I think the problem is the UIImage is a vector graphic, and only UIImageViews properly handle them. Perhaps we can force it to a bitmap image to get it to work properly.

Here is some experimental code you can try:

let image = UIImage(systemName: "gear").withTintColor(.red)

let data = image.pngData()
let newImage = UIImage(data:data) 
let texture = SKTexture(image: newImage)
let sprite = SKSpriteNode(texture: texture,size: CGSize(width: 32, height: 32))
like image 180
Knight0fDragon Avatar answered Feb 08 '23 17:02

Knight0fDragon


You have to set colorBlendFactor on the sprite.

func makeSprite(symbolName: String) -> SKSpriteNode {
    let image = Image(systemName: symbolName)

    // Thanks Oskar!
    // https://stackoverflow.com/a/69315037/1610473
    // See my adapted version of his code further down; I
    // think you won't need it if you're on iOS, meaning
    // using UIImage, but that's all black magic to me.
    let renderedByOskar = image.renderAsImage()!

    let texture = SKTexture(image: renderedByOskar)
    let sprite = SKSpriteNode(texture: texture)

    sprite.color = .green
    sprite.colorBlendFactor = 1 // <-- This

    return sprite
}

A green symbol, thanks to black magic:

A green symbol, made by black magic

Many thanks to Oskar for the rendering extensions that enable me to do this on macOS, meaning, without UIImage:

class NoInsetHostingView<V>: NSHostingView<V> where V: View {
    override var safeAreaInsets: NSEdgeInsets {
        return .init()
    }
}

extension Image {
    func renderAsImage() -> NSImage? {
        let view = NoInsetHostingView(rootView: self)
        view.setFrameSize(view.fittingSize)
        return view.bitmapImage()
    }
}

public extension NSView {
    func bitmapImage() -> NSImage? {
        guard let rep = bitmapImageRepForCachingDisplay(in: bounds) else {
            return nil
        }

        cacheDisplay(in: bounds, to: rep)

        guard let cgImage = rep.cgImage else {
            return nil
        }

        return NSImage(cgImage: cgImage, size: bounds.size)
    }
}
like image 23
SaganRitual Avatar answered Feb 08 '23 18:02

SaganRitual