diff options
author | Руслан Ижбулатов <lrn1986@gmail.com> | 2015-05-13 14:05:23 +0000 |
---|---|---|
committer | Jakub Steiner <jimmac@gmail.com> | 2015-05-13 19:07:38 +0200 |
commit | 315db6bc0a67984715b582320621d443f4990f30 (patch) | |
tree | d7c1dd38a94f152bedaf6c179719dd90532fb7a4 | |
parent | 778e229776e3edc26f5db9743ca1b2da7cc4448b (diff) | |
download | adwaita-icon-theme-315db6bc0a67984715b582320621d443f4990f30.tar.gz |
Add anicursorgen.py
https://bugzilla.gnome.org/show_bug.cgi?id=749053
-rw-r--r-- | src/cursors/anicursorgen.py | 297 |
1 files changed, 297 insertions, 0 deletions
diff --git a/src/cursors/anicursorgen.py b/src/cursors/anicursorgen.py new file mode 100644 index 000000000..ad13bfffe --- /dev/null +++ b/src/cursors/anicursorgen.py @@ -0,0 +1,297 @@ +#!/usr/bin/python2 +# -*- coding: utf-8 -*- +# anicursorgen +# Copyright (C) 2015 Руслан Ижбулатов <lrn1986@gmail.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import print_function + +import sys +import os +import argparse +import shlex +import io +import struct +from PIL import Image + +p = struct.pack + +program_name = 'anicursorgen' +program_version = 1.0 + +def main (): + parser = argparse.ArgumentParser (description='Creates .ani or .cur files from separate images and input metadata.', + add_help=False) + parser.add_argument ('-V', '--version', action='version', version='{}-{}'.format (program_name, program_version), + help='Display the version number and exit.') + parser.add_argument ('-h', '-?', action='help', + help='Display the usage message and exit.') + parser.add_argument ('-p', '--prefix', metavar='dir', default=None, + help='Find cursor images in the directory specified by dir. If not specified, the current directory is used.') + + parser.add_argument ('input_config', default='-', metavar='input-config [output-file]', nargs='?', + help='Input config file (stdin by default).') + parser.add_argument ('output_file', default='-', metavar='', nargs='?', + help='Output cursor file (stdout by default).') + + args = parser.parse_args () + + if args.prefix is None: + args.prefix = os.getcwd () + + if args.input_config == '-': + input_config = sys.stdin + else: + input_config = open (args.input_config, 'rb') + + if args.output_file == '-': + output_file = sys.stdout + else: + output_file = open (args.output_file, 'wb') + + result = make_cursor_from (input_config, output_file, args.prefix) + + input_config.close () + output_file.close () + + return result + +def make_cursor_from (inp, out, prefix): + frames = parse_config_from (inp, prefix) + + animated = frames_have_animation (frames) + + if animated: + result = make_ani (frames, out) + else: + buf = make_cur (frames) + copy_to (out, buf) + result = 0 + + return result + +def copy_to (out, buf): + buf.seek (0, io.SEEK_SET) + while True: + b = buf.read (1024) + if len (b) == 0: + break + out.write (b) + +def frames_have_animation (frames): + sizes = set () + + for frame in frames: + if frame[4] == 0: + continue + if frame[0] in sizes: + return True + sizes.add (frame[0]) + + return False + +def make_cur (frames): + buf = io.BytesIO () + buf.write (p ('<HHH', 0, 2, len (frames))) + frame_offsets = [] + for frame in frames: + width = frame[0] + if width > 255: + width = 0 + height = width + buf.write (p ('<BBBB HH', width, height, 0, 0, frame[1], frame[2])) + size_offset_pos = buf.seek (0, io.SEEK_CUR) + buf.write (p ('<II', 0, 0)) + frame_offsets.append ([size_offset_pos]) + + for i, frame in enumerate (frames): + frame_offset = buf.seek (0, io.SEEK_CUR) + frame_offsets[i].append (frame_offset) + + compressed = frame[0] > 48 + + if compressed: + write_png (buf, frame) + else: + write_cur (buf, frame) + + frame_end = buf.seek (0, io.SEEK_CUR) + frame_offsets[i].append (frame_end - frame_offset) + + for frame_offset in frame_offsets: + buf.seek (frame_offset[0]) + buf.write (p ('<II', frame_offset[2], frame_offset[1])) + + return buf + +def make_framesets (frames): + framesets = [] + sizes = set () + + # This assumes that frames are sorted + size = 0 + for i, frame in enumerate (frames): + if size == 0 or frame[0] != size: + size = frame[0] + counter = 0 + + if size in sizes: + print ("Frames are not sorted: frame {} has size {}, but we have seen that already".format (i, size), file=sys.stderr) + return None + + sizes.add (size) + + if counter >= len (framesets): + framesets.append ([]) + + framesets[counter].append (frame) + counter += 1 + + for i in range (1, len (framesets)): + if len (framesets[i - 1]) != len (framesets[i]): + print ("Frameset {} has size {}, expected {}".format (i, len (framesets[i]), len (framesets[i - 1])), file=sys.stderr) + return None + + for frameset in framesets: + for i in range (1, len (frameset)): + if frameset[i - 1][4] != frameset[i][4]: + print ("Frameset {} has duration {} for framesize {}, but {} for framesize {}".format (i, frameset[i][4], frameset[i][0], frameset[i - 1][4], frameset[i - 1][0]), file=sys.stderr) + return None + + return framesets + +def make_ani (frames, out): + framesets = make_framesets (frames) + if framesets is None: + return 1 + + buf = io.BytesIO () + + buf.write (b'RIFF') + riff_len_pos = buf.seek (0, io.SEEK_CUR) + buf.write (p ('<I', 0)) + riff_len_start = buf.seek (0, io.SEEK_CUR) + + buf.write (b'ACON') + buf.write (b'anih') + buf.write (p ('<IIIIIIIIII', 36, 36, len (framesets), len (framesets), 0, 0, 32, 1, framesets[0][0][4], 0x01)) + + rates = set () + for frameset in framesets: + rates.add (frameset[0][4]) + + if len (rates) != 1: + buf.write (b'rate') + buf.write (p ('<I', len (framesets) * 4)) + for frameset in framesets: + buf.write (p ('<I', frameset[0][4])) + + buf.write (b'LIST') + list_len_pos = buf.seek (0, io.SEEK_CUR) + buf.write (p ('<I', 0)) + list_len_start = buf.seek (0, io.SEEK_CUR) + + buf.write (b'fram') + + for frameset in framesets: + buf.write (b'icon') + cur = make_cur (frameset) + cur_size = cur.seek (0, io.SEEK_END) + aligned_cur_size = cur_size + #if cur_size % 4 != 0: + # aligned_cur_size += 4 - cur_size % 2 + buf.write (p ('<i', cur_size)) + copy_to (buf, cur) + pos = buf.seek (0, io.SEEK_END) + if pos % 2 != 0: + buf.write ('\x00' * (2 - (pos % 2))) + + end_at = buf.seek (0, io.SEEK_CUR) + buf.seek (riff_len_pos, io.SEEK_SET) + buf.write (p ('<I', end_at - riff_len_start)) + buf.seek (list_len_pos, io.SEEK_SET) + buf.write (p ('<I', end_at - list_len_start)) + + copy_to (out, buf) + + return 0 + +def write_png (out, frame): + with open (frame[3], 'rb') as src: + while True: + buf = src.read (1024) + if buf is None or len (buf) == 0: + break + out.write (buf) + +def write_cur (out, frame): + img = Image.open (frame[3]) + pixels = img.load () + + out.write (p ('<I II HH IIIIII', 40, frame[0], frame[0] * 2, 1, 32, 0, 0, 0, 0, 0, 0)) + + for y in reversed (range (frame[0])): + for x in range (frame[0]): + pixel = pixels[x, y] + out.write (p ('<BBBB', pixel[2], pixel[1], pixel[0], pixel[3])) + + acc = 0 + acc_pos = 0 + for y in reversed (range (frame[0])): + wrote = 0 + for x in range (frame[0]): + if pixels[x, y][3] <= 127: + acc = acc | (1 << acc_pos) + acc_pos += 1 + if acc_pos == 8: + acc_pos = 0 + out.write (chr (acc)) + wrote += 1 + if wrote % 4 != 0: + out.write (b'\x00' * (4 - wrote % 4)) + +def parse_config_from (inp, prefix): + frames = [] + + for line in inp.readlines (): + words = shlex.split (line.rstrip ('\n').rstrip ('\r')) + + if len (words) < 4: + continue + + try: + size = int (words[0]) + hotx = int (words[1]) - 1 + hoty = int (words[2]) - 1 + filename = words[3] + if not os.path.isabs (filename): + filename = prefix + '/' + filename + except: + continue + + if len (words) > 4: + try: + duration = int (words[4]) + except: + continue + else: + duration = 0 + + frames.append ((size, hotx, hoty, filename, duration)) + + return frames + +if __name__ == '__main__': + sys.exit (main ()) |