Added support for exporting mac app icons.

This commit is contained in:
Joey Payne 2014-11-03 20:35:07 +13:00
commit a6b4808dba
6 changed files with 3581 additions and 12 deletions

726
icns_info.py Normal file
View file

@ -0,0 +1,726 @@
import struct
from image_utils import Image, nearest_icon_size, resize
import png
#---------------------CONSTANTS-----------------------------------------------#
ICNS_TABLE_OF_CONTENTS = 0x544F4320 # "TOC "
ICNS_ICON_VERSION = 0x69636E56 # "icnV"
ICNS_1024x1024_32BIT_ARGB_DATA = 0x69633130 # "ic10"
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_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_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_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_NULL_DATA = 0x00000000
ICNS_NULL_MASK = 0x00000000
# icns file / resource type constants
ICNS_FAMILY_TYPE = 0x69636E73 # "icns"
ICNS_MACBINARY_TYPE = 0x6D42494E # "mBIN"
ICNS_NULL_TYPE = 0x00000000
ICNS_BYTE_BITS = 8
# icns error return values
ICNS_STATUS_OK = 0
ICNS_STATUS_NULL_PARAM = -1
ICNS_STATUS_NO_MEMORY = -2
ICNS_STATUS_INVALID_DATA = -3
ICNS_STATUS_IO_READ_ERR = 1
ICNS_STATUS_IO_WRITE_ERR = 2
ICNS_STATUS_DATA_NOT_FOUND = 3
ICNS_STATUS_UNSUPPORTED = 4
#---------------------------SYMBOL DICTS-------------------------------------#
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},
},
'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},
}}
#---------------------------UTILITY FUNCTIONS---------------------------------#
def encode_rle24(data):
encoded_data = bytearray()
dataRun = bytearray(130)
dataInChanSize = len(data)/4
dataTempCount = 0
dataTemp = bytearray(len(data) + len(data)/4)
if len(data) >= 65536:
dataTempCount = 4
for colorOffset in xrange(3):
runCount = 0
runLength = 1
runType = 0
dataRun[0] = data[colorOffset]
for dataInCount in xrange(1, dataInChanSize):
dataByte = data[colorOffset+(dataInCount*4)]
if runLength < 2:
dataRun[runLength] = dataByte
runLength += 1
elif runLength == 2:
if dataByte == dataRun[runLength-1] and dataByte == dataRun[runLength-2]:
runType = 1
else:
runType = 0
dataRun[runLength] = dataByte
runLength += 1
else:
if runType == 0 and runLength < 128:
if dataByte == dataRun[runLength-1] and dataByte == dataRun[runLength -2]:
dataTemp[dataTempCount] = runLength - 3
dataTempCount += 1
dataTemp[dataTempCount:dataTempCount+runLength-2] = dataRun[:runLength-2]
dataTempCount += runLength - 2
runCount += 1
dataRun[0] = dataRun[runLength - 2]
dataRun[1] = dataRun[runLength - 1]
dataRun[2] = dataByte
runLength = 3
runType = 1
else:
dataRun[runLength] = dataByte
runLength += 1
elif runType == 1 and runLength < 130:
if dataByte == dataRun[runLength - 1] and dataByte == dataRun[runLength - 2]:
dataRun[runLength] = dataByte
runLength += 1
else:
dataTemp[dataTempCount] = runLength + 125
dataTempCount += 1
dataTemp[dataTempCount] = dataRun[0]
dataTempCount += 1
runCount += 1
dataRun[0] = dataByte
runLength = 1
runType = 0
else:
if runType == 0:
dataTemp[dataTempCount] = runLength - 1
dataTempCount += 1
dataTemp[dataTempCount:dataTempCount+runLength] = dataRun[:runLength]
dataTempCount = dataTempCount + runLength
elif runType == 1:
dataTemp[dataTempCount] = runLength + 125
dataTempCount += 1
dataTemp[dataTempCount] = dataRun[0]
dataTempCount += 1
runCount += 1
dataRun[0] = dataByte
runLength = 1
runType = 0
if runLength > 0:
if runType == 0:
dataTemp[dataTempCount] = runLength - 1
dataTempCount += 1
dataTemp[dataTempCount:dataTempCount+runLength] = dataRun[:runLength]
dataTempCount = dataTempCount + runLength
elif runType == 1:
dataTemp[dataTempCount] = runLength + 125
dataTempCount += 1
dataTemp[dataTempCount] = dataRun[0]
dataTempCount += 1
runCount += 1
return dataTemp[:dataTempCount]
def get_mask_type_for_icon_type(icon_type):
if icon_type == ICNS_TABLE_OF_CONTENTS or\
icon_type == ICNS_ICON_VERSION or\
icon_type == ICNS_1024x1024_32BIT_ARGB_DATA or\
icon_type == ICNS_512x512_32BIT_ARGB_DATA or\
icon_type == ICNS_256x256_32BIT_ARGB_DATA:
return ICNS_NULL_MASK
if icon_type == ICNS_128x128_32BIT_DATA:
return ICNS_128x128_8BIT_MASK
if icon_type == ICNS_48x48_32BIT_DATA:
return ICNS_48x48_8BIT_MASK
if icon_type == ICNS_32x32_32BIT_DATA:
return ICNS_32x32_8BIT_MASK
if icon_type == ICNS_16x16_32BIT_DATA:
return ICNS_16x16_8BIT_MASK
# 8-bit image types - 1-bit mask types
if icon_type == ICNS_48x48_8BIT_DATA:
return ICNS_48x48_1BIT_MASK
if icon_type == ICNS_32x32_8BIT_DATA:
return ICNS_32x32_1BIT_MASK
if icon_type == ICNS_16x16_8BIT_DATA:
return ICNS_16x16_1BIT_MASK
if icon_type == ICNS_16x12_8BIT_DATA:
return ICNS_16x12_1BIT_MASK
# 4 bit image types - 1-bit mask types
if icon_type == ICNS_48x48_4BIT_DATA:
return ICNS_48x48_1BIT_MASK
if icon_type == ICNS_32x32_4BIT_DATA:
return ICNS_32x32_1BIT_MASK
if icon_type == ICNS_16x16_4BIT_DATA:
return ICNS_16x16_1BIT_MASK
if icon_type == ICNS_16x12_4BIT_DATA:
return ICNS_16x12_1BIT_MASK
# 1 bit image types - 1-bit mask types
if icon_type == ICNS_48x48_1BIT_DATA:
return ICNS_48x48_1BIT_MASK
if icon_type == ICNS_32x32_1BIT_DATA:
return ICNS_32x32_1BIT_MASK
if icon_type == ICNS_16x16_1BIT_DATA:
return ICNS_16x16_1BIT_MASK
if icon_type == ICNS_16x12_1BIT_DATA:
return ICNS_16x12_1BIT_MASK
return ICNS_NULL_MASK
def type_to_str(type):
s = bytearray()
s.append(type >> 24 & 0xff)
s.append(type >> 16 & 0xff)
s.append(type >> 8 & 0xff)
s.append(type & 0xff)
s.append(0)
return str(s)
class Printable(object):
def _attrs(self):
a = []
for attr in dir(self):
if not attr.startswith('_') and not callable(getattr(self, attr)):
a.append(attr)
return a
def _dict_items(self):
for a in reversed(self._attrs()):
yield a, getattr(self,a)
def _dict_string(self):
vals = []
for key, val in self._dict_items():
try:
vals.append(u'{}={}'.format(key, val))
except UnicodeDecodeError:
vals.append(u'{}=<not printable>'.format(key))
return u', '.join(vals)
def __repr__(self):
return unicode(self)
def __str__(self):
return unicode(self).encode('utf-8')
def __unicode__(self):
return u'{} [{}]'.format(self.__class__.__name__, self._dict_string())
#---------------------------CLASSES-------------------------------------------#
class Field(object):
"""This is a field object that will describe a field on the
class it is a part of.
"""
name = ''
size = 0
default_value = None
def __init__(self, name='', size=0, default_value=None):
self.name = name
self.size = size
self.default_value = default_value
class Structure(Printable):
"""A structure is composed of fields denoted by the _fields attribute."""
_fields = None
def __init__(self, *args, **kwargs):
for field in self._fields:
setattr(self, field.name, field.default_value)
for k,v in kwargs:
setattr(self, k, v)
@property
def size(self):
sum_ = 0
for f in self._fields:
sum_ += f.size
return sum_
def dump(self):
s = '>'
values = []
for field in self._fields:
val = getattr(self, field.name)
if not isinstance(val, basestring):
values.append(val)
s += struct_symbols[field.size]
else:
s += 'B'*len(val)
values.extend(struct.unpack('>'+'B'*len(val), val))
return struct.pack(s, *values)
class Size(Printable):
def __init__(self, width=0, height=0):
self.width = width
self.height = height
class ICNSInfo(Printable):
def __init__(self):
self.iconType = None
self.isImage = False
self.isMask = False
self.iconSize = Size()
self.iconChannels = 0
self.iconPixelDepth = 0
self.iconBitDepth = 0
self.iconRawDataSize = 0
data = None
def get_image_type(self):
if not self.isImage and not self.isMask:
return ICNS_NULL_TYPE
if self.iconSize.width == 0 or self.iconSize.height == 0:
if self.iconRawDataSize == 24:
if self.isImage and self.isMask:
return ICNS_NULL_TYPE
if self.isImage:
return ICNS_16x12_1BIT_DATA
if self.isMask:
return ICNS_16x12_1BIT_MASK
if self.iconRawDataSize == 32:
if self.isImage and self.isMask:
return ICNS_NULL_TYPE
if self.isImage:
return ICNS_16x16_1BIT_DATA
if self.isMask:
return ICNS_16x16_1BIT_MASK
return ICNS_NULL_TYPE
if self.iconBitDepth == 0 and (self.iconSize.width < 128 or self.iconSize.height < 128):
if self.iconPixelDepth == 0 or self.iconChannels == 0:
return ICNS_NULL_TYPE
else:
self.iconBitDepth = self.iconPixelDepth * self.iconChannels
if self.iconSize.width == 16 and self.iconSize.height == 12:
if self.iconBitDepth == 1:
if self.isImage:
return ICNS_16x12_1BIT_DATA
if self.isMask:
return ICNS_16x12_1BIT_MASK
if self.iconBitDepth == 4:
return ICNS_16x12_4BIT_DATA
if self.iconBitDepth == 8:
return ICNS_16x12_8BIT_DATA
return ICNS_NULL_TYPE
if self.iconSize.width != self.iconSize.height:
return ICNS_NULL_TYPE
data_type = ''
if self.isImage:
data_type = 'data'
elif self.isMask:
data_type = 'mask'
try:
return type_dict[data_type][self.iconSize.width][self.iconBitDepth]
except KeyError:
return ICNS_NULL_TYPE
@classmethod
def from_type(cls, type):
icon_info = cls()
icon_info.iconType = type
if type == ICNS_TABLE_OF_CONTENTS or type == ICNS_ICON_VERSION:
return icon_info
if type == ICNS_1024x1024_32BIT_ARGB_DATA:
icon_info.isImage = True
icon_info.isMask = False
icon_info.iconSize.width = 1024
icon_info.iconSize.height = 1024
icon_info.iconChannels = 4
icon_info.iconPixelDepth = 8
icon_info.iconBitDepth = 32
elif type == ICNS_512x512_32BIT_ARGB_DATA:
icon_info.isImage = True
icon_info.isMask = False
icon_info.iconSize.width = 512
icon_info.iconSize.height = 512
icon_info.iconChannels = 4
icon_info.iconPixelDepth = 8
icon_info.iconBitDepth = 32
elif type == ICNS_256x256_32BIT_ARGB_DATA:
icon_info.isImage = True
icon_info.isMask = False
icon_info.iconSize.width = 256
icon_info.iconSize.height = 256
icon_info.iconChannels = 4
icon_info.iconPixelDepth = 8
icon_info.iconBitDepth = 32
elif type == ICNS_128x128_32BIT_DATA:
icon_info.isImage = True
icon_info.isMask = False
icon_info.iconSize.width = 128
icon_info.iconSize.height = 128
icon_info.iconChannels = 4
icon_info.iconPixelDepth = 8
icon_info.iconBitDepth = 32
elif type == ICNS_48x48_32BIT_DATA:
icon_info.isImage = True
icon_info.isMask = False
icon_info.iconSize.width = 48
icon_info.iconSize.height = 48
icon_info.iconChannels = 4
icon_info.iconPixelDepth = 8
icon_info.iconBitDepth = 32
elif type == ICNS_32x32_32BIT_DATA:
icon_info.isImage = True
icon_info.isMask = False
icon_info.iconSize.width = 32
icon_info.iconSize.height = 32
icon_info.iconChannels = 4
icon_info.iconPixelDepth = 8
icon_info.iconBitDepth = 32
elif type == ICNS_16x16_32BIT_DATA:
icon_info.isImage = True
icon_info.isMask = False
icon_info.iconSize.width = 16
icon_info.iconSize.height = 16
icon_info.iconChannels = 4
icon_info.iconPixelDepth = 8
icon_info.iconBitDepth = 32
elif type == ICNS_48x48_8BIT_DATA:
icon_info.isImage = True
icon_info.isMask = False
icon_info.iconSize.width = 48
icon_info.iconSize.height = 48
icon_info.iconChannels = 1
icon_info.iconPixelDepth = 8
icon_info.iconBitDepth = 8
elif type == ICNS_32x32_8BIT_DATA:
icon_info.isImage = True
icon_info.isMask = False
icon_info.iconSize.width = 32
icon_info.iconSize.height = 32
icon_info.iconChannels = 1
icon_info.iconPixelDepth = 8
icon_info.iconBitDepth = 8
elif type == ICNS_16x16_8BIT_DATA:
icon_info.isImage = True
icon_info.isMask = False
icon_info.iconSize.width = 16
icon_info.iconSize.height = 16
icon_info.iconChannels = 1
icon_info.iconPixelDepth = 8
icon_info.iconBitDepth = 8
elif type == ICNS_16x12_8BIT_DATA:
icon_info.isImage = True
icon_info.isMask = False
icon_info.iconSize.width = 16
icon_info.iconSize.height = 12
icon_info.iconChannels = 1
icon_info.iconPixelDepth = 8
icon_info.iconBitDepth = 8
elif type == ICNS_128x128_8BIT_MASK:
icon_info.isImage = False
icon_info.isMask = True
icon_info.iconSize.width = 128
icon_info.iconSize.height = 128
icon_info.iconChannels = 1
icon_info.iconPixelDepth = 8
icon_info.iconBitDepth = 8
elif type == ICNS_48x48_8BIT_MASK:
icon_info.isImage = False
icon_info.isMask = True
icon_info.iconSize.width = 48
icon_info.iconSize.height = 48
icon_info.iconChannels = 1
icon_info.iconPixelDepth = 8
icon_info.iconBitDepth = 8
elif type == ICNS_32x32_8BIT_MASK:
icon_info.isImage = False
icon_info.isMask = True
icon_info.iconSize.width = 32
icon_info.iconSize.height = 32
icon_info.iconChannels = 1
icon_info.iconPixelDepth = 8
icon_info.iconBitDepth = 8
elif type == ICNS_16x16_8BIT_MASK:
icon_info.isImage = False
icon_info.isMask = True
icon_info.iconSize.width = 16
icon_info.iconSize.height = 16
icon_info.iconChannels = 1
icon_info.iconPixelDepth = 8
icon_info.iconBitDepth = 8
elif type == ICNS_48x48_4BIT_DATA:
icon_info.isImage = True
icon_info.isMask = False
icon_info.iconSize.width = 48
icon_info.iconSize.height = 48
icon_info.iconChannels = 1
icon_info.iconPixelDepth = 4
icon_info.iconBitDepth = 4
elif type == ICNS_32x32_4BIT_DATA:
icon_info.isImage = True
icon_info.isMask = False
icon_info.iconSize.width = 32
icon_info.iconSize.height = 32
icon_info.iconChannels = 1
icon_info.iconPixelDepth = 4
icon_info.iconBitDepth = 4
elif type == ICNS_16x16_4BIT_DATA:
icon_info.isImage = True
icon_info.isMask = False
icon_info.iconSize.width = 16
icon_info.iconSize.height = 16
icon_info.iconChannels = 1
icon_info.iconPixelDepth = 4
icon_info.iconBitDepth = 4
elif type == ICNS_16x12_4BIT_DATA:
icon_info.isImage = True
icon_info.isMask = False
icon_info.iconSize.width = 16
icon_info.iconSize.height = 12
icon_info.iconChannels = 1
icon_info.iconPixelDepth = 4
icon_info.iconBitDepth = 4
elif type == ICNS_48x48_1BIT_DATA:
icon_info.isImage = True
icon_info.isMask = True
icon_info.iconSize.width = 48
icon_info.iconSize.height = 48
icon_info.iconChannels = 1
icon_info.iconPixelDepth = 1
icon_info.iconBitDepth = 1
elif type == ICNS_32x32_1BIT_DATA:
icon_info.isImage = True
icon_info.isMask = True
icon_info.iconSize.width = 32
icon_info.iconSize.height = 32
icon_info.iconChannels = 1
icon_info.iconPixelDepth = 1
icon_info.iconBitDepth = 1
elif type == ICNS_16x16_1BIT_DATA:
icon_info.isImage = True
icon_info.isMask = True
icon_info.iconSize.width = 16
icon_info.iconSize.height = 16
icon_info.iconChannels = 1
icon_info.iconPixelDepth = 1
icon_info.iconBitDepth = 1
elif type == ICNS_16x12_1BIT_DATA:
icon_info.isImage = True
icon_info.isMask = True
icon_info.iconSize.width = 16
icon_info.iconSize.height = 12
icon_info.iconChannels = 1
icon_info.iconPixelDepth = 1
icon_info.iconBitDepth = 1
else:
print 'Unable to parse icon type {}'.format(type_to_str(type))
icon_info.iconType = ICNS_NULL_TYPE
icon_info.iconRawDataSize = icon_info.iconSize.height * icon_info.iconSize.width * icon_info.iconBitDepth/ICNS_BYTE_BITS
return icon_info
class ICNSHeader(Structure):
_fields = [Field('TypeID', 4, 'icns'),
Field('Size', 4)]
elements = None
def __init__(self, *args, **kwargs):
super(self.__class__, self).__init__(*args, **kwargs)
self.elements = []
def parse_image(self, image):
icon_size = nearest_icon_size(image.size[0], image.size[1])
icon_sizes = [icon_size]
self.Size = self.size
f_data = bytearray()
for icon_s in icon_sizes:
icns_element = ICNSElement()
im_data = resize(image, (icon_s, icon_s))
png_file = png.Reader(bytes=im_data)
width, height, data, stats_dict = png_file.read_flat()
if icon_size >= 256:
encoded_data = im_data
else:
encoded_data = encode_rle24(data)
bpp = stats_dict['bitdepth'] * 4
icns_info = ICNSInfo()
icns_info.isImage = 1
icns_info.iconSize.width = icon_size
icns_info.iconSize.height = icon_size
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(data))
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)
iconDataOffset = 0
maskDataOffset = 0
while iconDataOffset < icns_info.ImageDataSize and maskDataOffset < icns_mask.ImageDataSize:
icns_mask.data[maskDataOffset] = icns_info.data[iconDataOffset+3]
iconDataOffset += 4
maskDataOffset += 1
mask_element = ICNSElement()
mask_element.TypeID = mask_type
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
self.Size += icns_element.Size + mask_element.Size
f_data += icns_element.dump()+encoded_data+mask_element.dump()+icns_mask.data
self.elements.append(icns_element)
self.elements.append(mask_element)
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
self.Size += icns_element.Size
f_data += icns_element.dump()+encoded_data
self.elements.append(icns_element)
return self.dump()+f_data
class ICNSElement(Structure):
_fields = [Field('TypeID', 4),
Field('Size', 4)]
icns_image = None

