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''' 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): try: properties = CGImageSourceCopyPropertiesAtIndex( image_source, index, None) # perform not empty check for properties to prevent crash as CGImageSourceCopyPropertiesAtIndex sometimes fails in 10.15.4 and above if not properties: return False 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: pass 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 badge_disk_icon(badge_file, output_file): # Load the Removable disk icon url = CFURLCreateWithFileSystemPath(None, _REMOVABLE_DISK_PATH, kCFURLPOSIXPathStyle, False) backdrop = CGImageSourceCreateWithURL(url, None) backdropCount = CGImageSourceGetCount(backdrop) # Load the badge url = CFURLCreateWithFileSystemPath(None, badge_file, kCFURLPOSIXPathStyle, False) badge = CGImageSourceCreateWithURL(url, None) assert badge is not None, 'Unable to process image file: %s' % badge_file badgeCount = CGImageSourceGetCount(badge) # Set up a destination for our target url = CFURLCreateWithFileSystemPath(None, output_file, kCFURLPOSIXPathStyle, False) target = CGImageDestinationCreateWithURL(url, 'com.apple.icns', backdropCount, None) # Get the RGB colorspace rgbColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB) # Scale scale = 1.0 # Perspective transform corners = ((0.2, 0.95), (0.8, 0.95), (0.85, 0.35), (0.15, 0.35)) # Translation position = (0.5, 0.5) for n in range(backdropCount): props = CGImageSourceCopyPropertiesAtIndex(backdrop, n, None) width = props['PixelWidth'] height = props['PixelHeight'] dpi = props['DPIWidth'] depth = props['Depth'] # Choose the best sized badge image bestWidth = None # bestHeight = None bestBadge = None bestDepth = None # bestDPI = None for m in range(badgeCount): badgeProps = CGImageSourceCopyPropertiesAtIndex(badge, m, None) badgeWidth = badgeProps['PixelWidth'] # badgeHeight = badgeProps['PixelHeight'] badgeDPI = badgeProps['DPIWidth'] badgeDepth = badgeProps['Depth'] if (bestBadge is None or (badgeWidth <= width and (bestWidth > width or badgeWidth > bestWidth or (badgeWidth == bestWidth and badgeDPI == dpi and badgeDepth <= depth and (bestDepth is None or badgeDepth > bestDepth))))): bestBadge = m bestWidth = badgeWidth # bestHeight = badgeHeight # bestDPI = badgeDPI bestDepth = badgeDepth badgeImage = CGImageSourceCreateImageAtIndex(badge, bestBadge, None) badgeCI = CIImage.imageWithCGImage_(badgeImage) backgroundImage = CGImageSourceCreateImageAtIndex(backdrop, n, None) backgroundCI = CIImage.imageWithCGImage_(backgroundImage) compositor = CIFilter.filterWithName_('CISourceOverCompositing') lanczos = CIFilter.filterWithName_('CILanczosScaleTransform') perspective = CIFilter.filterWithName_('CIPerspectiveTransform') transform = CIFilter.filterWithName_('CIAffineTransform') lanczos.setValue_forKey_(badgeCI, kCIInputImageKey) lanczos.setValue_forKey_(scale * float(width) / bestWidth, kCIInputScaleKey) lanczos.setValue_forKey_(1.0, kCIInputAspectRatioKey) topLeft = (width * scale * corners[0][0], width * scale * corners[0][1]) topRight = (width * scale * corners[1][0], width * scale * corners[1][1]) bottomRight = (width * scale * corners[2][0], width * scale * corners[2][1]) bottomLeft = (width * scale * corners[3][0], width * scale * corners[3][1]) out = lanczos.valueForKey_(kCIOutputImageKey) if width >= 16: perspective.setValue_forKey_(out, kCIInputImageKey) perspective.setValue_forKey_(CIVector.vectorWithX_Y_(*topLeft), 'inputTopLeft') perspective.setValue_forKey_(CIVector.vectorWithX_Y_(*topRight), 'inputTopRight') perspective.setValue_forKey_(CIVector.vectorWithX_Y_(*bottomRight), 'inputBottomRight') perspective.setValue_forKey_(CIVector.vectorWithX_Y_(*bottomLeft), 'inputBottomLeft') out = perspective.valueForKey_(kCIOutputImageKey) tfm = NSAffineTransform.transform() tfm.translateXBy_yBy_(math.floor((position[0] - 0.5 * scale) * width), math.floor((position[1] - 0.5 * scale) * height)) transform.setValue_forKey_(out, kCIInputImageKey) transform.setValue_forKey_(tfm, 'inputTransform') out = transform.valueForKey_(kCIOutputImageKey) compositor.setValue_forKey_(out, kCIInputImageKey) compositor.setValue_forKey_(backgroundCI, kCIInputBackgroundImageKey) result = compositor.valueForKey_(kCIOutputImageKey) cgContext = CGBitmapContextCreate(None, width, height, 8, 0, rgbColorSpace, kCGImageAlphaPremultipliedLast) context = CIContext.contextWithCGContext_options_(cgContext, None) context.drawImage_inRect_fromRect_(result, ((0, 0), (width, height)), ((0, 0), (width, height))) image = CGBitmapContextCreateImage(cgContext) CGImageDestinationAddImage(target, image, props) CGImageDestinationFinalize(target)