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
Aug 18, 2022Technology
5 Best Practices of RESTful API Design to Keep Your Users Happy

Dive into our guide to RESTful API best practices

Sep 1, 2021TechnologyBusiness
Top 10 Web Development Frameworks in 2021 - 2022

We have reviewed the top web frameworks for server and client-side development and compared their pros and cons. Find out which one can be a great fit for your next project.

Sep 21, 2020Technology
How to Optimize Django ORM Queries

Django ORM is a very abstract and flexible API. But if you do not know exactly how it works, you will likely end up with slow and heavy views, if you have not already. So, this article provides practical solutions to N+1 and high loading time issues. For clarity, I will create a simple view that demonstrates common ORM query problems and shows frequently used practices.

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.

Sep 23, 2010Technology
Dynamic class generation, QuerySetManager and use_for_related_fields

It appears that not everyone knows that in python you can create classes dynamically without metaclasses. I'll show an example of how to do it.So we've learned how to use custom QuerySet to chain requests:Article.objects.old().public()Now we need to make it work for related objects:user.articles.old().public()This is done using use_for_related_fields, but it needs a little trick.

Mar 6, 2010Technology
Ajax form validation

There was a task to submit form with ajax, with server side validation of course. Obvious solution is to do validation and return json with erros. I didn't like idea of writing separate view for validation and then inserting errors in form html on client side. Especially since I already had a generic template for django form with errors display. In this article I'll describe how I solved the task.