From 6991d84e2ec9bc76b7f2ca3316474d0c080cf96b Mon Sep 17 00:00:00 2001 From: Joey Payne Date: Thu, 23 Apr 2015 12:16:25 -0600 Subject: [PATCH] Added support for Mac ICNS icon display when loading ICNS files. --- files/nw-versions.txt | 1 + icns_info.py | 845 ++++++++++++++++++++++++++++++++++++++---- main.py | 12 +- pycns.py | 8 +- 4 files changed, 789 insertions(+), 77 deletions(-) diff --git a/files/nw-versions.txt b/files/nw-versions.txt index 6fb6ce8..a96ff0d 100644 --- a/files/nw-versions.txt +++ b/files/nw-versions.txt @@ -1,3 +1,4 @@ +0.12.1 0.12.0 0.12.0-rc1 0.12.0-alpha3 diff --git a/icns_info.py b/icns_info.py index 9a30063..ccddeb8 100644 --- a/icns_info.py +++ b/icns_info.py @@ -1,64 +1,66 @@ import struct import image_utils import png - +from PIL import Image +import os +from cStringIO import StringIO #---------------------CONSTANTS-----------------------------------------------# -ICNS_TABLE_OF_CONTENTS = 0x544F4320 # "TOC " +ICNS_TABLE_OF_CONTENTS = 0x544F4320 # "TOC " -ICNS_ICON_VERSION = 0x69636E56 # "icnV" +ICNS_ICON_VERSION = 0x69636E56 # "icnV" -ICNS_1024x1024_32BIT_ARGB_DATA = 0x69633130 # "ic10" +ICNS_1024x1024_32BIT_ARGB_DATA = 0x69633130 # "ic10" -ICNS_512x512_32BIT_ARGB_DATA = 0x69633039 # "ic09" -ICNS_256x256_32BIT_ARGB_DATA = 0x69633038 # "ic08" +ICNS_512x512_32BIT_ARGB_DATA = 0x69633039 # "ic09" +ICNS_256x256_32BIT_ARGB_DATA = 0x69633038 # "ic08" -ICNS_128x128_32BIT_DATA = 0x69743332 # "it32" -ICNS_128x128_8BIT_MASK = 0x74386D6B # "t8mk" +ICNS_128x128_32BIT_DATA = 0x69743332 # "it32" +ICNS_128x128_8BIT_MASK = 0x74386D6B # "t8mk" -ICNS_48x48_1BIT_DATA = 0x69636823 # "ich#" -ICNS_48x48_4BIT_DATA = 0x69636834 # "ich4" -ICNS_48x48_8BIT_DATA = 0x69636838 # "ich8" -ICNS_48x48_32BIT_DATA = 0x69683332 # "ih32" -ICNS_48x48_1BIT_MASK = 0x69636823 # "ich#" -ICNS_48x48_8BIT_MASK = 0x68386D6B # "h8mk" +ICNS_48x48_1BIT_DATA = 0x69636823 # "ich#" +ICNS_48x48_4BIT_DATA = 0x69636834 # "ich4" +ICNS_48x48_8BIT_DATA = 0x69636838 # "ich8" +ICNS_48x48_32BIT_DATA = 0x69683332 # "ih32" +ICNS_48x48_1BIT_MASK = 0x69636823 # "ich#" +ICNS_48x48_8BIT_MASK = 0x68386D6B # "h8mk" -ICNS_32x32_1BIT_DATA = 0x49434E23 # "ICN#" -ICNS_32x32_4BIT_DATA = 0x69636C34 # "icl4" -ICNS_32x32_8BIT_DATA = 0x69636C38 # "icl8" -ICNS_32x32_32BIT_DATA = 0x696C3332 # "il32" -ICNS_32x32_1BIT_MASK = 0x49434E23 # "ICN#" -ICNS_32x32_8BIT_MASK = 0x6C386D6B # "l8mk" +ICNS_32x32_1BIT_DATA = 0x49434E23 # "ICN#" +ICNS_32x32_4BIT_DATA = 0x69636C34 # "icl4" +ICNS_32x32_8BIT_DATA = 0x69636C38 # "icl8" +ICNS_32x32_32BIT_DATA = 0x696C3332 # "il32" +ICNS_32x32_1BIT_MASK = 0x49434E23 # "ICN#" +ICNS_32x32_8BIT_MASK = 0x6C386D6B # "l8mk" -ICNS_16x16_1BIT_DATA = 0x69637323 # "ics#" -ICNS_16x16_4BIT_DATA = 0x69637334 # "ics4" -ICNS_16x16_8BIT_DATA = 0x69637338 # "ics8" -ICNS_16x16_32BIT_DATA = 0x69733332 # "is32" -ICNS_16x16_1BIT_MASK = 0x69637323 # "ics#" -ICNS_16x16_8BIT_MASK = 0x73386D6B # "s8mk" +ICNS_16x16_1BIT_DATA = 0x69637323 # "ics#" +ICNS_16x16_4BIT_DATA = 0x69637334 # "ics4" +ICNS_16x16_8BIT_DATA = 0x69637338 # "ics8" +ICNS_16x16_32BIT_DATA = 0x69733332 # "is32" +ICNS_16x16_1BIT_MASK = 0x69637323 # "ics#" +ICNS_16x16_8BIT_MASK = 0x73386D6B # "s8mk" -ICNS_16x12_1BIT_DATA = 0x69636D23 # "icm#" -ICNS_16x12_4BIT_DATA = 0x69636D34 # "icm4" -ICNS_16x12_1BIT_MASK = 0x69636D23 # "icm#" -ICNS_16x12_8BIT_DATA = 0x69636D38 # "icm8" +ICNS_16x12_1BIT_DATA = 0x69636D23 # "icm#" +ICNS_16x12_4BIT_DATA = 0x69636D34 # "icm4" +ICNS_16x12_1BIT_MASK = 0x69636D23 # "icm#" +ICNS_16x12_8BIT_DATA = 0x69636D38 # "icm8" -ICNS_32x32_1BIT_ICON = 0x49434F4E # "ICON" +ICNS_32x32_1BIT_ICON = 0x49434F4E # "ICON" -ICNS_TILE_VARIANT = 0x74696C65 # "tile" -ICNS_ROLLOVER_VARIANT = 0x6F766572 # "over" -ICNS_DROP_VARIANT = 0x64726F70 # "drop" -ICNS_OPEN_VARIANT = 0x6F70656E # "open" -ICNS_OPEN_DROP_VARIANT = 0x6F647270 # "odrp" +ICNS_TILE_VARIANT = 0x74696C65 # "tile" +ICNS_ROLLOVER_VARIANT = 0x6F766572 # "over" +ICNS_DROP_VARIANT = 0x64726F70 # "drop" +ICNS_OPEN_VARIANT = 0x6F70656E # "open" +ICNS_OPEN_DROP_VARIANT = 0x6F647270 # "odrp" ICNS_NULL_DATA = 0x00000000 ICNS_NULL_MASK = 0x00000000 # icns file / resource type constants -ICNS_FAMILY_TYPE = 0x69636E73 # "icns" +ICNS_FAMILY_TYPE = 0x69636E73 # "icns" -ICNS_MACBINARY_TYPE = 0x6D42494E # "mBIN" +ICNS_MACBINARY_TYPE = 0x6D42494E # "mBIN" ICNS_NULL_TYPE = 0x00000000 @@ -79,43 +81,322 @@ ICNS_STATUS_UNSUPPORTED = 4 #---------------------------SYMBOL DICTS-------------------------------------# -struct_symbols = {1:'B',#byte - 2:'H',#word - 4:'I',#unsigned int/double word - 8:'Q' #quad word - } +struct_symbols = {1: 'B', # byte + 2: 'H', # word + 4: 'I', # unsigned int/double word + 8: 'Q' # quad word + } #used to easily get the icon type from dimensions -type_dict = {'mask':{128:{8:ICNS_128x128_8BIT_MASK}, - 48:{1:ICNS_48x48_1BIT_MASK, - 8:ICNS_48x48_8BIT_MASK}, - 32:{1:ICNS_32x32_1BIT_MASK, - 8:ICNS_32x32_8BIT_MASK}, - 16:{1:ICNS_16x16_1BIT_MASK, - 8:ICNS_16x16_8BIT_MASK}, +type_dict = {'mask': {128: {8: ICNS_128x128_8BIT_MASK}, + 48: {1: ICNS_48x48_1BIT_MASK, + 8: ICNS_48x48_8BIT_MASK}, + 32: {1: ICNS_32x32_1BIT_MASK, + 8: ICNS_32x32_8BIT_MASK}, + 16: {1: ICNS_16x16_1BIT_MASK, + 8: ICNS_16x16_8BIT_MASK}, }, - 'data':{1024:{32:ICNS_1024x1024_32BIT_ARGB_DATA}, - 512:{32:ICNS_512x512_32BIT_ARGB_DATA}, - 256:{32:ICNS_256x256_32BIT_ARGB_DATA}, - 128:{32:ICNS_128x128_32BIT_DATA}, - 48:{1:ICNS_48x48_1BIT_DATA, - 4:ICNS_48x48_4BIT_DATA, - 8:ICNS_48x48_8BIT_DATA, - 32:ICNS_48x48_32BIT_DATA}, - 32:{1:ICNS_32x32_1BIT_DATA, - 4:ICNS_32x32_4BIT_DATA, - 8:ICNS_32x32_8BIT_DATA, - 32:ICNS_32x32_32BIT_DATA}, - 16:{1:ICNS_16x16_1BIT_DATA, - 4:ICNS_16x16_4BIT_DATA, - 8:ICNS_16x16_8BIT_DATA, - 32:ICNS_16x16_32BIT_DATA}, + 'data': {1024: {32: ICNS_1024x1024_32BIT_ARGB_DATA}, + 512: {32: ICNS_512x512_32BIT_ARGB_DATA}, + 256: {32: ICNS_256x256_32BIT_ARGB_DATA}, + 128: {32: ICNS_128x128_32BIT_DATA}, + 48: {1: ICNS_48x48_1BIT_DATA, + 4: ICNS_48x48_4BIT_DATA, + 8: ICNS_48x48_8BIT_DATA, + 32: ICNS_48x48_32BIT_DATA}, + 32: {1: ICNS_32x32_1BIT_DATA, + 4: ICNS_32x32_4BIT_DATA, + 8: ICNS_32x32_8BIT_DATA, + 32: ICNS_32x32_32BIT_DATA}, + 16: {1: ICNS_16x16_1BIT_DATA, + 4: ICNS_16x16_4BIT_DATA, + 8: ICNS_16x16_8BIT_DATA, + 32: ICNS_16x16_32BIT_DATA}, }} + +icns_colormap_4 = [ + [0xFF, 0xFF, 0xFF], + [0xFC, 0xF3, 0x05], + [0xFF, 0x64, 0x02], + [0xDD, 0x08, 0x06], + [0xF2, 0x08, 0x84], + [0x46, 0x00, 0xA5], + [0x00, 0x00, 0xD4], + [0x02, 0xAB, 0xEA], + [0x1F, 0xB7, 0x14], + [0x00, 0x64, 0x11], + [0x56, 0x2C, 0x05], + [0x90, 0x71, 0x3A], + [0xC0, 0xC0, 0xC0], + [0x80, 0x80, 0x80], + [0x40, 0x40, 0x40], + [0x00, 0x00, 0x00] +] + +icns_colormap_8 =[ + [0xFF, 0xFF, 0xFF], + [0xFF, 0xFF, 0xCC], + [0xFF, 0xFF, 0x99], + [0xFF, 0xFF, 0x66], + [0xFF, 0xFF, 0x33], + [0xFF, 0xFF, 0x00], + [0xFF, 0xCC, 0xFF], + [0xFF, 0xCC, 0xCC], + [0xFF, 0xCC, 0x99], + [0xFF, 0xCC, 0x66], + [0xFF, 0xCC, 0x33], + [0xFF, 0xCC, 0x00], + [0xFF, 0x99, 0xFF], + [0xFF, 0x99, 0xCC], + [0xFF, 0x99, 0x99], + [0xFF, 0x99, 0x66], + [0xFF, 0x99, 0x33], + [0xFF, 0x99, 0x00], + [0xFF, 0x66, 0xFF], + [0xFF, 0x66, 0xCC], + [0xFF, 0x66, 0x99], + [0xFF, 0x66, 0x66], + [0xFF, 0x66, 0x33], + [0xFF, 0x66, 0x00], + [0xFF, 0x33, 0xFF], + [0xFF, 0x33, 0xCC], + [0xFF, 0x33, 0x99], + [0xFF, 0x33, 0x66], + [0xFF, 0x33, 0x33], + [0xFF, 0x33, 0x00], + [0xFF, 0x00, 0xFF], + [0xFF, 0x00, 0xCC], + [0xFF, 0x00, 0x99], + [0xFF, 0x00, 0x66], + [0xFF, 0x00, 0x33], + [0xFF, 0x00, 0x00], + [0xCC, 0xFF, 0xFF], + [0xCC, 0xFF, 0xCC], + [0xCC, 0xFF, 0x99], + [0xCC, 0xFF, 0x66], + [0xCC, 0xFF, 0x33], + [0xCC, 0xFF, 0x00], + [0xCC, 0xCC, 0xFF], + [0xCC, 0xCC, 0xCC], + [0xCC, 0xCC, 0x99], + [0xCC, 0xCC, 0x66], + [0xCC, 0xCC, 0x33], + [0xCC, 0xCC, 0x00], + [0xCC, 0x99, 0xFF], + [0xCC, 0x99, 0xCC], + [0xCC, 0x99, 0x99], + [0xCC, 0x99, 0x66], + [0xCC, 0x99, 0x33], + [0xCC, 0x99, 0x00], + [0xCC, 0x66, 0xFF], + [0xCC, 0x66, 0xCC], + [0xCC, 0x66, 0x99], + [0xCC, 0x66, 0x66], + [0xCC, 0x66, 0x33], + [0xCC, 0x66, 0x00], + [0xCC, 0x33, 0xFF], + [0xCC, 0x33, 0xCC], + [0xCC, 0x33, 0x99], + [0xCC, 0x33, 0x66], + [0xCC, 0x33, 0x33], + [0xCC, 0x33, 0x00], + [0xCC, 0x00, 0xFF], + [0xCC, 0x00, 0xCC], + [0xCC, 0x00, 0x99], + [0xCC, 0x00, 0x66], + [0xCC, 0x00, 0x33], + [0xCC, 0x00, 0x00], + [0x99, 0xFF, 0xFF], + [0x99, 0xFF, 0xCC], + [0x99, 0xFF, 0x99], + [0x99, 0xFF, 0x66], + [0x99, 0xFF, 0x33], + [0x99, 0xFF, 0x00], + [0x99, 0xCC, 0xFF], + [0x99, 0xCC, 0xCC], + [0x99, 0xCC, 0x99], + [0x99, 0xCC, 0x66], + [0x99, 0xCC, 0x33], + [0x99, 0xCC, 0x00], + [0x99, 0x99, 0xFF], + [0x99, 0x99, 0xCC], + [0x99, 0x99, 0x99], + [0x99, 0x99, 0x66], + [0x99, 0x99, 0x33], + [0x99, 0x99, 0x00], + [0x99, 0x66, 0xFF], + [0x99, 0x66, 0xCC], + [0x99, 0x66, 0x99], + [0x99, 0x66, 0x66], + [0x99, 0x66, 0x33], + [0x99, 0x66, 0x00], + [0x99, 0x33, 0xFF], + [0x99, 0x33, 0xCC], + [0x99, 0x33, 0x99], + [0x99, 0x33, 0x66], + [0x99, 0x33, 0x33], + [0x99, 0x33, 0x00], + [0x99, 0x00, 0xFF], + [0x99, 0x00, 0xCC], + [0x99, 0x00, 0x99], + [0x99, 0x00, 0x66], + [0x99, 0x00, 0x33], + [0x99, 0x00, 0x00], + [0x66, 0xFF, 0xFF], + [0x66, 0xFF, 0xCC], + [0x66, 0xFF, 0x99], + [0x66, 0xFF, 0x66], + [0x66, 0xFF, 0x33], + [0x66, 0xFF, 0x00], + [0x66, 0xCC, 0xFF], + [0x66, 0xCC, 0xCC], + [0x66, 0xCC, 0x99], + [0x66, 0xCC, 0x66], + [0x66, 0xCC, 0x33], + [0x66, 0xCC, 0x00], + [0x66, 0x99, 0xFF], + [0x66, 0x99, 0xCC], + [0x66, 0x99, 0x99], + [0x66, 0x99, 0x66], + [0x66, 0x99, 0x33], + [0x66, 0x99, 0x00], + [0x66, 0x66, 0xFF], + [0x66, 0x66, 0xCC], + [0x66, 0x66, 0x99], + [0x66, 0x66, 0x66], + [0x66, 0x66, 0x33], + [0x66, 0x66, 0x00], + [0x66, 0x33, 0xFF], + [0x66, 0x33, 0xCC], + [0x66, 0x33, 0x99], + [0x66, 0x33, 0x66], + [0x66, 0x33, 0x33], + [0x66, 0x33, 0x00], + [0x66, 0x00, 0xFF], + [0x66, 0x00, 0xCC], + [0x66, 0x00, 0x99], + [0x66, 0x00, 0x66], + [0x66, 0x00, 0x33], + [0x66, 0x00, 0x00], + [0x33, 0xFF, 0xFF], + [0x33, 0xFF, 0xCC], + [0x33, 0xFF, 0x99], + [0x33, 0xFF, 0x66], + [0x33, 0xFF, 0x33], + [0x33, 0xFF, 0x00], + [0x33, 0xCC, 0xFF], + [0x33, 0xCC, 0xCC], + [0x33, 0xCC, 0x99], + [0x33, 0xCC, 0x66], + [0x33, 0xCC, 0x33], + [0x33, 0xCC, 0x00], + [0x33, 0x99, 0xFF], + [0x33, 0x99, 0xCC], + [0x33, 0x99, 0x99], + [0x33, 0x99, 0x66], + [0x33, 0x99, 0x33], + [0x33, 0x99, 0x00], + [0x33, 0x66, 0xFF], + [0x33, 0x66, 0xCC], + [0x33, 0x66, 0x99], + [0x33, 0x66, 0x66], + [0x33, 0x66, 0x33], + [0x33, 0x66, 0x00], + [0x33, 0x33, 0xFF], + [0x33, 0x33, 0xCC], + [0x33, 0x33, 0x99], + [0x33, 0x33, 0x66], + [0x33, 0x33, 0x33], + [0x33, 0x33, 0x00], + [0x33, 0x00, 0xFF], + [0x33, 0x00, 0xCC], + [0x33, 0x00, 0x99], + [0x33, 0x00, 0x66], + [0x33, 0x00, 0x33], + [0x33, 0x00, 0x00], + [0x00, 0xFF, 0xFF], + [0x00, 0xFF, 0xCC], + [0x00, 0xFF, 0x99], + [0x00, 0xFF, 0x66], + [0x00, 0xFF, 0x33], + [0x00, 0xFF, 0x00], + [0x00, 0xCC, 0xFF], + [0x00, 0xCC, 0xCC], + [0x00, 0xCC, 0x99], + [0x00, 0xCC, 0x66], + [0x00, 0xCC, 0x33], + [0x00, 0xCC, 0x00], + [0x00, 0x99, 0xFF], + [0x00, 0x99, 0xCC], + [0x00, 0x99, 0x99], + [0x00, 0x99, 0x66], + [0x00, 0x99, 0x33], + [0x00, 0x99, 0x00], + [0x00, 0x66, 0xFF], + [0x00, 0x66, 0xCC], + [0x00, 0x66, 0x99], + [0x00, 0x66, 0x66], + [0x00, 0x66, 0x33], + [0x00, 0x66, 0x00], + [0x00, 0x33, 0xFF], + [0x00, 0x33, 0xCC], + [0x00, 0x33, 0x99], + [0x00, 0x33, 0x66], + [0x00, 0x33, 0x33], + [0x00, 0x33, 0x00], + [0x00, 0x00, 0xFF], + [0x00, 0x00, 0xCC], + [0x00, 0x00, 0x99], + [0x00, 0x00, 0x66], + [0x00, 0x00, 0x33], + [0xEE, 0x00, 0x00], + [0xDD, 0x00, 0x00], + [0xBB, 0x00, 0x00], + [0xAA, 0x00, 0x00], + [0x88, 0x00, 0x00], + [0x77, 0x00, 0x00], + [0x55, 0x00, 0x00], + [0x44, 0x00, 0x00], + [0x22, 0x00, 0x00], + [0x11, 0x00, 0x00], + [0x00, 0xEE, 0x00], + [0x00, 0xDD, 0x00], + [0x00, 0xBB, 0x00], + [0x00, 0xAA, 0x00], + [0x00, 0x88, 0x00], + [0x00, 0x77, 0x00], + [0x00, 0x55, 0x00], + [0x00, 0x44, 0x00], + [0x00, 0x22, 0x00], + [0x00, 0x11, 0x00], + [0x00, 0x00, 0xEE], + [0x00, 0x00, 0xDD], + [0x00, 0x00, 0xBB], + [0x00, 0x00, 0xAA], + [0x00, 0x00, 0x88], + [0x00, 0x00, 0x77], + [0x00, 0x00, 0x55], + [0x00, 0x00, 0x44], + [0x00, 0x00, 0x22], + [0x00, 0x00, 0x11], + [0xEE, 0xEE, 0xEE], + [0xDD, 0xDD, 0xDD], + [0xBB, 0xBB, 0xBB], + [0xAA, 0xAA, 0xAA], + [0x88, 0x88, 0x88], + [0x77, 0x77, 0x77], + [0x55, 0x55, 0x55], + [0x44, 0x44, 0x44], + [0x22, 0x22, 0x22], + [0x11, 0x11, 0x11], + [0x00, 0x00, 0x00] +] + + #---------------------------UTILITY FUNCTIONS---------------------------------# def encode_rle24(data): - encoded_data = bytearray() dataRun = bytearray(130) dataInChanSize = len(data)/4 dataTempCount = 0 @@ -210,6 +491,44 @@ def encode_rle24(data): return dataTemp[:dataTempCount] +def decode_rle24(data, pixel_count): + color_value = 0 + run_length = 0 + data_offset = 0 + pixel_offset = 0 + i = 0 + dest_icon_data = bytearray(pixel_count*4) + + if from_bytes(data[:4]) == 0: + data_offset = 4 + color_offset = 0 + while color_offset < 3: + pixel_offset = 0 + while (pixel_offset < pixel_count) and (data_offset < len(data)): + if ((data[data_offset] & 0x80) == 0): + run_length = (0xFF & data[data_offset]) + 1 + data_offset += 1 + i = 0 + while (i < run_length) and (pixel_offset < pixel_count) and (data_offset < len(data)): + dest_icon_data[(pixel_offset*4)+color_offset] = data[data_offset] + pixel_offset += 1 + data_offset += 1 + i += 1 + else: + run_length = (0xFF & data[data_offset]) - 125 + data_offset += 1 + color_value = data[data_offset] + data_offset += 1 + i = 0 + while (i < run_length) and (pixel_offset < pixel_count): + dest_icon_data[(pixel_offset*4)+color_offset] = color_value + pixel_offset += 1 + i += 1 + color_offset += 1 + + return dest_icon_data + + def get_mask_type_for_icon_type(icon_type): if icon_type == ICNS_TABLE_OF_CONTENTS or\ icon_type == ICNS_ICON_VERSION or\ @@ -259,6 +578,22 @@ def get_mask_type_for_icon_type(icon_type): return ICNS_NULL_MASK + +def to_bytes(n, length, endianess='big'): + h = '%x' % n + s = ('0'*(len(h) % 2) + h).zfill(length*2).decode('hex') + return s if endianess == 'big' else s[::-1] + + +def from_bytes(byte_str): + bits = (len(byte_str)-1)*8 + result = 0 + for i in xrange(len(byte_str)): + result = (byte_str[i] << bits) + result + bits -= 8 + return result + + def type_to_str(type): s = bytearray() s.append(type >> 24 & 0xff) @@ -678,7 +1013,6 @@ class ICNSHeader(Structure): icon_type = icns_info.get_image_type() mask_type = get_mask_type_for_icon_type(icon_type) - if mask_type != ICNS_NULL_MASK: icns_mask = ICNSInfo.from_type(mask_type) @@ -695,7 +1029,6 @@ class ICNSHeader(Structure): mask_element.Size = len(icns_mask.data) + mask_element.size mask_element.icns_image = icns_mask - icns_element.TypeID = icon_type icns_element.Size = len(encoded_data) + icns_element.size icns_element.icns_image = icns_info @@ -706,7 +1039,7 @@ class ICNSHeader(Structure): self.elements.append(icns_element) self.elements.append(mask_element) - else:#just use the png data + else: # just use the png data icns_element.TypeID = icon_type icns_element.Size = len(encoded_data) + icns_element.size icns_element.icns_image = icns_info @@ -720,11 +1053,379 @@ class ICNSHeader(Structure): return self.dump()+f_data - - class ICNSElement(Structure): _fields = [Field('TypeID', 4), Field('Size', 4)] icns_image = None + data = None + @classmethod + def from_family(cls, icns_data, icon_type): + resource_size = len(icns_data) + offset = 8 + found_data = False + icon_element = cls() + + while not found_data and offset < resource_size: + icon_element.TypeID = from_bytes(icns_data[offset:offset+4]) + icon_element.Size = from_bytes(icns_data[offset+4:offset+8]) + size = from_bytes(icns_data[offset+4:offset+8]) + icon_element.data = icns_data[offset+8:offset+size] + + if icon_element.TypeID == icon_type: + found_data = True + else: + offset += icon_element.Size + + return icon_element + + def get_image(self): + icon_type = self.TypeID + raw_data_size = self.Size - 8 + data = self.data + + icns_info = ICNSInfo() + icns_info.isImage = 1 + + if icon_type in [ICNS_256x256_32BIT_ARGB_DATA, + ICNS_512x512_32BIT_ARGB_DATA, + ICNS_1024x1024_32BIT_ARGB_DATA]: + magic_png = bytearray([0x89, 0x50, 0x4E, 0x47, + 0x0D, 0x0A, 0x1A, 0x0A]) + magic_read = data[:8] + + if magic_png == magic_read: + png_file = png.Reader(bytes=data) + + width, height, png_data, stats_dict = png_file.read_flat() + + bpp = stats_dict['bitdepth'] * 4 + + icns_info = ICNSInfo() + icns_info.isImage = 1 + icns_info.iconSize.width = width + icns_info.iconSize.height = height + icns_info.iconBitDepth = bpp + icns_info.iconChannels = 4 if bpp == 32 else 1 + icns_info.iconPixelDepth = bpp / icns_info.iconChannels + icns_info.iconRawDataSize = width * height * 4 + icns_info.data = bytearray(list(png_data)) + else: + image = Image.open(StringIO(data)) + mode_to_bpp = {'1':1, 'L':8, 'P':8, 'RGB':24, 'RGBA':32, 'CMYK':32, 'YCbCr':24, 'I':32, 'F':32} + output = StringIO() + bpp = mode_to_bpp[image.mode] + image.save(output, format='PNG') + png_data = bytearray(output.getvalue()) + + icns_info = ICNSInfo() + icns_info.isImage = 1 + icns_info.iconSize.width = image.size[0] + icns_info.iconSize.height = image.size[1] + icns_info.iconBitDepth = bpp + icns_info.iconChannels = 4 if bpp == 32 else 1 + icns_info.iconPixelDepth = bpp / icns_info.iconChannels + icns_info.iconRawDataSize = image.size[0] * image.size[1] * 4 + icns_info.data = png_data + + else: + icns_info = ICNSInfo.from_type(icon_type) + + if icon_type in [ICNS_128x128_32BIT_DATA, + ICNS_48x48_32BIT_DATA, + ICNS_32x32_32BIT_DATA, + ICNS_16x16_32BIT_DATA]: + icon_bit_depth = icns_info.iconPixelDepth * icns_info.iconChannels + icon_data_row_size = icns_info.iconSize.width * icon_bit_depth*ICNS_BYTE_BITS + + if raw_data_size < icns_info.iconRawDataSize: + pixel_count = icns_info.iconSize.width*icns_info.iconSize.height + decoded_data = decode_rle24(data, pixel_count) + icns_info.data = decoded_data + else: + data_count = 0 + pixel_count = 0 + while data_count < icns_info.iconSize.width: + data_pos = data_count * icon_data_row_size + icns_info.data[data_pos:data_pos+icon_data_row_size] = data[data_pos:data_pos+icon_data_row_size] + data_count += 1 + pixel_count = icns_info.iconSize.width*icns_info.iconSize.height + data_count = 0 + while data_count < pixel_count: + argb = icns_info.data[data_count*4:data_count*4+4] + rgba = [argb[1], argb[2], argb[3], argb[0]] + icns_info.data[data_count*4:data_count*4+4] = rgba + data_count += 1 + elif icon_type in [ICNS_48x48_8BIT_DATA, + ICNS_32x32_8BIT_DATA, + ICNS_16x16_8BIT_DATA, + ICNS_16x12_8BIT_DATA, + ICNS_48x48_4BIT_DATA, + ICNS_32x32_4BIT_DATA, + ICNS_16x16_4BIT_DATA, + ICNS_16x12_4BIT_DATA, + ICNS_48x48_1BIT_DATA, + ICNS_32x32_1BIT_DATA, + ICNS_16x16_1BIT_DATA, + ICNS_16x12_1BIT_DATA]: + icon_bit_depth = icns_info.iconPixelDepth * icns_info.iconChannels + icon_data_row_size = icns_info.iconSize.width * icon_bit_depth*ICNS_BYTE_BITS + data_count = 0 + while data_count < icns_info.iconSize.width: + data_pos = data_count * icon_data_row_size + icns_info.data[data_pos:data_pos+icon_data_row_size] = data[data_pos:data_pos+icon_data_row_size] + data_count += 1 + + return icns_info + + def get_mask(self): + element_type = self.TypeID + element_size = self.Size + mask_type = element_type + raw_data_size = element_size - 8 + data = self.data + icns_info = ICNSInfo.from_type(mask_type) + mask_bit_depth = icns_info.iconSize.width * icns_info.iconSize.height + mask_data_size = icns_info.iconRawDataSize + mask_data_row_size = icns_info.iconSize.width * mask_bit_depth / ICNS_BYTE_BITS + + if mask_type in [ICNS_128x128_8BIT_MASK, + ICNS_48x48_8BIT_MASK, + ICNS_32x32_8BIT_MASK, + ICNS_16x16_8BIT_MASK]: + data_count = 0 + while data_count < icns_info.iconSize.height: + data_pos = data_count * mask_data_row_size + icns_info.data[data_pos:data_pos+mask_data_row_size] = data[data_pos:data_pos+mask_data_row_size] + data_count += 1 + + elif mask_type in [ICNS_48x48_1BIT_MASK, + ICNS_32x32_1BIT_MASK, + ICNS_16x16_1BIT_MASK, + ICNS_16x12_1BIT_MASK]: + if raw_data_size == mask_data_size*2: + data_count = 0 + while data_count < icns_info.iconSize.height: + data_pos = data_count * mask_data_row_size + icns_info.data[data_pos:data_pos+mask_data_row_size] = data[data_pos+mask_data_size:data_pos+mask_data_row_size+mask_data_size] + data_count += 1 + + else: + data_count = 0 + while data_count < icns_info.iconSize.height: + data_pos = data_count * mask_data_row_size + icns_info.data[data_pos:data_pos+mask_data_row_size] = data[data_pos:data_pos+mask_data_row_size] + data_count += 1 + return icns_info + + +def icns_read_be(icns_data, size): + icns_bytes = icns_data[:size] + + if size == 1: + return icns_bytes[0] + elif size == 2: + return icns_bytes[1] | icns_bytes[0] << 8 + elif size == 3: + return (icns_bytes[2] & 0xffff | icns_bytes[1] & 0xffff << 8| icns_bytes[0] & 0xffff << 16) & 0x00FFFFFF + elif size == 4: + return icns_bytes[3] | icns_bytes[2] << 8 | icns_bytes[1] << 16 | icns_bytes[0] << 24 + elif size == 8: + b = icns_bytes + return b[7] | b[6] << 8 | b[5] << 16 | b[4] << 24 | b[3] << 32 | b[2] << 40 | b[1] << 48 | b[0] << 56 + + +def icns_header_check(icns_data): + resource_type = icns_read_be(icns_data, 4) + resource_size = icns_read_be(icns_data[4:], 4) + + if resource_type != ICNS_FAMILY_TYPE: + raise Exception('File is not an ICNS file.') + if resource_size != len(icns_data): + raise Exception('Expected size {}, but got {}'.format(len(icns_data))) + + +def icns_parse_family_data(icns_data): + resource_type = icns_read_be(icns_data, 4) + resource_size = icns_read_be(icns_data[4:], 4) + + if resource_type == ICNS_FAMILY_TYPE: + if len(icns_data) == resource_size: + icns_data[:4] = to_bytes(resource_type, 4) + icns_data[4:8] = to_bytes(resource_size, 4) + + offset = 8 + while (offset+8) < resource_size: + element_type = icns_read_be(icns_data[offset:], 4) + element_size = icns_read_be(icns_data[offset+4:], 4) + icns_data[offset:offset+4] = to_bytes(element_type, 4) + icns_data[offset+4:offset+8] = to_bytes(element_size, 4) + offset += element_size + return icns_data + +def get_image_with_mask(icns_data, element_type): + element = ICNSElement.from_family(icns_data, element_type) + icns_image = element.get_image() + if element_type in [ICNS_256x256_32BIT_ARGB_DATA, + ICNS_512x512_32BIT_ARGB_DATA, + ICNS_1024x1024_32BIT_ARGB_DATA]: + return icns_image + mask_type = get_mask_type_for_icon_type(element_type) + mask_element = ICNSElement.from_family(icns_data, mask_type) + mask_image = mask_element.get_mask() + + old_bit_depth = icns_image.iconPixelDepth * icns_image.iconChannels + if old_bit_depth < 32: + old_bit_depth = icns_image.iconPixelDepth * icns_image.iconChannels + pixel_count = icns_image.iconSize.width * icns_image.iconSize.height + new_block_size = icns_image.iconSize.width * 32 + new_data_size = new_block_size * icns_image.iconSize.height + + old_data = icns_image.data + new_data = bytearray(new_data_size) + + data_count = 0 + + if element_type in [ICNS_48x48_8BIT_DATA, + ICNS_32x32_8BIT_DATA, + ICNS_16x16_8BIT_DATA, + ICNS_16x12_8BIT_DATA]: + for pixel_id in xrange(pixel_count): + color_index = old_data[data_count] + color_rgb = icns_colormap_8[color_index] + new_data[pixel_id*4+0] = color_rgb[0] + new_data[pixel_id*4+1] = color_rgb[1] + new_data[pixel_id*4+2] = color_rgb[2] + new_data[pixel_id*4+3] = 0xFF + data_count += 1 + elif element_type in [ICNS_48x48_4BIT_DATA, + ICNS_32x32_4BIT_DATA, + ICNS_16x16_4BIT_DATA, + ICNS_16x12_4BIT_DATA]: + data_value = 0 + for pixel_id in xrange(pixel_count): + if (pixel_id % 2) == 0: + data_value = old_data[data_count] + data_count += 1 + color_index = (data_value & 0xF0) >> 4 + color_rgb = icns_colormap_4[color_index] + new_data[pixel_id*4+0] = color_rgb[0] + new_data[pixel_id*4+1] = color_rgb[1] + new_data[pixel_id*4+2] = color_rgb[2] + new_data[pixel_id*4+3] = 0xFF + elif element_type in [ICNS_48x48_1BIT_DATA, + ICNS_32x32_1BIT_DATA, + ICNS_16x16_1BIT_DATA, + ICNS_16x12_1BIT_DATA]: + data_value = 0 + for pixel_id in xrange(pixel_count): + if (pixel_id % 8) == 0: + data_value = old_data[data_count] + data_count += 1 + color_index = 0x00 if (data_value & 0x80) else 0xFF + data_value = data_value << 1 + new_data[pixel_id*4+0] = color_index + new_data[pixel_id*4+1] = color_index + new_data[pixel_id*4+2] = color_index + new_data[pixel_id*4+3] = 0xFF + + icns_image.iconPixelDepth = 8 + icns_image.iconChannels = 4 + icns_image.iconRawDataSize = new_data_size + icns_image.data = new_data + + if mask_type in [ICNS_128x128_8BIT_MASK, + ICNS_48x48_8BIT_MASK, + ICNS_32x32_8BIT_MASK, + ICNS_16x16_8BIT_MASK]: + pixel_count = mask_image.iconSize.width * mask_image.iconSize.height + data_count = 0 + for pixel_id in xrange(pixel_count): + icns_image.data[pixel_id*4+3] = mask_image.data[data_count] + data_count += 1 + + elif mask_type in [ICNS_48x48_1BIT_MASK, + ICNS_32x32_1BIT_MASK, + ICNS_16x16_1BIT_MASK, + ICNS_16x12_1BIT_MASK]: + pixel_count = mask_image.iconSize.width * mask_image.iconSize.height + data_count = 0 + data_value = 0 + for pixel_id in xrange(pixel_count): + if (pixel_id % 8) == 0: + data_value = mask_image.data[data_count] + data_count += 1 + color_index = 0xFF if (data_value & 0x80) else 0x00 + data_value = data_value << 1 + icns_image.data[pixel_id*4+3] = color_index + im = Image.frombytes('RGBA', [icns_image.iconSize.width,icns_image.iconSize.height],str(icns_image.data)) + output = StringIO() + im.save(output, format='PNG') + icns_image.data = bytearray(output.getvalue()) + + return icns_image + + +def extract_icons(all_icns_data): + image_count = 0 + stack = [all_icns_data] + data = [] + + while stack: + + offset = 8 + icns_data = stack.pop() + + while ((offset+8) < len(icns_data)): + element = ICNSElement() + element.TypeID = from_bytes(icns_data[offset:offset+4]) + element.Size = from_bytes(icns_data[offset+4:offset+8]) + + if element.TypeID == ICNS_TABLE_OF_CONTENTS: + pass + elif element.TypeID in [ICNS_TILE_VARIANT, + ICNS_ROLLOVER_VARIANT, + ICNS_DROP_VARIANT, + ICNS_OPEN_VARIANT, + ICNS_OPEN_DROP_VARIANT]: + variant_data = icns_data[offset:offset+element.Size] + variant_data[:4] = 'icns' + variant_data[4:8] = to_bytes(element.Size, 4) + variant_data = icns_parse_family_data(variant_data) + stack.append(variant_data) + + else: + icon_info = ICNSInfo.from_type(element.TypeID) + if icon_info.isImage: + image_count += 1 + + image_data = get_image_with_mask(icns_data, element.TypeID) + image_data.data = str(image_data.data) + data.append(image_data) + + offset += element.Size + + return data + + +def icns_to_png(icns_file, out_file=None): + + if not os.path.exists(icns_file): + return + + icns_fp = open(icns_file, 'rb') + icns_data = bytearray(icns_fp.read()) + icns_fp.close() + + icns_header_check(icns_data) + new_data = icns_parse_family_data(icns_data) + + icons = extract_icons(new_data) + #for image in icons: + # fname = 'test{}.png'.format(image.iconSize.width) + # print fname + # f = open(fname, 'wb+') + # f.write(image.data) + # f.close() + return icons diff --git a/main.py b/main.py index 7a05cf4..dd9d575 100644 --- a/main.py +++ b/main.py @@ -12,7 +12,7 @@ from PySide.QtGui import QApplication, QHBoxLayout, QVBoxLayout from PySide.QtNetwork import QHttp from PySide.QtCore import QUrl, QFile, QIODevice, QCoreApplication - +from pycns import pngs_from_icns from command_line import CommandBase, logger, get_file MAX_RECENT = 10 @@ -704,9 +704,13 @@ class MainWindow(QtGui.QMainWindow, CommandBase): icon_path = os.path.join(self.project_dir(), icon_path) if os.path.exists(icon_path): if icon_path.endswith('.icns'): - #image = QtGui.QImage.fromData(make_thumbnail(icon_path,48), 'png') - return - image = QtGui.QImage(icon_path) + pngs = pngs_from_icns(icon_path) + if pngs: + image = QtGui.QImage.fromData(QtCore.QByteArray(pngs[-1].data), 'PNG') + else: + return + else: + image = QtGui.QImage(icon_path) if image.width() >= image.height(): image = image.scaledToWidth(48, QtCore.Qt.SmoothTransformation) diff --git a/pycns.py b/pycns.py index ba31e29..d43bcd1 100644 --- a/pycns.py +++ b/pycns.py @@ -1,4 +1,4 @@ -from icns_info import * +from icns_info import ICNSHeader, icns_to_png from image_utils import Image import sys @@ -6,6 +6,7 @@ import sys The image will be scaled keeping the aspect ratio if it is a non square image. """ + def encode_image_to_icns(image_path): """Takes an image and converts it to icns. Image aspect ratio will remain intact.""" i = Image(image_path) @@ -13,12 +14,17 @@ def encode_image_to_icns(image_path): f_data = icns_header.parse_image(i) return f_data + def save_icns(image_path, icns_path): im_data = encode_image_to_icns(image_path) if im_data is not None: icns_path = icns_path if icns_path.endswith('.icns') else icns_path+'.icns' open(icns_path, 'wb+').write(im_data) + +def pngs_from_icns(icns_path): + return icns_to_png(icns_path) + if __name__ == '__main__': if len(sys.argv) != 3: print 'Usage: pycns input_image_path output_icns_path'