Small doc icons look sharper, faster generation

This commit is contained in:
Nico Weber
2008-12-23 21:11:42 +01:00
committed by Bjorn Winckler
parent a5be20ba89
commit 48fc2be299
2 changed files with 247 additions and 41 deletions
+243 -41
View File
@@ -4,8 +4,6 @@
# for 'PDF' and compare the D with the D in Preview's pdf.icns
# http://www.macresearch.org/cocoa-scientists-part-xx-python-scriptersmeet-cocoa
import os
import sys
try:
from Foundation import *
from AppKit import *
@@ -13,6 +11,10 @@ try:
except:
dont_create = True # most likely because we're on tiger
import math
import os
import sys
# icon types
LARGE = 0 # 512, 128, 32, 16; about 96kB
SMALL = 1 # 128, 32, 16; about 36kB
@@ -22,6 +24,7 @@ LINK = 2 # Create link to generic icon; 4kB (== smallest block size on HFS+)
MAKEICNS = 'makeicns/makeicns'
# List of icons to create
# XXX: 32x32 variants only support 3-4 letters of text
GENERIC_ICON_NAME = 'MacVim-generic'
vimIcons = {
GENERIC_ICON_NAME: [u'', LARGE],
@@ -49,7 +52,7 @@ vimIcons = {
'MacVim-asp': [u'ASP', LINK],
'MacVim-bib': [u'BIB', LINK],
'MacVim-cs': [u'C#', LINK],
'MacVim-csfg': [u'CFDG', LINK], #D
'MacVim-csfg': [u'CFDG', LINK],
'MacVim-csv': [u'CSV', LINK],
'MacVim-tsv': [u'TSV', LINK],
'MacVim-cgi': [u'CGI', LINK],
@@ -64,7 +67,7 @@ vimIcons = {
'MacVim-ics': [u'ICS', SMALL],
'MacVim-ini': [u'INI', LINK],
'MacVim-io': [u'IO', LINK],
'MacVim-bsh': [u'BSH', LINK], #D
'MacVim-bsh': [u'BSH', LINK],
'MacVim-properties': [u'PROP', LINK],
'MacVim-jsp': [u'JSP', SMALL],
'MacVim-lisp': [u'LISP', SMALL],
@@ -79,7 +82,19 @@ vimIcons = {
'MacVim-vcf': [u'VCARD', SMALL],
'MacVim-vb': [u'VBASIC', LINK],
'MacVim-yaml': [u'YAML', SMALL],
'MacVim-gtd': [u'GTD', LINK], #D
'MacVim-gtd': [u'GTD', LINK],
}
shorttext = {
u'MacVim-py': u'PY',
u'MacVim-rb': u'RB',
u'MacVim-perl': u'PL',
u'MacVim-applescript': u'\uf8ffS',
u'MacVim-erl': u'ERL',
u'MacVim-fscript': u'FSCR',
u'MacVim-sch': u'SCM',
u'MacVim-vcf': u'VCF',
u'MacVim-vb': u'VB',
}
@@ -90,56 +105,223 @@ APPICON = 'vim-noshadow-512.png'
#APPICON = 'vim-noshadow-no-v-512.png'
def createIcon(outname, text, iconname=APPICON, bgname=BACKGROUND):
# Prepare input images
bg = NSImage.alloc().initWithContentsOfFile_(bgname)
if not bg:
print 'Failed to load', bgname
sys.exit(1)
def splitGenericDocumentIcon(img, s):
"""Takes the generic document icon and splits it into a background and a
shadow layer. For the 32x32 and 16x16 variants, the white pixels of the page
curl are hardcoded into the otherwise transparent shadow layer."""
icon = NSImage.alloc().initWithContentsOfFile_(iconname)
if not icon:
print 'Failed to load', iconname
sys.exit(1)
r = None
for rep in img.representations():
if map(int, rep.size()) == [s, s]:
r = rep
break
if not r:
raise Exception('Unsupported size %d', s)
# XXX: This is a bit slow in python, perhaps do this in C
if r.bitmapFormat() != (NSAlphaNonpremultipliedBitmapFormat |
NSAlphaFirstBitmapFormat) or \
r.bitsPerPixel() != 32 or \
r.isPlanar() or \
r.samplesPerPixel() != 4:
raise Exception("Unsupported image format")
w, h = map(int, r.size()) # cocoa returns floats. cocoa ftw.
bps = 4*w
data = r.bitmapData()
# These do not have alpha first!
ground = NSBitmapImageRep.alloc().initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bitmapFormat_bytesPerRow_bitsPerPixel_(
None, w, h, 8, 4, True, False, NSDeviceRGBColorSpace,
NSAlphaNonpremultipliedBitmapFormat, 0, 0)
shadow = NSBitmapImageRep.alloc().initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bitmapFormat_bytesPerRow_bitsPerPixel_(
None, w, h, 8, 4, True, False, NSDeviceRGBColorSpace,
NSAlphaNonpremultipliedBitmapFormat, 0, 0)
grounddata = ground.bitmapData()
shadowdata = shadow.bitmapData()
for y in xrange(h):
for x in xrange(w):
idx = y*bps + 4*x
ia, ir, ig, ib = data[idx:idx + 4]
if ia != chr(255):
# buffer objects don't support slice assignment :-(
grounddata[idx] = ir
grounddata[idx + 1] = ig
grounddata[idx + 2] = ib
grounddata[idx + 3] = ia
shadowdata[idx] = chr(0)
shadowdata[idx + 1] = chr(0)
shadowdata[idx + 2] = chr(0)
shadowdata[idx + 3] = chr(0)
continue
assert ir == ig == ib
grounddata[idx] = chr(255)
grounddata[idx + 1] = chr(255)
grounddata[idx + 2] = chr(255)
grounddata[idx + 3] = chr(255)
shadowdata[idx] = chr(0)
shadowdata[idx + 1] = chr(0)
shadowdata[idx + 2] = chr(0)
shadowdata[idx + 3] = chr(255 - ord(ir))
# Special-case 16x16 and 32x32 cases: Make some pixels on the fold white.
# Ideally, I could make the fold whiteish in all variants, but I can't.
whitePix = { 16: [(10, 2), (10, 3), (11, 3), (10, 4), (11, 4), (12, 4)],
32: [(21, 4), (21, 5), (22, 5), (21, 6), (22, 6), (23, 6)]}
if (w, h) in [(16, 16), (32, 32)]:
for x, y in whitePix[w]:
idx = y*bps + 4*x
shadowdata[idx] = chr(255)
shadowdata[idx + 1] = chr(255)
shadowdata[idx + 2] = chr(255)
shadowdata[idx + 3] = chr(255)
return ground, shadow
def drawText(text, s):
"""Draws text `s` into the current context of size `s`."""
# This looks not exactly like the font on Preview.app's document icons,
# but I believe that's because Preview's icons are drawn by Photoshop,
# and Adobe's font rendering is different from Apple's.
fontname = 'LucidaGrande-Bold'
# Prepare text format
style = NSMutableParagraphStyle.new()
style.setParagraphStyle_(NSParagraphStyle.defaultParagraphStyle())
style.setAlignment_(NSCenterTextAlignment)
# http://developer.apple.com/documentation/Cocoa/Conceptual/AttributedStrings/Articles/standardAttributes.html#//apple_ref/doc/uid/TP40004903
fontname = 'LucidaGrande-Bold'
attribs = {
NSParagraphStyleAttributeName: style,
NSParagraphStyleAttributeName: style,
NSFontAttributeName: NSFont.fontWithName_size_(fontname, 72.0),
NSKernAttributeName: -1.0, # tighten font a bit
NSForegroundColorAttributeName: NSColor.colorWithDeviceWhite_alpha_(
NSParagraphStyleAttributeName: style,
NSForegroundColorAttributeName: NSColor.colorWithDeviceWhite_alpha_(
0.34, 1)
}
if s == 512:
attribs[NSFontAttributeName] = NSFont.fontWithName_size_(fontname, 72.0)
attribs[NSKernAttributeName] = -1.0 # tighten font a bit
elif s == 256:
attribs[NSFontAttributeName] = NSFont.fontWithName_size_(fontname, 36.0)
attribs[NSKernAttributeName] = -1.0 # tighten font a bit
elif s == 128:
attribs[NSFontAttributeName] = NSFont.fontWithName_size_(fontname, 18.0)
elif s == 32:
#attribs[NSFontAttributeName] = NSFont.fontWithName_size_(
#'LucidaSans-Demi', 7.0)
attribs[NSKernAttributeName] = -0.25 # tighten font a bit
if NSFontAttributeName not in attribs:
attribs[NSFontAttributeName] = NSFont.fontWithName_size_(fontname, 7.0)
elif s == 16:
attribs[NSFontAttributeName] = NSFont.fontWithName_size_(fontname, 3.0)
if not attribs[NSFontAttributeName]:
print 'Failed to load font', fontname
sys.exit(1)
textRects = {
512: ((0, 7), (512, 119)),
128: ((0, 6), (128, 26.5)),
256: ((0, 7), (256, 57)),
}
if s in [128, 256, 512]:
text.drawInRect_withAttributes_(textRects[s], attribs)
elif s == 32:
#text.drawInRect_withAttributes_( ((1, 1), (31, 9)), attribs)
# Try to align text on pixel boundary:
ts = text.sizeWithAttributes_(attribs)
attribs[NSParagraphStyleAttributeName] = \
NSParagraphStyle.defaultParagraphStyle()
text.drawAtPoint_withAttributes_( (math.floor((32.0-ts[0])/2) + 0.5, 1.5),
attribs)
# for demibold roman:
#text.drawInRect_withAttributes_( ((0, 1), (31, 11)), attribs)
elif s == 16:
text.drawInRect_withAttributes_( ((1, 1), (15, 5)), attribs)
def createIcon(outname, text, ground, icon, shadow=None, s=512,
shorttext=None):
output = NSBitmapImageRep.alloc().initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bitmapFormat_bytesPerRow_bitsPerPixel_(
None, s, s, 8, 4, True, False, NSDeviceRGBColorSpace, 0, 0, 0)
# Draw!
bg.lockFocus()
w, h = 289, 289
icon.drawInRect_fromRect_operation_fraction_(
(((512-w)/2 + 1, 405 - h), (w, h)),
NSGraphicsContext.saveGraphicsState();
context = NSGraphicsContext. graphicsContextWithBitmapImageRep_(output);
context.setShouldAntialias_(True);
context.setImageInterpolation_(NSImageInterpolationHigh);
NSGraphicsContext.setCurrentContext_(context);
# luckily, cocoa simply copies the 128x128 version over for s = 128
# and does no resampling.
ground.draw()
#bg.drawInRect_fromRect_operation_fraction_(
#((0, 0), (s, s)),
#NSZeroRect, NSCompositeCopy, 1.0)
# found by flow program, better than anything i came up with manually before
# (except for the 16x16 variant :-( )
transforms = {
512: [ 0.7049, 0.5653, -4.2432, 0.5656],
256: [ 0.5690, 0.5658, -1.9331, 0.5656],
128: [ 1.1461, 0.5684, -0.8482, 0.5681],
32: [-0.2682, 0.5895, -2.2130, 0.5701], # intensity
#32: [-0.2731, 0.5898, -2.2262, 0.5729], # rgb (no rmse difference)
#16: [-0.3033, 0.4909, -1.3235, 0.4790], # program, intensity
#16: [-0.3087, 0.4920, -1.2990, 0.4750], # program, rgb mode
16: [ 0.0000, 0.5000, -1.0000, 0.5000], # manually, better
}
if s in [16, 32, 128, 256, 512]:
a = transforms[s]
# convert from `flow` coords to cocoa
a[2] = -a[2] # mirror y
w, h = s*a[1], s*a[3]
icon.drawInRect_fromRect_operation_fraction_(
(((s-w)/2 + a[0], (s-h)/2 + a[2]), (w, h)),
NSZeroRect, NSCompositeSourceOver, 1.0)
# Overlay shadow.
# shadow needs to be composited, so it needs to be in an nsimage
shadowImg = NSImage.alloc().initWithSize_( (s, s) )
shadowImg.addRepresentation_(shadow)
shadowImg.drawInRect_fromRect_operation_fraction_(
((0, 0), (s, s)),
NSZeroRect, NSCompositeSourceOver, 1.0)
text.drawInRect_withAttributes_( ((0, 7), (512, 119)), attribs)
bg.unlockFocus()
# draw text on top of shadow
if s in [16, 32] and shorttext:
text = shorttext
drawText(text, s)
NSGraphicsContext.restoreGraphicsState();
# Save
# http://www.cocoadev.com/index.pl?NSImageToJPEG (this is retarded)
tmp = NSBitmapImageRep.imageRepWithData_(bg.TIFFRepresentation())
png = tmp.representationUsingType_properties_(NSPNGFileType, None)
png = output.representationUsingType_properties_(NSPNGFileType, None)
png.writeToFile_atomically_(outname, True)
TMPFILE = 'make_icons_tmp.png'
TMPFILE = 'make_icons_tmp_%d.png'
sizes = [512, 128, 32, 16]
def main():
srcdir = os.getcwd()
if len(sys.argv) > 1:
@@ -158,16 +340,27 @@ def main():
os.remove(icnsName)
os.symlink('%s.icns' % GENERIC_ICON_NAME, icnsName)
return
# Make us not crash
# http://www.cocoabuilder.com/archive/message/cocoa/2008/8/6/214964
NSApplicationLoad()
#createIcon('test.png',
#NSString.stringWithString_(u'PDF'), iconname='preview.icns')
# Prepare input images
bg = NSImage.alloc().initWithContentsOfFile_(BACKGROUND)
if not bg:
print 'Failed to load', bgname
sys.exit(1)
grounds, shadows = zip(*[splitGenericDocumentIcon(bg, s) for s in sizes])
grounds = dict(zip(sizes, grounds))
shadows = dict(zip(sizes, shadows))
icon = NSImage.alloc().initWithContentsOfFile_(appIcon)
if not icon:
print 'Failed to load', appIcon
sys.exit(1)
if not os.access(makeIcns, os.X_OK):
print 'Cannot find makeicns at', makeIcns
print 'Cannot find makeicns at %s', makeIcns
return
# create LARGE and SMALL icons first...
@@ -177,19 +370,26 @@ def main():
print name
icnsName = '%s.icns' % name
createIcon(TMPFILE, NSString.stringWithString_(text), appIcon)
for s in sizes:
st = shorttext.get(name)
if st: st = NSString.stringWithString_(st)
createIcon(TMPFILE % s, NSString.stringWithString_(text),
grounds[s], icon, shadows[s], s=s, shorttext=st)
if size == LARGE:
os.system('%s -512 %s -128 %s -32 %s -16 %s -out %s' % (makeIcns,
TMPFILE, TMPFILE, TMPFILE, TMPFILE, icnsName))
TMPFILE % 512, TMPFILE % 128, TMPFILE % 32, TMPFILE % 16, icnsName))
elif size == SMALL:
os.system('%s -128 %s -32 %s -16 %s -out %s' % (makeIcns,
TMPFILE, TMPFILE, TMPFILE, icnsName))
TMPFILE % 128, TMPFILE % 32, TMPFILE % 16, icnsName))
del text, size, name, t
# ...create links later (to make sure the link targets exist)
for name, t in vimIcons.iteritems():
text, size = t
if size != LINK: continue
print name
print 'symlinking', name
icnsName = '%s.icns' % name
# remove old version of icns
@@ -198,9 +398,11 @@ def main():
os.symlink('%s.icns' % GENERIC_ICON_NAME, icnsName)
if __name__ == '__main__':
try:
main()
finally:
if os.access(TMPFILE, os.F_OK):
os.remove(TMPFILE)
for s in sizes:
if os.access(TMPFILE % s, os.F_OK):
os.remove(TMPFILE % s)
+4
View File
@@ -54,6 +54,9 @@ void usage() {
NSBitmapImageRep* getBitmapImageRepOfSize(NSImage* img, int size) {
// Don't resample if it's not necessary
// XXX: Seems as if this creates problems in some situations, disable this
// for now.
#if 0
NSEnumerator* e = [[img representations] objectEnumerator];
NSImageRep* ir;
while ((ir = [e nextObject])) {
@@ -68,6 +71,7 @@ NSBitmapImageRep* getBitmapImageRepOfSize(NSImage* img, int size) {
)
return br;
}
#endif
NSLog(@"Resampling for size %d", size);
NSBitmapImageRep* r = [[NSBitmapImageRep alloc]