Added support for Mac ICNS icon display when loading ICNS files.

This commit is contained in:
Joey Payne 2015-04-23 12:16:25 -06:00
commit 6991d84e2e
4 changed files with 789 additions and 77 deletions

View file

@ -1,3 +1,4 @@
0.12.1
0.12.0 0.12.0
0.12.0-rc1 0.12.0-rc1
0.12.0-alpha3 0.12.0-alpha3

View file

@ -1,64 +1,66 @@
import struct import struct
import image_utils import image_utils
import png import png
from PIL import Image
import os
from cStringIO import StringIO
#---------------------CONSTANTS-----------------------------------------------# #---------------------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_512x512_32BIT_ARGB_DATA = 0x69633039 # "ic09"
ICNS_256x256_32BIT_ARGB_DATA = 0x69633038 # "ic08" ICNS_256x256_32BIT_ARGB_DATA = 0x69633038 # "ic08"
ICNS_128x128_32BIT_DATA = 0x69743332 # "it32" ICNS_128x128_32BIT_DATA = 0x69743332 # "it32"
ICNS_128x128_8BIT_MASK = 0x74386D6B # "t8mk" ICNS_128x128_8BIT_MASK = 0x74386D6B # "t8mk"
ICNS_48x48_1BIT_DATA = 0x69636823 # "ich#" ICNS_48x48_1BIT_DATA = 0x69636823 # "ich#"
ICNS_48x48_4BIT_DATA = 0x69636834 # "ich4" ICNS_48x48_4BIT_DATA = 0x69636834 # "ich4"
ICNS_48x48_8BIT_DATA = 0x69636838 # "ich8" ICNS_48x48_8BIT_DATA = 0x69636838 # "ich8"
ICNS_48x48_32BIT_DATA = 0x69683332 # "ih32" ICNS_48x48_32BIT_DATA = 0x69683332 # "ih32"
ICNS_48x48_1BIT_MASK = 0x69636823 # "ich#" ICNS_48x48_1BIT_MASK = 0x69636823 # "ich#"
ICNS_48x48_8BIT_MASK = 0x68386D6B # "h8mk" ICNS_48x48_8BIT_MASK = 0x68386D6B # "h8mk"
ICNS_32x32_1BIT_DATA = 0x49434E23 # "ICN#" ICNS_32x32_1BIT_DATA = 0x49434E23 # "ICN#"
ICNS_32x32_4BIT_DATA = 0x69636C34 # "icl4" ICNS_32x32_4BIT_DATA = 0x69636C34 # "icl4"
ICNS_32x32_8BIT_DATA = 0x69636C38 # "icl8" ICNS_32x32_8BIT_DATA = 0x69636C38 # "icl8"
ICNS_32x32_32BIT_DATA = 0x696C3332 # "il32" ICNS_32x32_32BIT_DATA = 0x696C3332 # "il32"
ICNS_32x32_1BIT_MASK = 0x49434E23 # "ICN#" ICNS_32x32_1BIT_MASK = 0x49434E23 # "ICN#"
ICNS_32x32_8BIT_MASK = 0x6C386D6B # "l8mk" ICNS_32x32_8BIT_MASK = 0x6C386D6B # "l8mk"
ICNS_16x16_1BIT_DATA = 0x69637323 # "ics#" ICNS_16x16_1BIT_DATA = 0x69637323 # "ics#"
ICNS_16x16_4BIT_DATA = 0x69637334 # "ics4" ICNS_16x16_4BIT_DATA = 0x69637334 # "ics4"
ICNS_16x16_8BIT_DATA = 0x69637338 # "ics8" ICNS_16x16_8BIT_DATA = 0x69637338 # "ics8"
ICNS_16x16_32BIT_DATA = 0x69733332 # "is32" ICNS_16x16_32BIT_DATA = 0x69733332 # "is32"
ICNS_16x16_1BIT_MASK = 0x69637323 # "ics#" ICNS_16x16_1BIT_MASK = 0x69637323 # "ics#"
ICNS_16x16_8BIT_MASK = 0x73386D6B # "s8mk" ICNS_16x16_8BIT_MASK = 0x73386D6B # "s8mk"
ICNS_16x12_1BIT_DATA = 0x69636D23 # "icm#" ICNS_16x12_1BIT_DATA = 0x69636D23 # "icm#"
ICNS_16x12_4BIT_DATA = 0x69636D34 # "icm4" ICNS_16x12_4BIT_DATA = 0x69636D34 # "icm4"
ICNS_16x12_1BIT_MASK = 0x69636D23 # "icm#" ICNS_16x12_1BIT_MASK = 0x69636D23 # "icm#"
ICNS_16x12_8BIT_DATA = 0x69636D38 # "icm8" ICNS_16x12_8BIT_DATA = 0x69636D38 # "icm8"
ICNS_32x32_1BIT_ICON = 0x49434F4E # "ICON" ICNS_32x32_1BIT_ICON = 0x49434F4E # "ICON"
ICNS_TILE_VARIANT = 0x74696C65 # "tile" ICNS_TILE_VARIANT = 0x74696C65 # "tile"
ICNS_ROLLOVER_VARIANT = 0x6F766572 # "over" ICNS_ROLLOVER_VARIANT = 0x6F766572 # "over"
ICNS_DROP_VARIANT = 0x64726F70 # "drop" ICNS_DROP_VARIANT = 0x64726F70 # "drop"
ICNS_OPEN_VARIANT = 0x6F70656E # "open" ICNS_OPEN_VARIANT = 0x6F70656E # "open"
ICNS_OPEN_DROP_VARIANT = 0x6F647270 # "odrp" ICNS_OPEN_DROP_VARIANT = 0x6F647270 # "odrp"
ICNS_NULL_DATA = 0x00000000 ICNS_NULL_DATA = 0x00000000
ICNS_NULL_MASK = 0x00000000 ICNS_NULL_MASK = 0x00000000
# icns file / resource type constants # 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 ICNS_NULL_TYPE = 0x00000000
@ -79,43 +81,322 @@ ICNS_STATUS_UNSUPPORTED = 4
#---------------------------SYMBOL DICTS-------------------------------------# #---------------------------SYMBOL DICTS-------------------------------------#
struct_symbols = {1:'B',#byte struct_symbols = {1: 'B', # byte
2:'H',#word 2: 'H', # word
4:'I',#unsigned int/double word 4: 'I', # unsigned int/double word
8:'Q' #quad word 8: 'Q' # quad word
} }
#used to easily get the icon type from dimensions #used to easily get the icon type from dimensions
type_dict = {'mask':{128:{8:ICNS_128x128_8BIT_MASK}, type_dict = {'mask': {128: {8: ICNS_128x128_8BIT_MASK},
48:{1:ICNS_48x48_1BIT_MASK, 48: {1: ICNS_48x48_1BIT_MASK,
8:ICNS_48x48_8BIT_MASK}, 8: ICNS_48x48_8BIT_MASK},
32:{1:ICNS_32x32_1BIT_MASK, 32: {1: ICNS_32x32_1BIT_MASK,
8:ICNS_32x32_8BIT_MASK}, 8: ICNS_32x32_8BIT_MASK},
16:{1:ICNS_16x16_1BIT_MASK, 16: {1: ICNS_16x16_1BIT_MASK,
8:ICNS_16x16_8BIT_MASK}, 8: ICNS_16x16_8BIT_MASK},
}, },
'data':{1024:{32:ICNS_1024x1024_32BIT_ARGB_DATA}, 'data': {1024: {32: ICNS_1024x1024_32BIT_ARGB_DATA},
512:{32:ICNS_512x512_32BIT_ARGB_DATA}, 512: {32: ICNS_512x512_32BIT_ARGB_DATA},
256:{32:ICNS_256x256_32BIT_ARGB_DATA}, 256: {32: ICNS_256x256_32BIT_ARGB_DATA},
128:{32:ICNS_128x128_32BIT_DATA}, 128: {32: ICNS_128x128_32BIT_DATA},
48:{1:ICNS_48x48_1BIT_DATA, 48: {1: ICNS_48x48_1BIT_DATA,
4:ICNS_48x48_4BIT_DATA, 4: ICNS_48x48_4BIT_DATA,
8:ICNS_48x48_8BIT_DATA, 8: ICNS_48x48_8BIT_DATA,
32:ICNS_48x48_32BIT_DATA}, 32: ICNS_48x48_32BIT_DATA},
32:{1:ICNS_32x32_1BIT_DATA, 32: {1: ICNS_32x32_1BIT_DATA,
4:ICNS_32x32_4BIT_DATA, 4: ICNS_32x32_4BIT_DATA,
8:ICNS_32x32_8BIT_DATA, 8: ICNS_32x32_8BIT_DATA,
32:ICNS_32x32_32BIT_DATA}, 32: ICNS_32x32_32BIT_DATA},
16:{1:ICNS_16x16_1BIT_DATA, 16: {1: ICNS_16x16_1BIT_DATA,
4:ICNS_16x16_4BIT_DATA, 4: ICNS_16x16_4BIT_DATA,
8:ICNS_16x16_8BIT_DATA, 8: ICNS_16x16_8BIT_DATA,
32:ICNS_16x16_32BIT_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---------------------------------# #---------------------------UTILITY FUNCTIONS---------------------------------#
def encode_rle24(data): def encode_rle24(data):
encoded_data = bytearray()
dataRun = bytearray(130) dataRun = bytearray(130)
dataInChanSize = len(data)/4 dataInChanSize = len(data)/4
dataTempCount = 0 dataTempCount = 0
@ -210,6 +491,44 @@ def encode_rle24(data):
return dataTemp[:dataTempCount] 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): def get_mask_type_for_icon_type(icon_type):
if icon_type == ICNS_TABLE_OF_CONTENTS or\ if icon_type == ICNS_TABLE_OF_CONTENTS or\
icon_type == ICNS_ICON_VERSION or\ icon_type == ICNS_ICON_VERSION or\
@ -259,6 +578,22 @@ def get_mask_type_for_icon_type(icon_type):
return ICNS_NULL_MASK 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): def type_to_str(type):
s = bytearray() s = bytearray()
s.append(type >> 24 & 0xff) s.append(type >> 24 & 0xff)
@ -678,7 +1013,6 @@ class ICNSHeader(Structure):
icon_type = icns_info.get_image_type() icon_type = icns_info.get_image_type()
mask_type = get_mask_type_for_icon_type(icon_type) mask_type = get_mask_type_for_icon_type(icon_type)
if mask_type != ICNS_NULL_MASK: if mask_type != ICNS_NULL_MASK:
icns_mask = ICNSInfo.from_type(mask_type) 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.Size = len(icns_mask.data) + mask_element.size
mask_element.icns_image = icns_mask mask_element.icns_image = icns_mask
icns_element.TypeID = icon_type icns_element.TypeID = icon_type
icns_element.Size = len(encoded_data) + icns_element.size icns_element.Size = len(encoded_data) + icns_element.size
icns_element.icns_image = icns_info icns_element.icns_image = icns_info
@ -706,7 +1039,7 @@ class ICNSHeader(Structure):
self.elements.append(icns_element) self.elements.append(icns_element)
self.elements.append(mask_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.TypeID = icon_type
icns_element.Size = len(encoded_data) + icns_element.size icns_element.Size = len(encoded_data) + icns_element.size
icns_element.icns_image = icns_info icns_element.icns_image = icns_info
@ -720,11 +1053,379 @@ class ICNSHeader(Structure):
return self.dump()+f_data return self.dump()+f_data
class ICNSElement(Structure): class ICNSElement(Structure):
_fields = [Field('TypeID', 4), _fields = [Field('TypeID', 4),
Field('Size', 4)] Field('Size', 4)]
icns_image = None 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

12
main.py
View file

@ -12,7 +12,7 @@ from PySide.QtGui import QApplication, QHBoxLayout, QVBoxLayout
from PySide.QtNetwork import QHttp from PySide.QtNetwork import QHttp
from PySide.QtCore import QUrl, QFile, QIODevice, QCoreApplication from PySide.QtCore import QUrl, QFile, QIODevice, QCoreApplication
from pycns import pngs_from_icns
from command_line import CommandBase, logger, get_file from command_line import CommandBase, logger, get_file
MAX_RECENT = 10 MAX_RECENT = 10
@ -704,9 +704,13 @@ class MainWindow(QtGui.QMainWindow, CommandBase):
icon_path = os.path.join(self.project_dir(), icon_path) icon_path = os.path.join(self.project_dir(), icon_path)
if os.path.exists(icon_path): if os.path.exists(icon_path):
if icon_path.endswith('.icns'): if icon_path.endswith('.icns'):
#image = QtGui.QImage.fromData(make_thumbnail(icon_path,48), 'png') pngs = pngs_from_icns(icon_path)
return if pngs:
image = QtGui.QImage(icon_path) image = QtGui.QImage.fromData(QtCore.QByteArray(pngs[-1].data), 'PNG')
else:
return
else:
image = QtGui.QImage(icon_path)
if image.width() >= image.height(): if image.width() >= image.height():
image = image.scaledToWidth(48, image = image.scaledToWidth(48,
QtCore.Qt.SmoothTransformation) QtCore.Qt.SmoothTransformation)

View file

@ -1,4 +1,4 @@
from icns_info import * from icns_info import ICNSHeader, icns_to_png
from image_utils import Image from image_utils import Image
import sys import sys
@ -6,6 +6,7 @@ import sys
The image will be scaled keeping the aspect ratio if it is a non square image. The image will be scaled keeping the aspect ratio if it is a non square image.
""" """
def encode_image_to_icns(image_path): def encode_image_to_icns(image_path):
"""Takes an image and converts it to icns. Image aspect ratio will remain intact.""" """Takes an image and converts it to icns. Image aspect ratio will remain intact."""
i = Image(image_path) i = Image(image_path)
@ -13,12 +14,17 @@ def encode_image_to_icns(image_path):
f_data = icns_header.parse_image(i) f_data = icns_header.parse_image(i)
return f_data return f_data
def save_icns(image_path, icns_path): def save_icns(image_path, icns_path):
im_data = encode_image_to_icns(image_path) im_data = encode_image_to_icns(image_path)
if im_data is not None: if im_data is not None:
icns_path = icns_path if icns_path.endswith('.icns') else icns_path+'.icns' icns_path = icns_path if icns_path.endswith('.icns') else icns_path+'.icns'
open(icns_path, 'wb+').write(im_data) open(icns_path, 'wb+').write(im_data)
def pngs_from_icns(icns_path):
return icns_to_png(icns_path)
if __name__ == '__main__': if __name__ == '__main__':
if len(sys.argv) != 3: if len(sys.argv) != 3:
print 'Usage: pycns input_image_path output_icns_path' print 'Usage: pycns input_image_path output_icns_path'