Skip to content

Instantly share code, notes, and snippets.

@kpmiller
Last active March 14, 2022 21:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kpmiller/662710e480da44b744f7664c09d6157a to your computer and use it in GitHub Desktop.
Save kpmiller/662710e480da44b744f7664c09d6157a to your computer and use it in GitHub Desktop.
Create a PNG from a SF Symbol for a Stream Deck icon, Mac only (requires pyobjc bridge), tested on MacOS Monterey and python 3.8.9
#!/usr/bin/env python3
import Cocoa #from PyObjC, https://pyobjc.readthedocs.io/
import argparse
import sys, re
#taken from https://stackoverflow.com/questions/29643352/converting-hex-to-rgb-value-in-python
def hex_to_rgb(hx, hsl=False):
"""Converts a HEX code into RGB or HSL.
Args:
hx (str): Takes both short as well as long HEX codes.
hsl (bool): Converts the given HEX code into HSL value if True.
Return:
Tuple of length 3 consisting of either int or float values.
Raise:
ValueError: If given value is not a valid HEX code."""
if re.compile(r'#[a-fA-F0-9]{3}(?:[a-fA-F0-9]{3})?$').match(hx):
div = 255.0 if hsl else 0
if len(hx) <= 4:
return tuple(int(hx[i]*2, 16) / div if div else
int(hx[i]*2, 16) for i in (1, 2, 3))
return tuple(int(hx[i:i+2], 16) / div if div else
int(hx[i:i+2], 16) for i in (1, 3, 5))
raise ValueError(f'"{hx}" is not a valid HEX code.')
parser = argparse.ArgumentParser(description='Make a PNG file from the name of a sf symbol')
parser.add_argument('--size', default=128.0, type=float, help='PNG file will be square and this resolution')
parser.add_argument('--symbol', default="applelogo", help='Name of the SF Symbol')
parser.add_argument('--result', default=None, help='name of the output file')
parser.add_argument('--color', default='#19324B', help='Color for the symbol in HTML format')
parser.add_argument('--bgcolor', default=None, help='If present, a round rect of this HTML color will be drawn behind the SF Symbol')
parser.add_argument('--print-size', action='store_true', help='If specified, script will print the default size of the symbol and exit.')
opts = parser.parse_args()
if opts.result == None:
filename = opts.symbol+'.png'
else:
filename = os.path.abspath(os.path.expanduser(opts.result))
#make the SF symbol NSImage and apply color
r,g,b = hex_to_rgb(opts.color)
imagesf = Cocoa.NSImage.imageWithSystemSymbolName_accessibilityDescription_(opts.symbol, None)
sfconfig = Cocoa.NSImageSymbolConfiguration.configurationWithHierarchicalColor_(Cocoa.NSColor.colorWithRed_green_blue_alpha_(r/255.0, g/255.0, b/255.0, 1.0))
image = imagesf.imageWithSymbolConfiguration_(sfconfig)
sfsymbolsize = image.size()
if opts.print_size:
print(f'Size of {opts.symbol} is width={sfsymbolsize.width} height={sfsymbolsize.height}')
sys.exit(0)
#create the bitmap context
bmrep = Cocoa.NSBitmapImageRep.alloc()
bmrep.initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel_(None, opts.size, opts.size, 8, 4, Cocoa.YES, Cocoa.NO, Cocoa.NSCalibratedRGBColorSpace, 0, 0)
gfxctx = Cocoa.NSGraphicsContext.graphicsContextWithBitmapImageRep_(bmrep)
Cocoa.NSGraphicsContext.setCurrentContext_(gfxctx)
#figure out how big to make the symbol
nw = opts.size
nh = opts.size
x = 0.0
y = 0.0
if sfsymbolsize.width > sfsymbolsize.height:
#wider than tall
scale = opts.size / sfsymbolsize.width
nh = sfsymbolsize.height * scale
y = (opts.size - nh) / 2.0
elif sfsymbolsize.height > sfsymbolsize.width:
#taller than wide
scale = opts.size / sfsymbolsize.height
nw = sfsymbolsize.width * scale
x = (opts.size - nw) / 2.0
#if there's a background color, draw a rounded rect for a stream deck button
if opts.bgcolor != None:
r,g,b = hex_to_rgb(opts.bgcolor)
color = Cocoa.NSColor.colorWithRed_green_blue_alpha_(r/255.0, g/255.0, b/255.0, 1.0)
color.set()
bb = Cocoa.NSMakeRect(0.0, 0.0, opts.size, opts.size)
bp = Cocoa.NSBezierPath.bezierPathWithRoundedRect_xRadius_yRadius_(bb, opts.size * 0.1, opts.size * 0.1)
bp.fill()
#draw the symbol
r = Cocoa.NSMakeRect(x, y, nw, nh)
image.drawInRect_fromRect_operation_fraction_(r, Cocoa.NSZeroRect, Cocoa.NSCompositingOperationSourceOver, 1.0)
#write the output file
pngData = bmrep.representationUsingType_properties_(Cocoa.NSBitmapImageFileTypePNG, {})
pngData.writeToFile_atomically_(filename, Cocoa.YES)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment