summaryrefslogtreecommitdiff
path: root/src/cursors
diff options
context:
space:
mode:
authorРуслан Ижбулатов <lrn1986@gmail.com>2015-05-13 14:05:23 +0000
committerJakub Steiner <jimmac@gmail.com>2015-05-13 19:07:38 +0200
commit315db6bc0a67984715b582320621d443f4990f30 (patch)
treed7c1dd38a94f152bedaf6c179719dd90532fb7a4 /src/cursors
parent778e229776e3edc26f5db9743ca1b2da7cc4448b (diff)
downloadadwaita-icon-theme-315db6bc0a67984715b582320621d443f4990f30.tar.gz
Add anicursorgen.py
https://bugzilla.gnome.org/show_bug.cgi?id=749053
Diffstat (limited to 'src/cursors')
-rw-r--r--src/cursors/anicursorgen.py297
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 ())