Browse files

Use CoreGraphics and Image I/O to convert icns to png, preserving emb…

…edded ColorSync info under macOS < 10.13
  • Loading branch information...
gregneagle committed Oct 19, 2017
1 parent 49144ed commit 3d7021af8f0718f6e79dba96110b1c4af2cbd356
Showing with 42 additions and 19 deletions.
  1. +42 −19 code/client/munkilib/
@@ -31,8 +31,12 @@
# PyLint cannot properly find names inside Cocoa libraries, so issues bogus
# No name 'Foo' in module 'Bar' warnings. Disable them.
# pylint: disable=E0611
from Foundation import NSData
from AppKit import NSBitmapImageRep, NSPNGFileType
from Foundation import NSURL
from Quartz import (CGImageSourceCreateWithURL, CGImageSourceCreateImageAtIndex,
CGImageDestinationCreateWithURL, CGImageDestinationAddImage,
CGImageSourceGetCount, CGImageSourceCopyPropertiesAtIndex,
kCGImagePropertyDPIHeight, kCGImagePropertyPixelHeight)
# pylint: enable=E0611
from . import display
@@ -43,26 +47,45 @@
# pylint: disable=C0103
def convertIconToPNG(icon_path, destination_path, desired_pixel_height=350):
def convertIconToPNG(icon_path, destination_path,
desired_pixel_height=350, desired_dpi=72):
'''Converts an icns file to a png file, choosing the representation
closest to (but >= if possible) the desired_pixel_height.
Returns True if successful, False otherwise'''
if os.path.exists(icon_path):
image_data = NSData.dataWithContentsOfFile_(icon_path)
bitmap_reps = NSBitmapImageRep.imageRepsWithData_(image_data)
chosen_rep = None
for bitmap_rep in bitmap_reps:
if not chosen_rep:
chosen_rep = bitmap_rep
elif (bitmap_rep.pixelsHigh() >= desired_pixel_height
and bitmap_rep.pixelsHigh() < chosen_rep.pixelsHigh()):
chosen_rep = bitmap_rep
if chosen_rep:
png_data = chosen_rep.representationUsingType_properties_(
NSPNGFileType, None)
png_data.writeToFile_atomically_(destination_path, False)
return True
return False
icns_url = NSURL.fileURLWithPath_(icon_path)
png_url = NSURL.fileURLWithPath_(destination_path)
image_source = CGImageSourceCreateWithURL(icns_url, None)
if not image_source:
return False
number_of_images = CGImageSourceGetCount(image_source)
if number_of_images == 0:
return False
selected_index = 0
candidate = {}
# iterate through the individual icon sizes to find the "best" one
for index in range(number_of_images):
properties = CGImageSourceCopyPropertiesAtIndex(
image_source, index, None)
dpi = int(properties.get(kCGImagePropertyDPIHeight, 0))
height = int(properties.get(kCGImagePropertyPixelHeight, 0))
if (not candidate or
(height < desired_pixel_height and
height > candidate['height']) or
(height >= desired_pixel_height and
height < candidate['height']) or
(height == candidate['height'] and dpi == desired_dpi)):
candidate = {'index': index, 'dpi': dpi, 'height': height}
selected_index = index
except ValueError, err:
image = CGImageSourceCreateImageAtIndex(image_source, selected_index, None)
image_dest = CGImageDestinationCreateWithURL(png_url, 'public.png', 1, None)
CGImageDestinationAddImage(image_dest, image, None)
return CGImageDestinationFinalize(image_dest)
def findIconForApp(app_path):

0 comments on commit 3d7021a

Please sign in to comment.