Back
Mar 4, 2011

Css sprite generation

I've created this small sprite to create css sprites. It glues images from directory directory into single file and generates corresponding css.

#!/usr/bin/python

import os
import os.path as p
import Image


css_inline_item_template = '''
%(selector)s {
    background: url(%(url)s) no-repeat -%(xpos)spx -%(ypos)spx;
    width: %(width)spx;
    height: %(height)spx;
}
'''

css_base_template = '''
%(base_selector)s {
    background-image: url(%(url)s);
    background-repeat: no-repeat;
}
'''

css_base_item_template = '''
%(selector)s {
    background-position: -%(xpos)spx -%(ypos)spx;
    width: %(width)spx;
    height: %(height)spx;
}
'''


def generate_sprite(images, margin=10):
    def _basename(path):
        return p.splitext(p.basename(path))[0]

    data = []

    master_height = max(*[i.size[1] for i in images])
    master_width = sum([i.size[0] for i in images]) + margin * (len(images) - 1)

    sprite = Image.new(
        mode='RGBA',
        size=(master_width, master_height),
        color=(0,0,0,0))  # fully transparent

    location = 0
    for image in images:
        if 'transparency' in image.info:
            raise Exception('Incorrect transparency mode: ' + image.filename)

        sprite.paste(image,(location, 0))
        data.append({
            'xpos': location,
            'ypos': 0,
            'dir': _basename(p.dirname(image.filename)),
            'file': _basename(image.filename),
            'width': image.size[0],
            'height': image.size[1],
        })
        location += image.size[0] + margin

    return sprite, data


def render_css(base_template, item_template, css_data, sprite_url, base_selector, selector):
    base_info = {
        'base_selector': base_selector,
        'url': sprite_url,
    }
    base_css = base_template % base_info

    items_css = '\n'.join([
        item_template % dict([('selector', selector % image_data)]
            + image_data.items()
            + base_info.items())
        for image_data in css_data])

    return base_css + items_css


def create_sprite_and_css(images_dir, css_output, sprite_output,
        selector='.%(file)s', base_selector='.sprite', url=None, inline=False):
    images = [Image.open(p.join(images_dir, filename))
                for filename in os.listdir(images_dir)
                if not filename.startswith('.')]

    sprite, css_data = generate_sprite(images)
    sprite.save(sprite_output)

    sprite_url = url or p.basename(sprite_output)
    if inline:
        css = render_css('', css_inline_item_template, css_data, sprite_url, base_selector, selector)
    else:
        css = render_css(css_base_template, css_base_item_template, css_data, sprite_url, base_selector, selector)

    css_file = open(css_output, 'w')
    css_file.write(css)
    css_file.close()


if __name__ == '__main__':
    from optparse import OptionParser

    usage = "usage: %prog [options] images_dir css_output sprite_output"
    parser = OptionParser(usage=usage)
    parser.add_option("-i", "--inline", action="store_true", dest="inline", default=False,
                      help="When set, image url will be put in css rule for every item.")
    parser.add_option("-s", "--selector", dest="selector", default='.%(file)s',
                      help="Selector template. You can use dir, file, width, height here.")
    parser.add_option("-S", "--base-selector", dest="base_selector", default='.sprite',
                      help="Base selector template for base css rule (when -i is not set).")
    parser.add_option("-u", "--url", dest="url", default='',
                      help="Css rule Image url template.")

    (options, args) = parser.parse_args()

    if len(args) < 3:
        print 'You must provide all 3 arguments.'
    else:
        images_dir, css_output, sprite_output = [p.realpath(arg) for arg in args]
        create_sprite_and_css(images_dir, css_output, sprite_output,
                selector=options.selector,
                base_selector=options.base_selector,
                inline=options.inline,
                url=options.url)

Update

This article is pretty old. Now it's better to use something integrated to your build process, like grunt or webassets.

Subscribe for the news and updates

More thoughts
Apr 11, 2024Technology
Test Analysis at the Feature Level

In the previous article, we learned about test analysis for a product in general, and now we are ready to go further and look at test analysis for specific features.

Jun 27, 2018Technology
How to Work With Legacy Code: Code Refactoring Techniques

In this article we'll review general approach to working with the best kind of projects - the ones with old untested and undocumented spaghetti code and a tight schedule. We'll review anger management techniques, coping mechanisms and some refactoring tips that might come in handy.

Jan 22, 2017Technology
Django vs Rails Performance

This article is aimed for beginners, who are trying to choose between Ruby on Rails and Django. Let’s see which is fastest and why.

Apr 3, 2011Technology
Sprite cache invalidation

When we use css-sprites it's important to make browser cache them for longest period possible. On other hand, we need to refresh them when they are updated. This is especially visible when all icons are stored in single sprite. When it's outdated - entire site becomes ugly.

Jul 1, 2010Technology
Overriding QuerySet in Django

As you know, model managers can be overriden in Django. It's convenient to add custom filtration method.

Mar 6, 2010TechnologyManagement
Supplementing settings in settings_local

For local project settings, I use old trick with settings_local file:try:from settings_local import \*except ImportError:passSo in settings_local.py we can override variables from settings.py. I didn't know how to supplement them. For example how to add line to INSTALLED_APPS without copying whole list.Yesterday I finally understood that I can import settings from settings_local:# settings_local.pyfrom settings import \*INSTALLED_APPS += (# ...)