| 1 | #!/usr/bin/env python |
|---|
| 2 | # -*- coding:utf-8 -*- |
|---|
| 3 | """ |
|---|
| 4 | <ja> |
|---|
| 5 | yamlファイルで指定した組版を元にCSSスプライトを生成する |
|---|
| 6 | |
|---|
| 7 | = 簡単な使い方 = |
|---|
| 8 | |
|---|
| 9 | make_sprite -b ./imgs --print-coordinates sprite.yaml |
|---|
| 10 | |
|---|
| 11 | sprite.yamlと、 /imgs ディレクトリ内の画像を元に、sprite.pngを生成します。 |
|---|
| 12 | 標準出力に各画像の座標が出ます。 これはspire_css.pyの方で使います。 |
|---|
| 13 | |
|---|
| 14 | = yamlの書式 = |
|---|
| 15 | |
|---|
| 16 | (stub) |
|---|
| 17 | |
|---|
| 18 | </ja> |
|---|
| 19 | """ |
|---|
| 20 | |
|---|
| 21 | __version__ = '0.1' |
|---|
| 22 | __author__ = 'shn@glucose.jp' |
|---|
| 23 | |
|---|
| 24 | # |
|---|
| 25 | import sys, os |
|---|
| 26 | import yaml |
|---|
| 27 | import PIL.Image |
|---|
| 28 | |
|---|
| 29 | basedir = None |
|---|
| 30 | |
|---|
| 31 | S_HORIZONTAL = 'horizontal' |
|---|
| 32 | |
|---|
| 33 | HORIZONTAL = 0 |
|---|
| 34 | VERTICAL = 1 |
|---|
| 35 | |
|---|
| 36 | class SpriteBase(object): |
|---|
| 37 | def __init__(self, parent): |
|---|
| 38 | self.parent = parent |
|---|
| 39 | self.topleft = None |
|---|
| 40 | |
|---|
| 41 | def set_topleft(self, tl): |
|---|
| 42 | self.topleft = tl |
|---|
| 43 | |
|---|
| 44 | # |
|---|
| 45 | def make_position(self): |
|---|
| 46 | pass |
|---|
| 47 | |
|---|
| 48 | class Sprite(SpriteBase): |
|---|
| 49 | def __init__(self, parent, filename, image): |
|---|
| 50 | super(Sprite, self).__init__(parent) |
|---|
| 51 | |
|---|
| 52 | self.filename = filename |
|---|
| 53 | self.image = image |
|---|
| 54 | |
|---|
| 55 | def get_size(self): |
|---|
| 56 | return self.image.size |
|---|
| 57 | size = property(get_size) |
|---|
| 58 | |
|---|
| 59 | def build_image(self): |
|---|
| 60 | return self.image |
|---|
| 61 | |
|---|
| 62 | class SpriteSet(SpriteBase): |
|---|
| 63 | def __init__(self, parent, direction): |
|---|
| 64 | assert direction in (HORIZONTAL, VERTICAL) |
|---|
| 65 | super(SpriteSet, self).__init__(parent) |
|---|
| 66 | |
|---|
| 67 | self.direction = direction |
|---|
| 68 | self.sprites = [] |
|---|
| 69 | self.size = None |
|---|
| 70 | |
|---|
| 71 | def add_image(self, image): |
|---|
| 72 | #assert isinstance(image, SpriteBase) |
|---|
| 73 | self.sprites.append(image) |
|---|
| 74 | |
|---|
| 75 | def make_position(self): |
|---|
| 76 | # calc sprite size |
|---|
| 77 | get_size_f = lambda x: x |
|---|
| 78 | if self.direction == VERTICAL: |
|---|
| 79 | get_size_f = lambda x: (x[1], x[0]) |
|---|
| 80 | |
|---|
| 81 | corner = 0 |
|---|
| 82 | max_size = 0 |
|---|
| 83 | for sprite in self.sprites: |
|---|
| 84 | sprite.make_position() |
|---|
| 85 | |
|---|
| 86 | if self.direction == HORIZONTAL: |
|---|
| 87 | tl = corner, 0 |
|---|
| 88 | else: |
|---|
| 89 | tl = 0, corner |
|---|
| 90 | sprite.set_topleft(tl) |
|---|
| 91 | |
|---|
| 92 | # next |
|---|
| 93 | pri, sec = get_size_f(sprite.size) |
|---|
| 94 | corner += pri |
|---|
| 95 | max_size = max(max_size, sec) |
|---|
| 96 | |
|---|
| 97 | if self.direction == HORIZONTAL: |
|---|
| 98 | self.size = corner, max_size |
|---|
| 99 | else: |
|---|
| 100 | self.size = max_size, corner |
|---|
| 101 | |
|---|
| 102 | def build_image(self): |
|---|
| 103 | image = PIL.Image.new('RGBA', self.size, 0x000000FF) |
|---|
| 104 | |
|---|
| 105 | for sprite in self.sprites: |
|---|
| 106 | tl = sprite.topleft |
|---|
| 107 | sz = sprite.size |
|---|
| 108 | bounds = (tl[0], tl[1], tl[0] + sz[0], tl[1] + sz[1]) |
|---|
| 109 | image.paste(sprite.build_image(), bounds) |
|---|
| 110 | return image |
|---|
| 111 | |
|---|
| 112 | def print_coords(self, base_topleft=(0, 0)): |
|---|
| 113 | for sprite in self.sprites: |
|---|
| 114 | tl = base_topleft[0] + sprite.topleft[0], base_topleft[1] + sprite.topleft[1] |
|---|
| 115 | if isinstance(sprite, SpriteSet): |
|---|
| 116 | sprite.print_coords(tl) |
|---|
| 117 | else: |
|---|
| 118 | print "%s,%d,%d" % (sprite.filename, tl[0], tl[1]) |
|---|
| 119 | |
|---|
| 120 | class ImageLoader(object): |
|---|
| 121 | def __init__(self, base_dir='.'): |
|---|
| 122 | self.base_dir = base_dir |
|---|
| 123 | |
|---|
| 124 | def load(self, yamlfile): |
|---|
| 125 | sprite = self.load_sprite(None, yaml.load(open(yamlfile))) |
|---|
| 126 | sprite.make_position() |
|---|
| 127 | |
|---|
| 128 | return sprite |
|---|
| 129 | |
|---|
| 130 | def load_sprite(self, parent, dataset): |
|---|
| 131 | assert parent is None or isinstance(parent, SpriteSet) |
|---|
| 132 | |
|---|
| 133 | if isinstance(dataset, str): |
|---|
| 134 | # is sprite image file name. |
|---|
| 135 | image = PIL.Image.open(os.path.join(self.base_dir, dataset)) |
|---|
| 136 | return Sprite(parent, dataset, image) |
|---|
| 137 | elif isinstance(dataset, dict): |
|---|
| 138 | # is sprite set |
|---|
| 139 | assert 'images' in dataset |
|---|
| 140 | |
|---|
| 141 | direction = HORIZONTAL if dataset.get('direction', S_HORIZONTAL) == S_HORIZONTAL else VERTICAL |
|---|
| 142 | imagelist = dataset['images'] |
|---|
| 143 | |
|---|
| 144 | return self.load_sprite_set(parent, direction, imagelist) |
|---|
| 145 | elif isinstance(dataset, list): |
|---|
| 146 | # is sprite set by simple alsfjleaflasef. inverted direction of parent |
|---|
| 147 | direction = HORIZONTAL |
|---|
| 148 | if parent: |
|---|
| 149 | direction = 1 - parent.direction |
|---|
| 150 | return self.load_sprite_set(parent, direction, dataset) |
|---|
| 151 | |
|---|
| 152 | def load_sprite_set(self, parent, direction, imagelist): |
|---|
| 153 | ss = SpriteSet(parent, direction) |
|---|
| 154 | for dataset in imagelist: |
|---|
| 155 | ss.add_image(self.load_sprite(ss, dataset)) |
|---|
| 156 | return ss |
|---|
| 157 | |
|---|
| 158 | ### |
|---|
| 159 | def build_sprite(yamlfile, output_file='sprite.png', base_dir='.', print_coords=False): |
|---|
| 160 | sprite = ImageLoader(base_dir).load(yamlfile) |
|---|
| 161 | sprite_image = sprite.build_image() |
|---|
| 162 | sprite_image.save(output_file, 'PNG') |
|---|
| 163 | |
|---|
| 164 | if print_coords: |
|---|
| 165 | sprite.print_coords() |
|---|
| 166 | |
|---|
| 167 | def get_options(): |
|---|
| 168 | import optparse |
|---|
| 169 | |
|---|
| 170 | parser = optparse.OptionParser( |
|---|
| 171 | usage='%prog [options] YAML_FILE' |
|---|
| 172 | ) |
|---|
| 173 | parser.add_option( |
|---|
| 174 | '-o', '--output-file', |
|---|
| 175 | action='store', type='string', |
|---|
| 176 | dest='output_file', |
|---|
| 177 | default='sprite.png', |
|---|
| 178 | help="output sprite image file name. default 'sprite.png'" |
|---|
| 179 | ) |
|---|
| 180 | parser.add_option( |
|---|
| 181 | '-b', '--base-dir', |
|---|
| 182 | action='store', type='string', |
|---|
| 183 | dest='base_dir', |
|---|
| 184 | default='', |
|---|
| 185 | help="image file's base directory. default current directory" |
|---|
| 186 | ) |
|---|
| 187 | parser.add_option( |
|---|
| 188 | '--print-coordinates', |
|---|
| 189 | action='store_true', default=False, |
|---|
| 190 | dest='print_coords', |
|---|
| 191 | help="print destination coordinates" |
|---|
| 192 | ) |
|---|
| 193 | |
|---|
| 194 | options, args = parser.parse_args() |
|---|
| 195 | |
|---|
| 196 | if len(args) != 1: |
|---|
| 197 | parser.print_help() |
|---|
| 198 | parser.exit() |
|---|
| 199 | |
|---|
| 200 | return options, args |
|---|
| 201 | |
|---|
| 202 | if __name__ == "__main__": |
|---|
| 203 | options, args = get_options() |
|---|
| 204 | build_sprite(args[0], **options.__dict__) |
|---|