38
image_utils.py Normal file
View file

@ -0,0 +1,38 @@
from cStringIO import StringIO
try:
from PIL import Image as im
Image = im.open
def resize(image, size):
output = StringIO()
back = im.new('RGBA', size, (0,0,0,0))
image.thumbnail(size, im.ANTIALIAS)
offset = [0,0]
if image.size[0] >= image.size[1]:
offset[1] = back.size[1]/2-image.size[1]/2
else:
offset[0] = back.size[0]/2-image.size[0]/2
back.paste(image, tuple(offset))
back.save(output, image.format)
contents = output.getvalue()
output.close()
return contents
except ImportError:
raise Exception('Python image library PIL/pillow is required.')
LARGEST_ICON_SIZE = 1024
SMALLEST_ICON_SIZE = 16
sizes = [LARGEST_ICON_SIZE,512,256,128,48,32,SMALLEST_ICON_SIZE]
def nearest_icon_size(width, height):
maximum = max(width, height)
if maximum >= LARGEST_ICON_SIZE:
return LARGEST_ICON_SIZE
if maximum <= SMALLEST_ICON_SIZE:
return SMALLEST_ICON_SIZE
for i in xrange(len(sizes)-1):
current_size = sizes[i]
next_size = sizes[i+1]
if current_size > maximum >= next_size:
return next_size

43
main.py
View file

@ -1,4 +1,5 @@
from utils import zip_files, join_files, log, get_temp_dir, open_folder_in_explorer
from pycns import save_icns
from pepy.pe import PEFile
import urllib2, re
@ -63,6 +64,7 @@ class Setting(object):
self.default_value = kwargs.pop('default_value', None)
self.button = kwargs.pop('button', None)
self.button_callback = kwargs.pop('button_callback', None)
self.description = kwargs.pop('description', '')
self.set_extra_attributes_from_keyword_args(kwargs)
@ -151,7 +153,7 @@ class MainWindow(QtGui.QWidget):
'keywords':Setting(name='keywords', default_value='', type='string'),
'nodejs': Setting('nodejs', 'Include Nodejs', default_value=True, type='check'),
'node-main': Setting('node-main', 'Alt. Nodejs', default_value='', type='file', file_types='*.js'),
'single-instance': Setting('single-instance', 'Single Instance', default_value=True, type='check')}
'single-instance': Setting('single-instance', 'Single Instance', default_value=True, type='check', description='Restrict the app to run with only a single instance allowed at a time.')}
webkit_settings = {'plugin': Setting('plugin', 'Load plugins', default_value=False, type='check'),
'java': Setting('java', 'Load Java', default_value=False, type='check'),
@ -159,6 +161,8 @@ class MainWindow(QtGui.QWidget):
window_settings = {'title': Setting(name='title', default_value='', type='string'),
'icon': Setting('icon', 'Window Icon', default_value='', type='file', file_types='*.png *.jpg *.jpeg'),
'mac_app_icon': Setting('mac_icon', 'Mac Icon', default_value='', type='file', file_types='*.png *.jpg *.jpeg *.icns', description='The icon to be displayed for the Mac Application. Defaults to Window Icon.'),
'exe_icon': Setting('exe_icon', 'Exe Icon', default_value='', type='file', file_types='*.png *.jpg *.jpeg', description='The icon to be displayed in the windows exe of the app. Defaults to Window Icon.'),
'width': Setting('width', default_value=640, type='string'),
'height': Setting('height', default_value=480, type='string'),
'min_width': Setting('min_width', default_value=None, type='string'),
@ -167,13 +171,13 @@ class MainWindow(QtGui.QWidget):
'max_height': Setting('max_height', default_value=None, type='string'),
'toolbar': Setting('toolbar', 'Show Toolbar', default_value=False, type='check'),
'always-on-top': Setting('always-on-top', 'Keep on top', default_value=False, type='check'),
'frame': Setting('frame', 'Window Frame', default_value=True, type='check'),
'show_in_taskbar': Setting('show_in_taskbar', 'Taskbar', default_value=True, type='check'),
'frame': Setting('frame', 'Window Frame', default_value=True, type='check', description='Show the frame of the window.'),
'show_in_taskbar': Setting('show_in_taskbar', 'Taskbar', default_value=True, type='check', description='Show the app running in the taskbar.'),
'visible': Setting('visible', default_value=True, type='check'),
'resizable': Setting('resizable', default_value=False, type='check'),
'fullscreen': Setting('fullscreen', default_value=False, type='check'),
'position': Setting('position','Position by', default_value=None, values=[None, 'mouse', 'center'], type='list'),
'as_desktop': Setting('as_desktop', default_value=False, type='check'),
'position': Setting('position','Position by', default_value=None, values=[None, 'mouse', 'center'], type='list', description='The position to place the window when it opens.'),
'as_desktop': Setting('as_desktop', default_value=False, type='check', description='Tries to render the app to the desktop background.'),
}
win_32_dir_prefix = 'node-webkit-v{}-win-ia32'
@ -245,7 +249,8 @@ class MainWindow(QtGui.QWidget):
'nodejs', 'single-instance', 'plugin',
'java', 'page-cache']
window_setting_order = ['title', 'icon', 'position', 'width', 'height', 'min_width', 'min_height',
window_setting_order = ['title', 'icon', 'mac_app_icon', 'exe_icon', 'position', 'width', 'height',
'min_width', 'min_height',
'max_width', 'max_height', 'toolbar', 'always-on-top', 'frame',
'show_in_taskbar', 'visible', 'resizable', 'fullscreen', 'as_desktop']
@ -404,7 +409,7 @@ class MainWindow(QtGui.QWidget):
if setting.type == 'file' and setting.value and not os.path.exists(os.path.join(self.projectDir(),setting.value)):
log(setting.value, "does not exist")
settings_valid = False
if setting.type == 'folder' and setting.value and not os.path.exists(setting.value):
if setting.type == 'folder' and setting.value and not os.path.exists(os.path.join(self.projectDir(),setting.value)):
settings_valid = False
export_chosen = False
@ -862,7 +867,9 @@ class MainWindow(QtGui.QWidget):
display_name = setting.display_name+':'
if setting.required:
display_name += '*'
glayout.addWidget(QtGui.QLabel(display_name),row,col)
setting_label = QtGui.QLabel(display_name)
setting_label.setToolTip(setting.description)
glayout.addWidget(setting_label,row,col)
glayout.addLayout(self.createSetting(setting_name),row,col+1)
col += 2
@ -1178,6 +1185,7 @@ class MainWindow(QtGui.QWidget):
self.progress_text += '.'
shutil.copy(zip_file, os.path.join(app_path, 'Contents', 'Resources', 'app.nw'))
self.create_icns_for_app(os.path.join(app_path, 'Contents', 'Resources', 'nw.icns'))
self.progress_text += '.'
else:
@ -1208,11 +1216,24 @@ class MainWindow(QtGui.QWidget):
finally:
shutil.rmtree(tempDir)
def create_icns_for_app(self, icns_path):
icon_setting, mac_app_icon_setting = self.getSetting('icon'), self.getSetting('mac_app_icon')
icon_path = mac_app_icon_setting.value if mac_app_icon_setting.value else icon_setting.value
if icon_path:
icon_path = os.path.join(self.projectDir(), icon_path)
if not icon_path.endswith('.icns'):
save_icns(icon_path, icns_path)
else:
shutil.copy(icon_path, icns_path)
def replace_icon_in_exe(self, exe_path):
icon_setting = self.getSetting('icon')
if icon_setting.value:
icon_setting, exe_icon_setting = self.getSetting('icon'), self.getSetting('exe_icon')
icon_path = exe_icon_setting.value if exe_icon_setting.value else icon_setting.value
if icon_path:
p = PEFile(exe_path)
p.replace_icon(os.path.join(self.projectDir(), icon_setting.value))
p.replace_icon(os.path.join(self.projectDir(), icon_path))
p.write(exe_path)
p = None

2
pepy

@ -1 +1 @@
Subproject commit 0cb12bd2f5d640064dacaab84aaaf82d93fc881e
Subproject commit 19c8f08c61e01ef107519476db95fbed1c0bfe9d

2756
png.py Normal file

File diff suppressed because it is too large Load diff

28
pycns.py Normal file
View file

@ -0,0 +1,28 @@
from icns_info import *
from image_utils import Image, nearest_icon_size, resize
import sys
"""This module takes any image that is readable by PIL and exports it to an icns file.
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)
icns_header = ICNSHeader()
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)
icns_path = icns_path if icns_path.endswith('.icns') else icns_path+'.icns'
open(icns_path, 'wb+').write(im_data)
if __name__ == '__main__':
if len(sys.argv) != 3:
print 'Usage: pycns input_image_path output_icns_path'
sys.exit()
image_path, icns_path = sys.argv[1:3]
save_icns(image_path, icns_path)