Created
May 19, 2011 07:34
-
-
Save mbarkhau/980354 to your computer and use it in GitHub Desktop.
Makes a sprite and css file for png images in a directory.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import os, sys | |
import subprocess | |
import Image | |
from collections import namedtuple | |
min_css = lambda (s): s.replace(" ", "").replace("\n", "") + "\n" | |
BASE_CSS = min_css(""" | |
.sprite | |
{ | |
display:inline-block; | |
overflow:hidden; | |
text-align: left !important; | |
text-indent: -99999px; | |
background: url(%(url)s) no-repeat; | |
} | |
""") | |
ICON_CSS = min_css(""" | |
.s_%(name)s | |
{ | |
width:%(w)dpx; | |
height:%(h)dpx; | |
background-position:%(x)dpx %(y)dpx; | |
} | |
""") | |
class Pos(object): | |
def __init__(self, x=0, y=0): | |
self.x = x | |
self.y = y | |
def __repr__(self): | |
return "Pos(x=" + str(self.x) + ", y=" + str(self.y) + ")" | |
class Rect(object): | |
def __init__(self, x=0, y=0, w=0, h=0): | |
self.x = x | |
self.y = y | |
self.w = w | |
self.h = h | |
@property | |
def area(self): | |
return self.w * self.h | |
@property | |
def right(self): | |
return self.x + self.w | |
@property | |
def bottom(self): | |
return self.y + self.h | |
def has_overlap(self, other): | |
return not ( | |
other.x > self.right or self.x > other.right or | |
other.y > self.bottom or self.y > other.bottom | |
) | |
class ImgRect(Rect): | |
def __init__(self, img, name): | |
self.img = img | |
self.name = name | |
self.x = self.y = 0 | |
self.w, self.h = img.size | |
def area_key(i): | |
return i.area | |
def width_key(i): | |
return i.w | |
def height_key(i): | |
return i.h | |
def read_image(path): | |
img_name = path.split("/")[-1].split('.')[0] | |
img = Image.open(path) | |
return ImgRect(img, img_name) | |
def generate(path, media_path='/img/', sprite_file='sprites.png', css_file='sprites.css'): | |
""" Creates a sprite and css file in the specified directory | |
path : The filesystem path to your media files | |
media_path : The url from which the media files are served | |
(used in generated css file) | |
sprite_file : The name of the sprite image (placed in path) | |
css_file : The name of the css file (placed in path) | |
""" | |
if not os.path.exists(path): | |
print "No such directory: %s" % path | |
return | |
path = os.path.abspath(path) | |
sprite_url = media_path + sprite_file | |
sprite_file = os.path.join(path, sprite_file) | |
css_file = os.path.join(path, css_file) | |
files = os.listdir(path) | |
def is_image_file(f): | |
return ( | |
not sprite_file.endswith(f) and ( | |
f.endswith('png') or | |
f.endswith('jpg') or | |
f.endswith('jpeg') or | |
f.endswith('gif') | |
) | |
) | |
image_paths = [os.path.join(path, f) for f in files if is_image_file(f)] | |
if not image_paths: | |
print "No images found in %s" % path | |
return | |
print "compiling %d icons" % len(image_paths) | |
images = [read_image(p) for p in image_paths] | |
def sprite_size(images): | |
"""Returns the dimensions that can contain all of the images""" | |
max_x = 0 | |
max_y = 0 | |
for img in images: | |
max_x = max(max_x, img.right) | |
max_y = max(max_y, img.bottom) | |
return [max_x, max_y] | |
def expansion(img, pos, frame): | |
"""returns: extra area required, if the img uses pos""" | |
p_right = pos.x + img.w | |
p_bottom = pos.y + img.h | |
exp = 0 | |
if p_right > frame.w: | |
exp += (p_right - frame.w) * frame.h | |
if p_bottom > frame.h: | |
exp += (p_bottom - frame.h) * frame.w | |
return exp | |
def select_pos(img, positions, frame): | |
"""select pos which expands frame the least""" | |
min_pos = None | |
min_exp = 0 | |
for pos in positions: | |
exp = expansion(img, pos, frame) | |
# is_inward = pos.x < min_pos.x or pos.y < min_pos.y | |
# or exp == 0 and is_inward | |
if not min_pos or min_exp > exp: | |
min_pos = pos | |
min_exp = exp | |
return min_pos | |
def position_img(pos, img, frame): | |
"""update relevant data when positioning img in frame""" | |
img.x = pos.x | |
img.y = pos.y | |
frame.w = max(frame.w, img.right) | |
frame.h = max(frame.h, img.bottom) | |
def new_positions(pos, img, positions, positioned): | |
positioned.append(img) | |
positions.remove(pos) | |
r = Rect() | |
r.w = img.w | |
r.h = img.h | |
new_positions = [] | |
for p in positions: | |
r.x = p.x | |
r.y = p.y | |
if not img.has_overlap(r): | |
new_positions.append(p) | |
def has_overlap(pos): | |
r.w = 1000 | |
r.h = 1000 | |
r.x = pos.x | |
r.y = pos.y | |
for p_img in positioned: | |
if p_img.has_overlap(r): | |
return True | |
return False | |
below = Pos(img.right+1, img.y) | |
right = Pos(img.x, img.bottom+1) | |
if not has_overlap(below): | |
new_positions.append(below) | |
if not has_overlap(right): | |
new_positions.append(right) | |
return new_positions | |
def pack(images): | |
"""Set x,y coordinates for images, such that they fit in a sprit | |
returns rect with dimensions for sprite | |
""" | |
frame = Rect(0, 0, 0, 0) | |
positions = [ Pos(0, 0) ] | |
positioned = [] | |
for img in images: | |
pos = select_pos(img, positions, frame) | |
position_img(pos, img, frame) | |
positions = new_positions(pos, img, positions, positioned) | |
return frame | |
def gen_sprite(images, frame): | |
sprite_img = Image.new(mode='RGBA', size=(frame.w, frame.h)) | |
for img in images: | |
sprite_img.paste(img.img, (img.x, img.y)) | |
sprite_img.save(sprite_file) | |
cmd = "optipng -q %s" % sprite_file | |
ret_code = subprocess.call(cmd, shell=True) | |
if ret_code == 127: | |
print "You should install 'optipng'." | |
elif ret_code == 0: | |
print "created %s" % sprite_file | |
def gen_css(images, frame): | |
icon_css = [] | |
for img in images: | |
arg = { | |
'name': img.name, | |
'x': img.x, | |
'y': img.y, | |
'w': img.w, | |
'h': img.h | |
} | |
icon_css.append(ICON_CSS % arg) | |
with open(css_file, 'w') as css: | |
arg = { 'url': sprite_url, 'w': frame.w, 'h': frame.h } | |
css.write(BASE_CSS % arg) | |
css.write("".join(icon_css)) | |
print "created %s" % css_file | |
images.sort(key=area_key, reverse=True) | |
frame = pack(images) | |
gen_sprite(images, frame) | |
gen_css(images, frame) | |
USAGE = """Usage: | |
1. Put the images you want to sprite in a directory | |
2. Run simple_sprite.py in that directory/pass it the path | |
3. profit... | |
> simple_sprite.py /path/to/images | |
compiling 22 icons | |
created /path/to/images/sprites.png | |
created /path/to/images/sprites.css | |
You may need to modify background-image:url(/img/sprites.png) | |
in sprites.css depending on your webserver configuration. | |
In your page you can use the images like so: | |
<span class="sprite s_{{example_icon}}></span> # ommit .png | |
Depends on PIL (python image library) | |
You should also install optipng | |
Works only with .png, .jpg, and .gif files | |
""" | |
def main(args): | |
if len(args) == 1: | |
path = '.' | |
else: | |
path = args[1] | |
if not path or path in ('-h', '--help'): | |
print USAGE | |
else: | |
generate(path) | |
if __name__ == '__main__': | |
try: | |
main(sys.argv) | |
except Exception, e: | |
print e | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment