mirror of
https://github.com/reddit-archive/reddit.git
synced 2026-02-09 22:24:59 -05:00
186 lines
6.3 KiB
Python
186 lines
6.3 KiB
Python
# The contents of this file are subject to the Common Public Attribution
|
|
# License Version 1.0. (the "License"); you may not use this file except in
|
|
# compliance with the License. You may obtain a copy of the License at
|
|
# http://code.reddit.com/LICENSE. The License is based on the Mozilla Public
|
|
# License Version 1.1, but Sections 14 and 15 have been added to cover use of
|
|
# software over a computer network and provide for limited attribution for the
|
|
# Original Developer. In addition, Exhibit A has been modified to be consistent
|
|
# with Exhibit B.
|
|
#
|
|
# Software distributed under the License is distributed on an "AS IS" basis,
|
|
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
|
|
# the specific language governing rights and limitations under the License.
|
|
#
|
|
# The Original Code is reddit.
|
|
#
|
|
# The Original Developer is the Initial Developer. The Initial Developer of
|
|
# the Original Code is reddit Inc.
|
|
#
|
|
# All portions of the code written by reddit are Copyright (c) 2006-2013 reddit
|
|
# Inc. All Rights Reserved.
|
|
###############################################################################
|
|
|
|
import os
|
|
import re
|
|
import hashlib
|
|
import Image
|
|
import subprocess
|
|
|
|
from r2.lib.static import generate_static_name
|
|
|
|
SPRITE_PADDING = 1
|
|
sprite_line = re.compile(r"background-image: *url\((.*)\) *.*/\* *SPRITE *(stretch-x)? *\*/")
|
|
|
|
|
|
def optimize_png(filename, optimizer='/usr/bin/env optipng'):
|
|
with open(os.path.devnull, 'w') as devnull:
|
|
subprocess.check_call(' '.join((optimizer, filename)), shell=True, stdout=devnull)
|
|
|
|
|
|
def _extract_css_info(match):
|
|
image_filename, properties = match.groups('')
|
|
image_filename = image_filename.strip('"\'')
|
|
should_stretch = (properties == 'stretch-x')
|
|
return image_filename, should_stretch
|
|
|
|
|
|
class SpritableImage(object):
|
|
def __init__(self, image, should_stretch=False):
|
|
self.image = image
|
|
self.stretch = should_stretch
|
|
self.filenames = []
|
|
|
|
@property
|
|
def width(self):
|
|
return self.image.size[0]
|
|
|
|
@property
|
|
def height(self):
|
|
return self.image.size[1]
|
|
|
|
def stretch_to_width(self, width):
|
|
self.image = self.image.resize((width, self.height))
|
|
|
|
|
|
class SpriteBin(object):
|
|
def __init__(self, bounding_box):
|
|
# the bounding box is a tuple of
|
|
# top-left-x, top-left-y, bottom-right-x, bottom-right-y
|
|
self.bounding_box = bounding_box
|
|
self.offset = 0
|
|
self.height = bounding_box[3] - bounding_box[1]
|
|
|
|
def has_space_for(self, image):
|
|
return (self.offset + image.width <= self.bounding_box[2] and
|
|
self.height >= image.height)
|
|
|
|
def add_image(self, image):
|
|
image.sprite_location = (self.offset, self.bounding_box[1])
|
|
self.offset += image.width + SPRITE_PADDING
|
|
|
|
|
|
def _load_spritable_images(css_filename):
|
|
css_location = os.path.dirname(os.path.abspath(css_filename))
|
|
|
|
images = {}
|
|
with open(css_filename, 'r') as f:
|
|
for line in f:
|
|
m = sprite_line.search(line)
|
|
if not m:
|
|
continue
|
|
|
|
image_filename, should_stretch = _extract_css_info(m)
|
|
image = Image.open(os.path.join(css_location, image_filename))
|
|
image_hash = hashlib.md5(image.convert("RGBA").tostring()).hexdigest()
|
|
|
|
if image_hash not in images:
|
|
images[image_hash] = SpritableImage(image, should_stretch)
|
|
else:
|
|
assert images[image_hash].stretch == should_stretch
|
|
images[image_hash].filenames.append(image_filename)
|
|
|
|
# Sort images by filename to group the layout by names when possible.
|
|
return sorted(images.values(), key=lambda i: i.filenames[0])
|
|
|
|
|
|
def _generate_sprite(images, sprite_path):
|
|
sprite_width = max(i.width for i in images)
|
|
sprite_height = 0
|
|
|
|
# put all the max-width and stretch-x images together at the top
|
|
small_images = []
|
|
for image in images:
|
|
if image.width == sprite_width or image.stretch:
|
|
if image.stretch:
|
|
image.stretch_to_width(sprite_width)
|
|
image.sprite_location = (0, sprite_height)
|
|
sprite_height += image.height + SPRITE_PADDING
|
|
else:
|
|
small_images.append(image)
|
|
|
|
# lay out the remaining images -- done with a greedy algorithm
|
|
small_images.sort(key=lambda i: i.height, reverse=True)
|
|
bins = []
|
|
|
|
for image in small_images:
|
|
# find a bin to fit in
|
|
for bin in bins:
|
|
if bin.has_space_for(image):
|
|
break
|
|
else:
|
|
# or give up and create a new bin
|
|
bin = SpriteBin((0, sprite_height, sprite_width, sprite_height + image.height))
|
|
sprite_height += image.height + SPRITE_PADDING
|
|
bins.append(bin)
|
|
|
|
bin.add_image(image)
|
|
|
|
# generate the image
|
|
sprite_dimensions = (sprite_width, sprite_height)
|
|
background_color = (255, 69, 0, 0) # transparent orangered
|
|
sprite = Image.new('RGBA', sprite_dimensions, background_color)
|
|
|
|
for image in images:
|
|
sprite.paste(image.image, image.sprite_location)
|
|
|
|
sprite.save(sprite_path, optimize=True)
|
|
optimize_png(sprite_path)
|
|
|
|
# give back the mangled name
|
|
sprite_base, sprite_name = os.path.split(sprite_path)
|
|
return generate_static_name(sprite_name, base=sprite_base)
|
|
|
|
|
|
def _rewrite_css(css_filename, sprite_path, images):
|
|
# map filenames to coordinates
|
|
locations = {}
|
|
for image in images:
|
|
for filename in image.filenames:
|
|
locations[filename] = image.sprite_location
|
|
|
|
def rewrite_sprite_reference(match):
|
|
image_filename, should_stretch = _extract_css_info(match)
|
|
position = locations[image_filename]
|
|
|
|
return ''.join((
|
|
'background-image: url(%s);' % sprite_path,
|
|
'background-position: -%dpx -%dpx;' % position,
|
|
'background-repeat: %s;' % ('repeat' if should_stretch else 'no-repeat'),
|
|
))
|
|
|
|
# read in the css and replace sprite references
|
|
with open(css_filename, 'r') as f:
|
|
css = f.read()
|
|
return sprite_line.sub(rewrite_sprite_reference, css)
|
|
|
|
|
|
def spritify(css_filename, sprite_path):
|
|
images = _load_spritable_images(css_filename)
|
|
sprite_path = _generate_sprite(images, sprite_path)
|
|
return _rewrite_css(css_filename, sprite_path, images)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import sys
|
|
print spritify(sys.argv[1], sys.argv[2])
|