Skip to content

Instantly share code, notes, and snippets.

@ToolmanP
Created April 21, 2026 08:10
Show Gist options
  • Select an option

  • Save ToolmanP/70ea65f585a5523076a27353f639de39 to your computer and use it in GitHub Desktop.

Select an option

Save ToolmanP/70ea65f585a5523076a27353f639de39 to your computer and use it in GitHub Desktop.
Script to convert Apple color map file (.clr) into GIMP color pallete (.gpl)
#!/usr/bin/env python3
"""Convert Apple .clr files to GIMP/Inkscape .gpl palette format"""
import sys
import plistlib
from pathlib import Path
def parse_ns_components(data):
"""Parse NSComponents byte string like b'0.5 0.2 0.8 1'"""
try:
# Decode bytes to string and split
text = data.decode('utf-8') if isinstance(data, bytes) else data
parts = text.strip().split()
if len(parts) >= 3:
r = float(parts[0])
g = float(parts[1])
b = float(parts[2])
# Normalize 0-1 range to 0-255
return int(r * 255), int(g * 255), int(b * 255)
except (ValueError, AttributeError, IndexError):
pass
return 0, 0, 0
def read_apple_clr(filepath):
"""Read Apple .clr NSKeyedArchiver format and extract colors."""
with open(filepath, 'rb') as f:
data = f.read()
plist = plistlib.loads(data)
colors = []
if not isinstance(plist, dict):
return colors
objects = plist.get('$objects', [])
top = plist.get('$top', {})
# Get the keys (color names) and colors arrays
keys_ref = top.get('NSKeys')
colors_ref = top.get('NSColors')
if not keys_ref or not colors_ref:
return colors
# Dereference UIDs
if isinstance(keys_ref, plistlib.UID):
keys_obj = objects[keys_ref.data] if keys_ref.data < len(objects) else None
else:
keys_obj = None
if isinstance(colors_ref, plistlib.UID):
colors_obj = objects[colors_ref.data] if colors_ref.data < len(objects) else None
else:
colors_obj = None
if not keys_obj or not colors_obj:
return colors
# Get the arrays of references
key_refs = keys_obj.get('NS.objects', []) if isinstance(keys_obj, dict) else []
color_refs = colors_obj.get('NS.objects', []) if isinstance(colors_obj, dict) else []
# Match up names with colors
for name_ref, color_ref in zip(key_refs, color_refs):
# Get name
name = 'Untitled'
if isinstance(name_ref, plistlib.UID):
name_data = objects[name_ref.data]
if isinstance(name_data, str):
name = name_data
# Get color
r, g, b = 0, 0, 0
if isinstance(color_ref, plistlib.UID):
color_data = objects[color_ref.data]
if isinstance(color_data, dict):
# Parse NSComponents
ns_components = color_data.get('NSComponents')
if ns_components:
r, g, b = parse_ns_components(ns_components)
colors.append((name, r, g, b))
return colors
def write_gpl(colors, output_path, palette_name=None):
"""Write colors to GIMP/Inkscape .gpl palette file."""
if palette_name is None:
palette_name = Path(output_path).stem
lines = [
"GIMP Palette",
f"Name: {palette_name}",
"Columns: 4",
"#",
]
for name, r, g, b in colors:
# Clamp values to 0-255
r = max(0, min(255, r))
g = max(0, min(255, g))
b = max(0, min(255, b))
lines.append(f"{r:3d} {g:3d} {b:3d}\t{name}")
with open(output_path, 'w') as f:
f.write('\n'.join(lines) + '\n')
def main():
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <input.clr> [output.gpl]")
print("Converts Apple .clr color palette to GIMP/Inkscape .gpl format")
sys.exit(1)
input_file = sys.argv[1]
if len(sys.argv) >= 3:
output_file = sys.argv[2]
else:
output_file = str(Path(input_file).with_suffix('.gpl'))
palette_name = Path(input_file).stem
try:
colors = read_apple_clr(input_file)
if not colors:
print("No colors found in file!")
sys.exit(1)
write_gpl(colors, output_file, palette_name)
print(f"Converted {len(colors)} colors: {input_file} -> {output_file}")
except Exception as e:
print(f"Error: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment