Added support for exporting mac app icons.
This commit is contained in:
parent
45c1e9af56
commit
a6b4808dba
6 changed files with 3581 additions and 12 deletions
726
icns_info.py
Normal file
726
icns_info.py
Normal 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
38
image_utils.py
Normal 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
43
main.py
|
|
@ -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
2
pepy
|
|
@ -1 +1 @@
|
|||
Subproject commit 0cb12bd2f5d640064dacaab84aaaf82d93fc881e
|
||||
Subproject commit 19c8f08c61e01ef107519476db95fbed1c0bfe9d
|
||||
28
pycns.py
Normal file
28
pycns.py
Normal 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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue