mirror of
https://github.com/ful1e5/Bibata_Cursor.git
synced 2025-05-30 23:05:24 -04:00
buid files added
This commit is contained in:
parent
b6f715d3b4
commit
48af1fafa9
3 changed files with 1132 additions and 512 deletions
418
anicursorgen.py
Executable file
418
anicursorgen.py
Executable file
|
@ -0,0 +1,418 @@
|
|||
#!/usr/bin/python3
|
||||
# -*- 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
|
||||
import math
|
||||
from PIL import Image
|
||||
from PIL import ImageFilter
|
||||
|
||||
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('-s', '--add-shadows', action='store_true', dest='add_shadows', default=False,
|
||||
help='Generate shadows for cursors (disabled by default).')
|
||||
parser.add_argument('-n', '--no-shadows', action='store_false', dest='add_shadows', default=False,
|
||||
help='Do not generate shadows for cursors (put after --add-shadows to cancel its effect).')
|
||||
|
||||
shadows = parser.add_argument_group(
|
||||
title='Shadow generation', description='Only relevant when --add-shadows is given')
|
||||
|
||||
shadows.add_argument('-r', '--right-shift', metavar='%', type=float, default=9.375,
|
||||
help='Shift shadow right by this percentage of the canvas size (default is 9.375).')
|
||||
shadows.add_argument('-d', '--down-shift', metavar='%', type=float, default=3.125,
|
||||
help='Shift shadow down by this percentage of the canvas size (default is 3.125).')
|
||||
shadows.add_argument('-b', '--blur', metavar='%', type=float, default=3.125,
|
||||
help='Blur radius, in percentage of the canvas size (default is 3.125, set to 0 to disable blurring).')
|
||||
shadows.add_argument('-c', '--color', metavar='%', default='0x00000040',
|
||||
help='Shadow color in 0xRRGGBBAA form (default is 0x00000040).')
|
||||
|
||||
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()
|
||||
|
||||
try:
|
||||
if args.color[0] != '0' or args.color[1] not in ['x', 'X'] or len(args.color) != 10:
|
||||
raise ValueError
|
||||
args.color = (int(args.color[2:4], 16), int(args.color[4:6], 16), int(
|
||||
args.color[6:8], 16), int(args.color[8:10], 16))
|
||||
except:
|
||||
print("Can't parse the color '{}'".format(args.color), file=sys.stderr)
|
||||
parser.print_help()
|
||||
return 1
|
||||
|
||||
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)
|
||||
|
||||
input_config.close()
|
||||
output_file.close()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def make_cursor_from(inp, out, args):
|
||||
frames = parse_config_from(inp, args.prefix)
|
||||
|
||||
animated = frames_have_animation(frames)
|
||||
|
||||
if animated:
|
||||
result = make_ani(frames, out, args)
|
||||
else:
|
||||
buf = make_cur(frames, args)
|
||||
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, args, animated=False):
|
||||
buf = io.BytesIO()
|
||||
buf.write(p('<HHH', 0, 2, len(frames)))
|
||||
frame_offsets = []
|
||||
|
||||
def frame_size_cmp(f1, f2):
|
||||
if f1[0] < f2[0]:
|
||||
return -1
|
||||
elif f1[0] > f2[0]:
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
frames = sorted(frames, frame_size_cmp, reverse=True)
|
||||
|
||||
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)
|
||||
|
||||
frame_png = Image.open(frame[3])
|
||||
|
||||
if args.add_shadows:
|
||||
succeeded, shadowed = create_shadow(frame_png, args)
|
||||
if succeeded == 0:
|
||||
frame_png.close()
|
||||
frame_png = shadowed
|
||||
|
||||
# Windows 10 fails to read PNG-compressed cursors for some reason
|
||||
# and the information about storing PNG-compressed cursors is
|
||||
# sparse. This is why PNG compression is not used.
|
||||
# Previously this was conditional on cursor size (<= 48 to be uncompressed).
|
||||
compressed = False
|
||||
|
||||
# On the other hand, Windows 10 refuses to read very large
|
||||
# uncompressed animated cursor files, but does accept
|
||||
# PNG-compressed animated cursors for some reason. Go figure.
|
||||
if animated:
|
||||
compressed = True
|
||||
|
||||
if compressed:
|
||||
write_png(buf, frame, frame_png)
|
||||
else:
|
||||
write_cur(buf, frame, frame_png)
|
||||
|
||||
frame_png.close()
|
||||
|
||||
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
|
||||
|
||||
def frameset_size_cmp(f1, f2):
|
||||
if f1[0][0] < f2[0][0]:
|
||||
return -1
|
||||
elif f1[0][0] > f2[0][0]:
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
framesets = sorted(framesets, frameset_size_cmp, reverse=True)
|
||||
|
||||
return framesets
|
||||
|
||||
|
||||
def make_ani(frames, out, args):
|
||||
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, args, animated=True)
|
||||
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))).encode())
|
||||
|
||||
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, frame_png):
|
||||
frame_png.save(out, "png", optimize=True)
|
||||
|
||||
|
||||
def write_cur(out, frame, frame_png):
|
||||
pixels = frame_png.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(list(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(list(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).encode())
|
||||
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():
|
||||
line = line.decode()
|
||||
words = shlex.split(line.rstrip('\n').rstrip('\r'))
|
||||
print(words)
|
||||
if len(words) < 4:
|
||||
continue
|
||||
|
||||
try:
|
||||
size = int(words[0])
|
||||
# print(size)
|
||||
hotx = int(words[1]) - 1
|
||||
# print(hotx)
|
||||
hoty = int(words[2]) - 1
|
||||
# print(hoty)
|
||||
filename = words[3]
|
||||
# print(filename)
|
||||
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
|
||||
|
||||
|
||||
def create_shadow(orig, args):
|
||||
blur_px = orig.size[0] / 100.0 * args.blur
|
||||
right_px = int(orig.size[0] / 100.0 * args.right_shift)
|
||||
down_px = int(orig.size[1] / 100.0 * args.down_shift)
|
||||
|
||||
shadow = Image.new('RGBA', orig.size, (0, 0, 0, 0))
|
||||
shadowize(shadow, orig, args.color)
|
||||
shadow.load()
|
||||
|
||||
if args.blur > 0:
|
||||
crop = (int(math.floor(-blur_px)), int(math.floor(-blur_px)),
|
||||
orig.size[0] + int(math.ceil(blur_px)), orig.size[1] + int(math.ceil(blur_px)))
|
||||
right_px += int(math.floor(-blur_px))
|
||||
down_px += int(math.floor(-blur_px))
|
||||
shadow = shadow.crop(crop)
|
||||
flt = ImageFilter.GaussianBlur(blur_px)
|
||||
shadow = shadow.filter(flt)
|
||||
shadow.load()
|
||||
|
||||
shadowed = Image.new('RGBA', orig.size, (0, 0, 0, 0))
|
||||
shadowed.paste(shadow, (right_px, down_px))
|
||||
shadowed.crop((0, 0, orig.size[0], orig.size[1]))
|
||||
shadowed = Image.alpha_composite(shadowed, orig)
|
||||
|
||||
return 0, shadowed
|
||||
|
||||
|
||||
def shadowize(shadow, orig, color):
|
||||
o_pxs = orig.load()
|
||||
s_pxs = shadow.load()
|
||||
for y in range(orig.size[1]):
|
||||
for x in range(orig.size[0]):
|
||||
o_px = o_pxs[x, y]
|
||||
if o_px[3] > 0:
|
||||
s_pxs[x, y] = (color[0], color[1], color[2],
|
||||
int(color[3] * (o_px[3] / 255.0)))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
512
build.py
512
build.py
|
@ -1,512 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# SVGSlice
|
||||
#
|
||||
# Released under the GNU General Public License, version 2.
|
||||
# Email Lee Braiden of Digital Unleashed at lee.b@digitalunleashed.com
|
||||
# with any questions, suggestions, patches, or general uncertainties
|
||||
# regarding this software.
|
||||
#
|
||||
|
||||
usageMsg = """You need to add a layer called "slices", and draw rectangles on it to represent the areas that should be saved as slices. It helps when drawing these rectangles if you make them translucent.
|
||||
|
||||
If you name these slices using the "id" field of Inkscape's built-in XML editor, that name will be reflected in the slice filenames.
|
||||
|
||||
Please remember to HIDE the slices layer before exporting, so that the rectangles themselves are not drawn in the final image slices."""
|
||||
|
||||
# How it works:
|
||||
#
|
||||
# Basically, svgslice just parses an SVG file, looking for the tags that define
|
||||
# the slices should be, and saves them in a list of rectangles. Next, it generates
|
||||
# an XHTML file, passing that out stdout to Inkscape. This will be saved by inkscape
|
||||
# under the name chosen in the save dialog. Finally, it calls
|
||||
# inkscape again to render each rectangle as a slice.
|
||||
#
|
||||
# Currently, nothing fancy is done to layout the XHTML file in a similar way to the
|
||||
# original document, so the generated pages is essentially just a quick way to see
|
||||
# all of the slices in once place, and perhaps a starting point for more layout work.
|
||||
#
|
||||
|
||||
from optparse import OptionParser
|
||||
|
||||
optParser = OptionParser()
|
||||
optParser.add_option('-n','--name',dest='name',default='cursor',help='Specified name of theme')
|
||||
optParser.add_option('-i','--inherit',dest='inherit',default='Adwaita',help='Specified name of theme wan to inherit DEFAULT:Adwaita')
|
||||
optParser.add_option('-d','--debug',action='store_true',dest='debug',help='Enable extra debugging info.')
|
||||
optParser.add_option('-t','--test',action='store_true',dest='testing',help='Test mode: leave temporary files for examination.')
|
||||
optParser.add_option('-p','--sliceprefix',action='store',dest='sliceprefix',help='Specifies the prefix to use for individual slice filenames.')
|
||||
|
||||
from xml.sax import saxutils, make_parser, SAXParseException
|
||||
from xml.sax.handler import feature_namespaces
|
||||
import os, sys, tempfile, shutil ,glob ,errno
|
||||
|
||||
svgFilename = None
|
||||
|
||||
def dbg(msg):
|
||||
if options.debug:
|
||||
sys.stderr.write(msg)
|
||||
|
||||
def cleanup():
|
||||
if svgFilename != None and os.path.exists(svgFilename):
|
||||
os.unlink(svgFilename)
|
||||
|
||||
def fatalError(msg):
|
||||
sys.stderr.write(msg)
|
||||
cleanup()
|
||||
sys.exit(20)
|
||||
|
||||
def baseName(svgFilename):
|
||||
base=os.path.basename(svgFilename)
|
||||
base=os.path.splitext(base)[0]
|
||||
return base
|
||||
|
||||
def symlink(target, link_name):
|
||||
try:
|
||||
os.symlink(target, link_name)
|
||||
except OSError as e:
|
||||
if e.errno == errno.EEXIST:
|
||||
os.remove(link_name)
|
||||
os.symlink(target, link_name)
|
||||
else:
|
||||
raise e
|
||||
|
||||
class SVGRect:
|
||||
"""Manages a simple rectangular area, along with certain attributes such as a name"""
|
||||
def __init__(self, x1,y1,x2,y2, name=None):
|
||||
self.x1 = x1
|
||||
self.y1 = y1
|
||||
self.x2 = x2
|
||||
self.y2 = y2
|
||||
self.name = name
|
||||
dbg("New SVGRect: (%s)" % name)
|
||||
|
||||
def renderFromSVG(self, svgFName, sliceFName):
|
||||
for size in (24, 28, 32, 40, 48, 56, 64, 72, 80, 88, 96):
|
||||
os.system("mkdir -p build/%s/pngs/%sx%s" % (options.name,size, size))
|
||||
rc = os.system('inkscape --without-gui -w %s -h %s --export-id="%s" --export-png="build/%s/pngs/%sx%s/%s" "%s"' % (size, size, self.name, options.name, size, size, sliceFName, svgFName))
|
||||
if rc > 0:
|
||||
fatalError('ABORTING: Inkscape failed to render the slice.')
|
||||
|
||||
|
||||
class SVGHandler(saxutils.xmlreader.handler.ContentHandler):
|
||||
"""Base class for SVG parsers"""
|
||||
def __init__(self):
|
||||
self.pageBounds = SVGRect(0,0,0,0)
|
||||
|
||||
def isFloat(self, stringVal):
|
||||
try:
|
||||
return (float(stringVal), True)[1]
|
||||
except (ValueError, TypeError):
|
||||
return False
|
||||
|
||||
def parseCoordinates(self, val):
|
||||
"""Strips the units from a coordinate, and returns just the value."""
|
||||
if val.endswith('px'):
|
||||
val = float(val.rstrip('px'))
|
||||
elif val.endswith('pt'):
|
||||
val = float(val.rstrip('pt'))
|
||||
elif val.endswith('cm'):
|
||||
val = float(val.rstrip('cm'))
|
||||
elif val.endswith('mm'):
|
||||
val = float(val.rstrip('mm'))
|
||||
elif val.endswith('in'):
|
||||
val = float(val.rstrip('in'))
|
||||
elif val.endswith('%'):
|
||||
val = float(val.rstrip('%'))
|
||||
elif self.isFloat(val):
|
||||
val = float(val)
|
||||
else:
|
||||
fatalError("Coordinate value %s has unrecognised units. Only px,pt,cm,mm,and in units are currently supported." % val)
|
||||
return val
|
||||
|
||||
def startElement_svg(self, name, attrs):
|
||||
"""Callback hook which handles the start of an svg image"""
|
||||
dbg('startElement_svg called')
|
||||
width = attrs.get('width', None)
|
||||
height = attrs.get('height', None)
|
||||
self.pageBounds.x2 = self.parseCoordinates(width)
|
||||
self.pageBounds.y2 = self.parseCoordinates(height)
|
||||
|
||||
def endElement(self, name):
|
||||
"""General callback for the end of a tag"""
|
||||
dbg('Ending element "%s"' % name)
|
||||
|
||||
|
||||
class SVGLayerHandler(SVGHandler):
|
||||
"""Parses an SVG file, extracing slicing rectangles from a "slices" layer"""
|
||||
def __init__(self):
|
||||
SVGHandler.__init__(self)
|
||||
self.svg_rects = []
|
||||
self.layer_nests = 0
|
||||
|
||||
def inSlicesLayer(self):
|
||||
return (self.layer_nests >= 1)
|
||||
|
||||
def add(self, rect):
|
||||
"""Adds the given rect to the list of rectangles successfully parsed"""
|
||||
self.svg_rects.append(rect)
|
||||
|
||||
def startElement_layer(self, name, attrs):
|
||||
"""Callback hook for parsing layer elements
|
||||
|
||||
Checks to see if we're starting to parse a slices layer, and sets the appropriate flags. Otherwise, the layer will simply be ignored."""
|
||||
dbg('found layer: name="%s" id="%s"' % (name, attrs['id']))
|
||||
if attrs.get('inkscape:groupmode', None) == 'layer':
|
||||
if self.inSlicesLayer() or attrs['inkscape:label'] == 'slices':
|
||||
self.layer_nests += 1
|
||||
|
||||
def endElement_layer(self, name):
|
||||
"""Callback for leaving a layer in the SVG file
|
||||
|
||||
Just undoes any flags set previously."""
|
||||
dbg('leaving layer: name="%s"' % name)
|
||||
if self.inSlicesLayer():
|
||||
self.layer_nests -= 1
|
||||
|
||||
def startElement_rect(self, name, attrs):
|
||||
"""Callback for parsing an SVG rectangle
|
||||
|
||||
Checks if we're currently in a special "slices" layer using flags set by startElement_layer(). If we are, the current rectangle is considered to be a slice, and is added to the list of parsed
|
||||
rectangles. Otherwise, it will be ignored."""
|
||||
if self.inSlicesLayer():
|
||||
x1 = self.parseCoordinates(attrs['x'])
|
||||
y1 = self.parseCoordinates(attrs['y'])
|
||||
x2 = self.parseCoordinates(attrs['width']) + x1
|
||||
y2 = self.parseCoordinates(attrs['height']) + y1
|
||||
name = attrs['id']
|
||||
rect = SVGRect(x1,y1, x2,y2, name)
|
||||
self.add(rect)
|
||||
|
||||
def startElement(self, name, attrs):
|
||||
"""Generic hook for examining and/or parsing all SVG tags"""
|
||||
if options.debug:
|
||||
dbg('Beginning element "%s"' % name)
|
||||
if name == 'svg':
|
||||
self.startElement_svg(name, attrs)
|
||||
elif name == 'g':
|
||||
# inkscape layers are groups, I guess, hence 'g'
|
||||
self.startElement_layer(name, attrs)
|
||||
elif name == 'rect':
|
||||
self.startElement_rect(name, attrs)
|
||||
|
||||
def endElement(self, name):
|
||||
"""Generic hook called when the parser is leaving each SVG tag"""
|
||||
dbg('Ending element "%s"' % name)
|
||||
if name == 'g':
|
||||
self.endElement_layer(name)
|
||||
|
||||
def generateXHTMLPage(self):
|
||||
"""Generates an XHTML page for the SVG rectangles previously parsed."""
|
||||
write = sys.stdout.write
|
||||
write('<?xml version="1.0" encoding="UTF-8"?>\n')
|
||||
write('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">\n')
|
||||
write('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n')
|
||||
write(' <head>\n')
|
||||
write(' <title>Sample SVGSlice Output</title>\n')
|
||||
write(' </head>\n')
|
||||
write(' <body>\n')
|
||||
write(' <p>Sorry, SVGSlice\'s XHTML output is currently very basic. Hopefully, it will serve as a quick way to preview all generated slices in your browser, and perhaps as a starting point for further layout work. Feel free to write it and submit a patch to the author :)</p>\n')
|
||||
|
||||
write(' <p>')
|
||||
for rect in self.svg_rects:
|
||||
write(' <img src="%s" alt="%s (please add real alternative text for this image)" longdesc="Please add a full description of this image" />\n' % (sliceprefix + rect.name + '.png', rect.name))
|
||||
write(' </p>')
|
||||
|
||||
write('<p><a href="http://validator.w3.org/check?uri=referer"><img src="http://www.w3.org/Icons/valid-xhtml10" alt="Valid XHTML 1.0!" height="31" width="88" /></a></p>')
|
||||
|
||||
write(' </body>\n')
|
||||
write('</html>\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# parse command line into arguments and options
|
||||
(options, args) = optParser.parse_args()
|
||||
|
||||
if len(args) != 1:
|
||||
fatalError("\nCall me with the SVG as a parameter.\n\n")
|
||||
originalFilename = args[0]
|
||||
|
||||
svgFilename = originalFilename + '.svg'
|
||||
shutil.copyfile(originalFilename, svgFilename)
|
||||
|
||||
# setup program variables from command line (in other words, handle non-option args)
|
||||
basename = os.path.splitext(svgFilename)[0]
|
||||
|
||||
if options.sliceprefix:
|
||||
sliceprefix = options.sliceprefix
|
||||
else:
|
||||
sliceprefix = ''
|
||||
|
||||
# initialise results before actually attempting to parse the SVG file
|
||||
svgBounds = SVGRect(0,0,0,0)
|
||||
rectList = []
|
||||
|
||||
# Try to parse the svg file
|
||||
xmlParser = make_parser()
|
||||
xmlParser.setFeature(feature_namespaces, 0)
|
||||
|
||||
# setup XML Parser with an SVGLayerHandler class as a callback parser ####
|
||||
svgLayerHandler = SVGLayerHandler()
|
||||
xmlParser.setContentHandler(svgLayerHandler)
|
||||
try:
|
||||
xmlParser.parse(svgFilename)
|
||||
except SAXParseException as e:
|
||||
fatalError("Error parsing SVG file '%s': line %d,col %d: %s. If you're seeing this within inkscape, it probably indicates a bug that should be reported." % (svgFilename, e.getLineNumber(), e.getColumnNumber(), e.getMessage()))
|
||||
|
||||
# verify that the svg file actually contained some rectangles.
|
||||
if len(svgLayerHandler.svg_rects) == 0:
|
||||
fatalError("""No slices were found in this SVG file. Please refer to the documentation for guidance on how to use this SVGSlice. As a quick summary:""" + usageMsg)
|
||||
else:
|
||||
dbg("Parsing successful.")
|
||||
|
||||
#svgLayerHandler.generateXHTMLPage()
|
||||
|
||||
# loop through each slice rectangle, and render a PNG image for it
|
||||
for rect in svgLayerHandler.svg_rects:
|
||||
sliceFName = sliceprefix + rect.name + '.png'
|
||||
|
||||
dbg('Saving slice as: "%s"' % sliceFName)
|
||||
rect.renderFromSVG(svgFilename, sliceFName)
|
||||
|
||||
cleanup()
|
||||
|
||||
dbg('\n\nSlicing complete.\n')
|
||||
|
||||
"""Generate cursor files based on 96x96 size hotspots."""
|
||||
|
||||
files = (
|
||||
(45.5, 46.8, "X_cursor"),
|
||||
(50, 46, "bd_double_arrow"),
|
||||
(16, 84, "bottom_left_corner"),
|
||||
(84, 84, "bottom_right_corner"),
|
||||
(50, 72, "bottom_side"),
|
||||
(48, 72, "bottom_tee"),
|
||||
(14.5, 10.6, "circle"),
|
||||
(23.1, 74.1, "color-picker"),
|
||||
(18.8, 11.2, "copy"),
|
||||
(44, 44, "cross"),
|
||||
(48, 48, "crossed_circle"),
|
||||
(44, 44, "crosshair"),
|
||||
(45, 50, "dnd-ask"),
|
||||
(45, 50, "dnd-copy"),
|
||||
(45, 50, "dnd-link"),
|
||||
(45, 50, "dnd-move"),
|
||||
(45, 50, "dnd-none"),
|
||||
(46, 46, "dotbox"),
|
||||
(46, 46, "fd_double_arrow"),
|
||||
(48, 50, "grabbing"),
|
||||
(47.6, 28, "hand1"),
|
||||
(36, 20, "hand2"),
|
||||
(27, 15, "left_ptr"),
|
||||
(23.2, 13.2, "left_ptr_watch"),
|
||||
(24, 50, "left_side"),
|
||||
(28, 48, "left_tee"),
|
||||
(19, 11.4, "link"),
|
||||
(21, 67, "ll_angle"),
|
||||
(72, 67, "lr_angle"),
|
||||
(19, 11, "move"),
|
||||
(28.6, 82.6, "pencil"),
|
||||
(48, 48, "plus"),
|
||||
(46, 80, "question_arrow"),
|
||||
(65, 16, "right_ptr"),
|
||||
(76, 50, "right_side"),
|
||||
(72, 48, "right_tee"),
|
||||
(50, 72, "sb_down_arrow"),
|
||||
(47, 46, "sb_h_double_arrow"),
|
||||
(24, 45.6, "sb_left_arrow"),
|
||||
(75, 45, "sb_right_arrow"),
|
||||
(51, 17, "sb_up_arrow"),
|
||||
(45, 45, "sb_v_double_arrow"),
|
||||
(50, 46, "tcross"),
|
||||
(21, 21, "top_left_corner"),
|
||||
(72, 21, "top_right_corner"),
|
||||
(50, 20, "top_side"),
|
||||
(48, 24, "top_tee"),
|
||||
(22, 25, "ul_angle"),
|
||||
(72, 25, "ur_angle"),
|
||||
(48, 46, "watch"),
|
||||
(50, 50, "xterm"),
|
||||
)
|
||||
|
||||
"""Genrate CursorList"""
|
||||
|
||||
content="""
|
||||
00000000000000020006000e7e9ffc3f progress
|
||||
00008160000006810000408080010102 size_ver
|
||||
03b6e0fcb3499374a867c041f52298f0 circle
|
||||
08e8e1c95fe2fc01f976f1e063a24ccd progress
|
||||
3ecb610c1bf2410f44200f48c40d3599 progress
|
||||
5c6cd98b3f3ebcb1f9c7f1c204630408 help
|
||||
9d800788f1b08800ae810202380a0822 pointer
|
||||
640fb0e74195791501fd1ed57b41487f alias
|
||||
1081e37283d90000800003c07f3ef6bf copy
|
||||
3085a0e285430894940527032f8b26df alias
|
||||
4498f0e0c1937ffe01fd06f973665830 dnd-move
|
||||
6407b0e94181790501fd1e167b474872 copy
|
||||
9081237383d90e509aa00f00170e968f dnd-move
|
||||
a2a266d0498c3104214a47bd64ab0fc8 alias
|
||||
b66166c04f8c3109214a4fbd64a50fc8 copy
|
||||
d9ce0ab605698f320427677b458ad60b help
|
||||
e29285e634086352946a0e7090d73106 pointer
|
||||
fcf21c00b30f7e3f83fe0dfd12e71cff dnd-move
|
||||
alias copy
|
||||
all-scroll fleur
|
||||
bottom_left_corner size_bdiag
|
||||
bottom_right_corner size_fdiag
|
||||
cell crosshair
|
||||
center_ptr default
|
||||
circle not-allowed
|
||||
closedhand dnd-move
|
||||
col-resize size_hor
|
||||
color-picker crosshair
|
||||
context-menu default
|
||||
copy dnd-move
|
||||
cross crosshair
|
||||
tcross crosshair
|
||||
crossed_circle not-allowed
|
||||
dnd-copy copy
|
||||
dnd-none dnd-move
|
||||
dnd-no-drop not-allowed
|
||||
draft pencil
|
||||
e-resize size_hor
|
||||
forbidden no-drop
|
||||
h_double_arrow size_hor
|
||||
half-busy progress
|
||||
hand1 pointer
|
||||
hand2 pointer
|
||||
help default
|
||||
ibeam text
|
||||
left_ptr default
|
||||
left_ptr_help help
|
||||
left_ptr_watch progress
|
||||
left_side left-arrow
|
||||
link alias
|
||||
move dnd-move
|
||||
n-resize size-ver
|
||||
no-drop not-allowed
|
||||
plus cell
|
||||
pointing_hand pointer
|
||||
question_arrow help
|
||||
right_ptr default
|
||||
right_side right-arrow
|
||||
row-resize size_ver
|
||||
s-resize size_ver
|
||||
sb_h_double_arrow size_hor
|
||||
sb_v_double_arrow size_ver
|
||||
size_all fleur
|
||||
split_h col-resize
|
||||
split_v row-resize
|
||||
top_left_corner size_fdiag
|
||||
top_right_corner size_bdiag
|
||||
top_side up-arrow
|
||||
v_double_arrow size_ver
|
||||
vertical-text text
|
||||
w-resize size_hor
|
||||
watch wait
|
||||
whats_this help
|
||||
xterm text
|
||||
dnd-move default
|
||||
down-arrow default
|
||||
crosshair default
|
||||
fleur default
|
||||
left-arrow default
|
||||
not-allowed default
|
||||
openhand default
|
||||
pencil default
|
||||
pirate default
|
||||
pointer default
|
||||
progress default
|
||||
right-arrow default
|
||||
size-bdiag default
|
||||
size-fdiag default
|
||||
size-hor default
|
||||
size-ver default
|
||||
text default
|
||||
up-arrow default
|
||||
wait default
|
||||
x-cursor default
|
||||
wayland-cursor default
|
||||
zoom-in default
|
||||
zoom-out default
|
||||
ne-resize size_bdiag
|
||||
sw-resize size_bdiag
|
||||
nw-resize size_fdiag
|
||||
se-resize size_fdiag
|
||||
"""
|
||||
|
||||
#create build/config directory
|
||||
os.system("mkdir -p build/config")
|
||||
|
||||
#create & write hotspot
|
||||
for file_ in files:
|
||||
with open("build/config/%s.cursor" % file_[2], "w") as f:
|
||||
for size in (24, 28, 32, 40, 48, 56, 64, 72, 80, 88, 96):
|
||||
f.write("{size} {xhot} {yhot} {size}x{size}/{file_}.png\n".format(
|
||||
size = size,
|
||||
xhot = round(file_[0] / 96 * size),
|
||||
yhot = round(file_[1] / 96 * size),
|
||||
file_= file_[2]
|
||||
))
|
||||
|
||||
#create CursorList
|
||||
fileCursorList=open("build/cursorList","w")
|
||||
fileCursorList.write(content)
|
||||
fileCursorList.close()
|
||||
|
||||
dbg('\n\nConfig successfully generated\n')
|
||||
|
||||
os.system("mkdir -p %s/cursors"%(options.name))
|
||||
|
||||
for filepath in glob.iglob('build/config/*.cursor'):
|
||||
base=os.path.basename(filepath)
|
||||
base=os.path.splitext(base)[0]
|
||||
print('Genrating %s'%(base))
|
||||
rc = os.system('xcursorgen -p build/%s/pngs "%s" "%s/cursors/%s"' % (options.name, filepath,options.name, base))
|
||||
if rc > 0:
|
||||
fatalError('\n\nABORTING: Xcursorgen failed to generate the cursor.\n')
|
||||
fatalError('\nFAIL: %s %s \n'%(filepath, rc))
|
||||
|
||||
try:
|
||||
cursor_config=open('build/cursorList', 'r')
|
||||
#change wrok dir for symblink
|
||||
os.chdir('%s/cursors'%(options.name))
|
||||
while cursor_config.readline():
|
||||
line = cursor_config.readline()
|
||||
line=line.strip('\n')
|
||||
# print(line)
|
||||
fromlink, tolink =line.split(' ', 1)
|
||||
|
||||
if os.path.exists(fromlink):
|
||||
continue
|
||||
|
||||
print('Linking %s -> %s'%(fromlink, tolink))
|
||||
symlink(tolink, fromlink)
|
||||
|
||||
cursor_config.close()
|
||||
except FileNotFoundError:
|
||||
fatalError('\n\nFAIL: cursorList does not exist\n')
|
||||
print('FAIL: cursorList does not exist')
|
||||
|
||||
dbg('\n\nCursor build complete.\n')
|
||||
|
||||
#create .theme files
|
||||
#go to parent directory here is Options.name
|
||||
os.chdir('..')
|
||||
#cursor.theme content
|
||||
print('\n\nCreating indexing...')
|
||||
fileCursorTheme = open('cursor.theme','w')
|
||||
fileCursorTheme.write('[Icon Theme]\n')
|
||||
fileCursorTheme.write('Name=%s\n'%(options.name))
|
||||
fileCursorTheme.write('Inherits=%s\n'%(options.inherit))
|
||||
fileCursorTheme.close()
|
||||
#index.theme content
|
||||
fileIndexTheme = open('index.theme','w')
|
||||
fileIndexTheme.write('[Icon Theme]\n')
|
||||
fileIndexTheme.write('Name=%s\n'%(options.name))
|
||||
|
||||
for lang in ('ar','bg','ca','cs','da','de','dz','el','en_CA','en_GB','eo','es','et','eu','fi','fr','ga','gl','gu','he','hr','hu','id','it','ja','km','ko','lt','mk','ms','nb','ne','nl','pa','pl','pt_BR','pt','ro','ru','rw','sk','sr_Latn','sr','sv','tr','tt','uk','vi','xh','yi','zh_CN','zh_TW'):
|
||||
fileIndexTheme.write('Name[%s]=%s\n'%(lang, options.name))
|
||||
fileIndexTheme.write('Comment=%s cursor theme'%(options.name))
|
||||
fileIndexTheme.close()
|
||||
print('\nCreating indexing... DONE')
|
||||
dbg('\n\nCursor indexing complete.\n')
|
714
render-cursors.py
Executable file
714
render-cursors.py
Executable file
|
@ -0,0 +1,714 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# SVGSlice
|
||||
#
|
||||
# Released under the GNU General Public License, version 2.
|
||||
# Email Lee Braiden of Digital Unleashed at lee.b@digitalunleashed.com
|
||||
# with any questions, suggestions, patches, or general uncertainties
|
||||
# regarding this software.
|
||||
#
|
||||
|
||||
usageMsg = """You need to add a layer called "slices", and draw rectangles on it to represent the areas that should be saved as slices. It helps when drawing these rectangles if you make them translucent.
|
||||
|
||||
If you name these slices using the "id" field of Inkscape's built-in XML editor, that name will be reflected in the slice filenames.
|
||||
|
||||
Please remember to HIDE the slices layer before exporting, so that the rectangles themselves are not drawn in the final image slices."""
|
||||
|
||||
# How it works:
|
||||
#
|
||||
# Basically, svgslice just parses an SVG file, looking for the tags that define
|
||||
# the slices should be, and saves them in a list of rectangles. Next, it generates
|
||||
# an XHTML file, passing that out stdout to Inkscape. This will be saved by inkscape
|
||||
# under the name chosen in the save dialog. Finally, it calls
|
||||
# inkscape again to render each rectangle as a slice.
|
||||
#
|
||||
# Currently, nothing fancy is done to layout the XHTML file in a similar way to the
|
||||
# original document, so the generated pages is essentially just a quick way to see
|
||||
# all of the slices in once place, and perhaps a starting point for more layout work.
|
||||
#
|
||||
|
||||
from optparse import OptionParser
|
||||
|
||||
optParser = OptionParser()
|
||||
optParser.add_option('-d','--debug',action='store_true',dest='debug',help='Enable extra debugging info.')
|
||||
optParser.add_option('-t','--test',action='store_true',dest='testing',help='Test mode: leave temporary files for examination.')
|
||||
optParser.add_option('-p','--sliceprefix',action='store',dest='sliceprefix',help='Specifies the prefix to use for individual slice filenames.')
|
||||
optParser.add_option('-r','--remove-shadows',action='store_true',dest='remove_shadows',help='Remove shadows the cursors have.')
|
||||
optParser.add_option('-o','--hotspots',action='store_true',dest='hotspots',help='Produce hotspot images and hotspot datafiles.')
|
||||
optParser.add_option('-s','--scales',action='store_true',dest='scales',help='Produce 125% (Large) and 150% (Extra Large) scaled versions of each image as well.')
|
||||
optParser.add_option('-m','--min-canvas-size',action='store',type='int',dest='min_canvas_size',default=-1, help='Cursor canvas must be at least this big (defaults to -1).')
|
||||
optParser.add_option('-f','--fps',action='store',type='int',dest='fps',default=60,help='Assume that all animated cursors have this FPS (defaults to 60).')
|
||||
optParser.add_option('-a','--anicursorgen',action='store_true',dest='anicur',default=False,help='Assume that anicursorgen will be used to assemble cursors (xcursorgen is assumed by default).')
|
||||
optParser.add_option('-c','--corner-align',action='store_true',dest='align_corner',default=False,help='Align cursors to the top-left corner (by default they are centered).')
|
||||
optParser.add_option('-i','--invert',action='store_true',dest='invert',default=False,help='Invert colors (disabled by default).')
|
||||
optParser.add_option('-n','--number-of-renderers',action='store',type='int',dest='number_of_renderers',default=1, help='Number of renderer instances run in parallel. Defaults to 1. Set to 0 for autodetection.')
|
||||
|
||||
from xml.sax import saxutils, make_parser, SAXParseException, handler, xmlreader
|
||||
from xml.sax.handler import feature_namespaces
|
||||
import os, sys, tempfile, shutil, subprocess
|
||||
import re
|
||||
from threading import Thread
|
||||
from PIL import Image
|
||||
import multiprocessing
|
||||
import io
|
||||
|
||||
svgFilename = None
|
||||
hotsvgFilename = None
|
||||
sizes = [24,32,48,64,96]
|
||||
scale_pairs = [(1.25, 's1'), (1.50, 's2')]
|
||||
mode_shadows = ['shadows']
|
||||
mode_hotspots = ['hotspots']
|
||||
mode_slices = ['slices']
|
||||
mode_invert = ['invert']
|
||||
|
||||
def natural_sort(l):
|
||||
convert = lambda text: int(text) if text.isdigit() else text.lower()
|
||||
alphanum_key = lambda key: [ convert(c) for c in re.split(b'([0-9]+)', key) ]
|
||||
return sorted(l, key = alphanum_key)
|
||||
|
||||
def dbg(msg):
|
||||
if options.debug:
|
||||
sys.stderr.write(msg+'\n')
|
||||
|
||||
def cleanup():
|
||||
global inkscape_instances
|
||||
for inkscape, inkscape_stderr, inkscape_stderr_thread in inkscape_instances:
|
||||
inkscape.communicate (('quit\n').encode())
|
||||
del inkscape
|
||||
del inkscape_stderr_thread
|
||||
del inkscape_stderr
|
||||
del inkscape_instances
|
||||
if svgFilename != None and os.path.exists(svgFilename):
|
||||
os.unlink(svgFilename)
|
||||
if hotsvgFilename != None and os.path.exists(hotsvgFilename):
|
||||
os.unlink(hotsvgFilename)
|
||||
|
||||
def fatalError(msg):
|
||||
sys.stderr.write(msg)
|
||||
cleanup()
|
||||
sys.exit(20)
|
||||
|
||||
def stderr_reader(inkscape, inkscape_stderr):
|
||||
try:
|
||||
while True:
|
||||
line = inkscape_stderr.readline()
|
||||
if line and len (line.rstrip ('\n').rstrip ('\r')) > 0:
|
||||
fatalError('ABORTING: Inkscape failed to render a slice: {}'.format (line))
|
||||
elif line:
|
||||
print (("STDERR> {}".format (line)))
|
||||
else:
|
||||
raise EOFError
|
||||
except EOFError as error:
|
||||
print(error)
|
||||
|
||||
def find_hotspot (hotfile):
|
||||
img = Image.open(hotfile)
|
||||
pixels = img.load()
|
||||
reddest = [-1, -1, -999999]
|
||||
for y in range(img.size[1]):
|
||||
for x in range(img.size[0]):
|
||||
redness = pixels[x,y][0] - pixels[x,y][1] - pixels[x,y][2]
|
||||
if redness > reddest[2]:
|
||||
reddest = [x, y, redness]
|
||||
return (reddest[0] + 1, reddest[1] + 1)
|
||||
|
||||
def cropalign (size, filename):
|
||||
img = Image.open (filename)
|
||||
content_dimensions = img.getbbox ()
|
||||
if content_dimensions is None:
|
||||
content_dimensions = (0, 0, img.size[0], img.size[1])
|
||||
hcropped = content_dimensions[2] - content_dimensions[0]
|
||||
vcropped = content_dimensions[3] - content_dimensions[1]
|
||||
if hcropped > size or vcropped > size:
|
||||
if hcropped > size:
|
||||
left = (hcropped - size) / 2
|
||||
right = (hcropped - size) - left
|
||||
else:
|
||||
left = 0
|
||||
right = 0
|
||||
if vcropped > size:
|
||||
top = (vcropped - size) / 2
|
||||
bottom = (vcropped - size) - top
|
||||
else:
|
||||
top = 0
|
||||
bottom = 0
|
||||
content_dimensions = (content_dimensions[0] + left, content_dimensions[1] + top, content_dimensions[2] - right, content_dimensions[3] - bottom)
|
||||
sys.stderr.write ("WARNING: {} is too big to be cleanly cropped to {} ({}x{} at best), cropping to {}x{}!\n".format (filename, size, hcropped, vcropped, content_dimensions[2] - content_dimensions[0], content_dimensions[3] - content_dimensions[1]))
|
||||
sys.stderr.flush ()
|
||||
if options.testing:
|
||||
img.save (filename + ".orig.png", "png")
|
||||
dbg("{} content is {} {} {} {}".format (filename, content_dimensions[0], content_dimensions[1], content_dimensions[2], content_dimensions[3]))
|
||||
cropimg = img.crop ((content_dimensions[0], content_dimensions[1], content_dimensions[2], content_dimensions[3]))
|
||||
pixels = cropimg.load ()
|
||||
if options.testing:
|
||||
cropimg.save (filename + ".crop.png", "png")
|
||||
if options.align_corner:
|
||||
expimg = cropimg.crop ((0, 0, size, size))
|
||||
result = (content_dimensions[0], content_dimensions[1])
|
||||
else:
|
||||
hslack = size - cropimg.size[0]
|
||||
vslack = size - cropimg.size[1]
|
||||
left = hslack / 2
|
||||
top = vslack / 2
|
||||
expimg = cropimg.crop ((-left, -top, size - left, size - top))
|
||||
result = (content_dimensions[0] - left, content_dimensions[1] - top)
|
||||
pixels = expimg.load ()
|
||||
if options.invert:
|
||||
negative (expimg)
|
||||
expimg.save (filename, "png")
|
||||
del cropimg
|
||||
del img
|
||||
return result
|
||||
|
||||
def cropalign_hotspot (new_base, size, filename):
|
||||
if new_base is None:
|
||||
return
|
||||
img = Image.open (filename)
|
||||
expimg = img.crop ((new_base[0], new_base[1], new_base[0] + size, new_base[1] + size))
|
||||
pixels = expimg.load ()
|
||||
expimg.save (filename, "png")
|
||||
del img
|
||||
|
||||
def negative (img):
|
||||
pixels = img.load ()
|
||||
for y in range (0, img.size[1]):
|
||||
for x in range (0, img.size[0]):
|
||||
r, g, b, a = pixels[x,y]
|
||||
pixels[x,y] = (255 - r, 255 - g, 255 - b, a)
|
||||
|
||||
class SVGRect:
|
||||
"""Manages a simple rectangular area, along with certain attributes such as a name"""
|
||||
def __init__(self, x1,y1,x2,y2, name=None):
|
||||
self.x1 = x1
|
||||
self.y1 = y1
|
||||
self.x2 = x2
|
||||
self.y2 = y2
|
||||
self.name = name
|
||||
dbg("New SVGRect: (%s)" % name)
|
||||
|
||||
def renderFromSVG(self, svgFName, slicename, skipped, roundrobin, hotsvgFName):
|
||||
|
||||
def do_res (size, output, svgFName, skipped, roundrobin):
|
||||
global inkscape_instances
|
||||
if os.path.exists (output):
|
||||
skipped[output] = True
|
||||
return
|
||||
command = '-w {size} -h {size} --export-id="{export_id}" --export-png="{export_png}" {svg}\n'.format (size=size, export_id=self.name, export_png=output, svg=svgFName)
|
||||
dbg("Command: {}".format (command))
|
||||
inkscape_instances[roundrobin[0]][0].stdin.write ((command).encode())
|
||||
|
||||
pngsliceFName = slicename + '.png'
|
||||
hotsliceFName = slicename + '.hotspot.png'
|
||||
|
||||
dbg('Saving slice as: "%s"' % pngsliceFName)
|
||||
for i, size in enumerate (sizes):
|
||||
subdir = 'bitmaps/{}x{}'.format (size, size)
|
||||
if not os.path.exists (subdir):
|
||||
os.makedirs (subdir)
|
||||
relslice = '{}/{}'.format (subdir, pngsliceFName)
|
||||
do_res (size, relslice, svgFName, skipped, roundrobin)
|
||||
if options.hotspots:
|
||||
hotrelslice = '{}/{}'.format (subdir, hotsliceFName)
|
||||
do_res (size, hotrelslice, hotsvgFName, skipped, roundrobin)
|
||||
for scale in scale_pairs:
|
||||
subdir = 'bitmaps/{}x{}_{}'.format (size, size, scale[1])
|
||||
relslice = '{}/{}'.format (subdir, pngsliceFName)
|
||||
if not os.path.exists (subdir):
|
||||
os.makedirs (subdir)
|
||||
scaled_size = int (size * scale[0])
|
||||
do_res (scaled_size, relslice, svgFName, skipped, roundrobin)
|
||||
if options.hotspots:
|
||||
hotrelslice = '{}/{}'.format (subdir, hotsliceFName)
|
||||
do_res (scaled_size, hotrelslice, hotsvgFName, skipped, roundrobin)
|
||||
# This is not inside do_res() because we want each instance to work all scales in case scales are enabled,
|
||||
# otherwise instances that get mostly smallscale renders will finish up way before the others
|
||||
roundrobin[0] += 1
|
||||
if roundrobin[0] >= options.number_of_renderers:
|
||||
roundrobin[0] = 0
|
||||
|
||||
def get_next_size (index, current_size):
|
||||
if index % 2 == 0:
|
||||
# 24->32, 48->64, 96->128, 192->256
|
||||
return (current_size * 4) / 3
|
||||
else:
|
||||
# 32->48, 64->96, 128->192, 256->384
|
||||
return (current_size * 3) / 2
|
||||
|
||||
def get_csize (index, current_size):
|
||||
size = current_size
|
||||
if len (scale_pairs) > 0:
|
||||
size = get_next_size (index, size)
|
||||
return max (options.min_canvas_size, size)
|
||||
|
||||
def postprocess_slice (slicename, skipped):
|
||||
pngsliceFName = slicename + '.png'
|
||||
hotsliceFName = slicename + '.hotspot.png'
|
||||
|
||||
for i, size in enumerate (sizes):
|
||||
subdir = 'bitmaps/{}x{}'.format (size, size)
|
||||
relslice = '{}/{}'.format (subdir, pngsliceFName)
|
||||
csize = get_csize (i, size)
|
||||
if relslice not in skipped:
|
||||
# new_base = cropalign (csize, relslice)
|
||||
if options.hotspots:
|
||||
hotrelslice = '{}/{}'.format (subdir, hotsliceFName)
|
||||
# cropalign_hotspot (new_base, csize, hotrelslice)
|
||||
for scale in scale_pairs:
|
||||
subdir = 'bitmaps/{}x{}_{}'.format (size, size, scale[1])
|
||||
relslice = '{}/{}'.format (subdir, pngsliceFName)
|
||||
if relslice not in skipped:
|
||||
# new_base = cropalign (csize, relslice)
|
||||
if options.hotspots:
|
||||
hotrelslice = '{}/{}'.format (subdir, hotsliceFName)
|
||||
# cropalign_hotspot (new_base, csize, hotrelslice)
|
||||
|
||||
def write_xcur(slicename):
|
||||
pngsliceFName = slicename + '.png'
|
||||
hotsliceFName = slicename + '.hotspot.png'
|
||||
|
||||
framenum = -1
|
||||
if slicename[-5:].startswith ('_'):
|
||||
try:
|
||||
framenum = int (slicename[-4:])
|
||||
slicename = slicename[:-5]
|
||||
except:
|
||||
pass
|
||||
|
||||
# This relies on the fact that frame 1 is the first frame of an animation in the rect list
|
||||
# If that is not so, the *icongen input file will end up missing some of the lines
|
||||
if framenum == -1 or framenum == 1:
|
||||
mode = 'wb'
|
||||
else:
|
||||
mode = 'ab'
|
||||
if framenum == -1:
|
||||
fps_field = ''
|
||||
else:
|
||||
if options.anicur:
|
||||
# For anicursorgen use jiffies
|
||||
fps_field = ' {}'.format (int (60.0 / options.fps))
|
||||
else:
|
||||
# For xcursorgen use milliseconds
|
||||
fps_field = ' {}'.format (int (1000.0 / options.fps))
|
||||
xcur = {}
|
||||
xcur['s0'] = open ('bitmaps/{}.in'.format (slicename), mode)
|
||||
if len (scale_pairs) > 0:
|
||||
xcur['s1'] = open ('bitmaps/{}.s1.in'.format (slicename), mode)
|
||||
xcur['s2'] = open ('bitmaps/{}.s2.in'.format (slicename), mode)
|
||||
for i, size in enumerate (sizes):
|
||||
subdir = 'bitmaps/{}x{}'.format (size, size)
|
||||
relslice = '{}/{}'.format (subdir, pngsliceFName)
|
||||
hotrelslice = '{}/{}'.format (subdir, hotsliceFName)
|
||||
hot = find_hotspot (hotrelslice)
|
||||
csize = get_csize (i, size)
|
||||
xcur['s0'].write ("{csize} {hotx} {hoty} {filename}{fps_field}\n".format (csize=csize, hotx=hot[0], hoty=hot[1], filename='{}x{}/{}'.format (size, size, pngsliceFName), fps_field=fps_field).encode())
|
||||
for scale in scale_pairs:
|
||||
subdir = 'bitmaps/{}x{}_{}'.format (size, size, scale[1])
|
||||
relslice = '{}/{}'.format (subdir, pngsliceFName)
|
||||
scaled_size = int (size * scale[0])
|
||||
hotrelslice = '{}/{}'.format (subdir, hotsliceFName)
|
||||
hot = find_hotspot (hotrelslice)
|
||||
xcur[scale[1]].write ("{csize} {hotx} {hoty} {filename}{fps_field}\n".format (csize=csize, hotx=hot[0], hoty=hot[1], filename='{}x{}_{}/{}'.format (size, size, scale[1], pngsliceFName), fps_field=fps_field))
|
||||
xcur['s0'].close ()
|
||||
if len (scale_pairs) > 0:
|
||||
xcur['s1'].close ()
|
||||
xcur['s2'].close ()
|
||||
|
||||
def sort_file(filename):
|
||||
with open (filename, 'rb') as src:
|
||||
contents = src.readlines ()
|
||||
with open (filename, 'wb') as dst:
|
||||
for line in natural_sort (contents):
|
||||
dst.write (line)
|
||||
|
||||
def sort_xcur(slicename, passed):
|
||||
pngsliceFName = slicename + '.png'
|
||||
|
||||
framenum = -1
|
||||
if slicename[-5:].startswith ('_'):
|
||||
try:
|
||||
framenum = int (slicename[-4:])
|
||||
slicename = slicename[:-5]
|
||||
except:
|
||||
pass
|
||||
if slicename in passed:
|
||||
return
|
||||
passed[slicename] = True
|
||||
|
||||
sort_file ('bitmaps/{}.in'.format (slicename))
|
||||
if len (scale_pairs) > 0:
|
||||
sort_file ('bitmaps/{}.s1.in'.format (slicename))
|
||||
sort_file ('bitmaps/{}.s2.in'.format (slicename))
|
||||
|
||||
def delete_hotspot(slicename):
|
||||
hotsliceFName = slicename + '.hotspot.png'
|
||||
|
||||
for i, size in enumerate (sizes):
|
||||
subdir = 'bitmaps/{}x{}'.format (size, size)
|
||||
hotrelslice = '{}/{}'.format (subdir, hotsliceFName)
|
||||
if os.path.exists (hotrelslice):
|
||||
os.unlink (hotrelslice)
|
||||
for scale in scale_pairs:
|
||||
subdir = 'bitmaps/{}x{}_{}'.format (size, size, scale[1])
|
||||
hotrelslice = '{}/{}'.format (subdir, hotsliceFName)
|
||||
if os.path.exists (hotrelslice):
|
||||
os.unlink (hotrelslice)
|
||||
|
||||
class SVGHandler(handler.ContentHandler):
|
||||
"""Base class for SVG parsers"""
|
||||
def __init__(self):
|
||||
self.pageBounds = SVGRect(0,0,0,0)
|
||||
|
||||
def isFloat(self, stringVal):
|
||||
try:
|
||||
return (float(stringVal), True)[1]
|
||||
except (ValueError, TypeError) as e:
|
||||
return False
|
||||
|
||||
def parseCoordinates(self, val):
|
||||
"""Strips the units from a coordinate, and returns just the value."""
|
||||
if val.endswith('px'):
|
||||
val = float(val.rstrip('px'))
|
||||
elif val.endswith('pt'):
|
||||
val = float(val.rstrip('pt'))
|
||||
elif val.endswith('cm'):
|
||||
val = float(val.rstrip('cm'))
|
||||
elif val.endswith('mm'):
|
||||
val = float(val.rstrip('mm'))
|
||||
elif val.endswith('in'):
|
||||
val = float(val.rstrip('in'))
|
||||
elif val.endswith('%'):
|
||||
val = float(val.rstrip('%'))
|
||||
elif self.isFloat(val):
|
||||
val = float(val)
|
||||
else:
|
||||
fatalError("Coordinate value %s has unrecognised units. Only px,pt,cm,mm,and in units are currently supported." % val)
|
||||
return val
|
||||
|
||||
def startElement_svg(self, name, attrs):
|
||||
"""Callback hook which handles the start of an svg image"""
|
||||
dbg('startElement_svg called')
|
||||
width = attrs.get('width', None)
|
||||
height = attrs.get('height', None)
|
||||
self.pageBounds.x2 = self.parseCoordinates(width)
|
||||
self.pageBounds.y2 = self.parseCoordinates(height)
|
||||
|
||||
def endElement(self, name):
|
||||
"""General callback for the end of a tag"""
|
||||
dbg('Ending element "%s"' % name)
|
||||
|
||||
|
||||
class SVGLayerHandler(SVGHandler):
|
||||
"""Parses an SVG file, extracing slicing rectangles from a "slices" layer"""
|
||||
def __init__(self):
|
||||
SVGHandler.__init__(self)
|
||||
self.svg_rects = []
|
||||
self.layer_nests = 0
|
||||
|
||||
def inSlicesLayer(self):
|
||||
return (self.layer_nests >= 1)
|
||||
|
||||
def add(self, rect):
|
||||
"""Adds the given rect to the list of rectangles successfully parsed"""
|
||||
self.svg_rects.append(rect)
|
||||
|
||||
def startElement_layer(self, name, attrs):
|
||||
"""Callback hook for parsing layer elements
|
||||
|
||||
Checks to see if we're starting to parse a slices layer, and sets the appropriate flags. Otherwise, the layer will simply be ignored."""
|
||||
dbg('found layer: name="%s" id="%s"' % (name, attrs['id']))
|
||||
if attrs.get('inkscape:groupmode', None) == 'layer':
|
||||
if self.inSlicesLayer() or attrs['inkscape:label'] == 'slices':
|
||||
self.layer_nests += 1
|
||||
|
||||
def endElement_layer(self, name):
|
||||
"""Callback for leaving a layer in the SVG file
|
||||
|
||||
Just undoes any flags set previously."""
|
||||
dbg('leaving layer: name="%s"' % name)
|
||||
if self.inSlicesLayer():
|
||||
self.layer_nests -= 1
|
||||
|
||||
def startElement_rect(self, name, attrs):
|
||||
"""Callback for parsing an SVG rectangle
|
||||
|
||||
Checks if we're currently in a special "slices" layer using flags set by startElement_layer(). If we are, the current rectangle is considered to be a slice, and is added to the list of parsed
|
||||
rectangles. Otherwise, it will be ignored."""
|
||||
if self.inSlicesLayer():
|
||||
x1 = self.parseCoordinates(attrs['x'])
|
||||
y1 = self.parseCoordinates(attrs['y'])
|
||||
x2 = self.parseCoordinates(attrs['width']) + x1
|
||||
y2 = self.parseCoordinates(attrs['height']) + y1
|
||||
name = attrs['id']
|
||||
rect = SVGRect(x1,y1, x2,y2, name)
|
||||
self.add(rect)
|
||||
|
||||
def startElement(self, name, attrs):
|
||||
"""Generic hook for examining and/or parsing all SVG tags"""
|
||||
dbg('Beginning element "%s"' % name)
|
||||
if name == 'svg':
|
||||
self.startElement_svg(name, attrs)
|
||||
elif name == 'g':
|
||||
# inkscape layers are groups, I guess, hence 'g'
|
||||
self.startElement_layer(name, attrs)
|
||||
elif name == 'rect':
|
||||
self.startElement_rect(name, attrs)
|
||||
|
||||
def endElement(self, name):
|
||||
"""Generic hook called when the parser is leaving each SVG tag"""
|
||||
dbg('Ending element "%s"' % name)
|
||||
if name == 'g':
|
||||
self.endElement_layer(name)
|
||||
|
||||
def generateXHTMLPage(self):
|
||||
"""Generates an XHTML page for the SVG rectangles previously parsed."""
|
||||
write = sys.stdout.write
|
||||
write('<?xml version="1.0" encoding="UTF-8"?>\n')
|
||||
write('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">\n')
|
||||
write('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n')
|
||||
write(' <head>\n')
|
||||
write(' <title>Sample SVGSlice Output</title>\n')
|
||||
write(' </head>\n')
|
||||
write(' <body>\n')
|
||||
write(' <p>Sorry, SVGSlice\'s XHTML output is currently very basic. Hopefully, it will serve as a quick way to preview all generated slices in your browser, and perhaps as a starting point for further layout work. Feel free to write it and submit a patch to the author :)</p>\n')
|
||||
|
||||
write(' <p>')
|
||||
for rect in self.svg_rects:
|
||||
write(' <img src="%s" alt="%s (please add real alternative text for this image)" longdesc="Please add a full description of this image" />\n' % (sliceprefix + rect.name + '.png', rect.name))
|
||||
write(' </p>')
|
||||
|
||||
write('<p><a href="http://validator.w3.org/check?uri=referer"><img src="http://www.w3.org/Icons/valid-xhtml10" alt="Valid XHTML 1.0!" height="31" width="88" /></a></p>')
|
||||
|
||||
write(' </body>\n')
|
||||
write('</html>\n')
|
||||
|
||||
class SVGFilter (saxutils.XMLFilterBase):
|
||||
def __init__ (self, upstream, downstream, mode, **kwargs):
|
||||
saxutils.XMLFilterBase.__init__(self, upstream)
|
||||
self._downstream = downstream
|
||||
self.mode = mode
|
||||
|
||||
def startDocument (self):
|
||||
self.in_throwaway_layer_stack = [False]
|
||||
|
||||
def startElement (self, localname, attrs):
|
||||
def modify_style (style, old_style, new_style=None):
|
||||
styles = style.split (';')
|
||||
new_styles = []
|
||||
if old_style is not None:
|
||||
match_to = old_style + ':'
|
||||
for s in styles:
|
||||
if len (s) > 0 and (old_style is None or not s.startswith (match_to)):
|
||||
new_styles.append (s)
|
||||
if new_style is not None:
|
||||
new_styles.append (new_style)
|
||||
return ';'.join (new_styles)
|
||||
|
||||
dict = {}
|
||||
is_throwaway_layer = False
|
||||
is_slices = False
|
||||
is_hotspots = False
|
||||
is_shadows = False
|
||||
is_layer = False
|
||||
if localname == 'g':
|
||||
for key, value in attrs.items ():
|
||||
if key == 'inkscape:label':
|
||||
if value == 'slices':
|
||||
is_slices = True
|
||||
elif value == 'hotspots':
|
||||
is_hotspots = True
|
||||
elif value == 'shadows':
|
||||
is_shadows = True
|
||||
elif key == 'inkscape:groupmode':
|
||||
if value == 'layer':
|
||||
is_layer = True
|
||||
if mode_shadows in self.mode and is_shadows:
|
||||
# Only remove the shadows
|
||||
is_throwaway_layer = True
|
||||
elif mode_hotspots in self.mode and not (is_hotspots or is_slices):
|
||||
# Remove all layers but hotspots and slices
|
||||
if localname == 'g':
|
||||
is_throwaway_layer = True
|
||||
idict = {}
|
||||
idict.update (attrs)
|
||||
if 'style' not in attrs.keys ():
|
||||
idict['style'] = ''
|
||||
for key, value in idict.items():
|
||||
alocalname = key
|
||||
if alocalname == 'style':
|
||||
had_style = True
|
||||
if alocalname == 'style' and is_slices:
|
||||
# Make slices invisible. Do not check the mode, because there is
|
||||
# no circumstances where we *want* to render slices
|
||||
value = modify_style (value, 'display', 'display:none')
|
||||
if alocalname == 'style' and is_hotspots:
|
||||
if mode_hotspots in self.mode:
|
||||
# Make hotspots visible in hotspots mode
|
||||
value = modify_style (value, 'display', 'display:inline')
|
||||
else:
|
||||
# Make hotspots invisible otherwise
|
||||
value = modify_style (value, 'display', 'display:none')
|
||||
if alocalname == 'style' and mode_invert in self.mode and is_layer and is_shadows:
|
||||
value = modify_style (value, None, 'filter:url(#InvertFilter)')
|
||||
dict[key] = value
|
||||
|
||||
if self.in_throwaway_layer_stack[0] or is_throwaway_layer:
|
||||
self.in_throwaway_layer_stack.insert(0, True)
|
||||
else:
|
||||
self.in_throwaway_layer_stack.insert(0, False)
|
||||
attrs = xmlreader.AttributesImpl(dict)
|
||||
self._downstream.startElement(localname, attrs)
|
||||
|
||||
def characters(self, content):
|
||||
if self.in_throwaway_layer_stack[0]:
|
||||
return
|
||||
self._downstream.characters(content)
|
||||
|
||||
def endElement(self, localname):
|
||||
if self.in_throwaway_layer_stack.pop(0):
|
||||
return
|
||||
self._downstream.endElement(localname)
|
||||
|
||||
def filter_svg (input, output, mode):
|
||||
"""filter_svg(input:file, output:file, mode)
|
||||
|
||||
Parses the SVG input from the input stream.
|
||||
For mode == 'hotspots' it filters out all
|
||||
layers except for hotspots and slices. Also makes hotspots
|
||||
visible.
|
||||
For mode == 'shadows' it filters out the shadows layer.
|
||||
"""
|
||||
|
||||
mode_objs = []
|
||||
if 'hotspots' in mode:
|
||||
mode_objs.append (mode_hotspots)
|
||||
if 'shadows' in mode:
|
||||
mode_objs.append (mode_shadows)
|
||||
if 'slices' in mode:
|
||||
mode_objs.append (mode_slices)
|
||||
if 'invert' in mode:
|
||||
mode_objs.append (mode_invert)
|
||||
if len (mode_objs) == 0:
|
||||
raise ValueError()
|
||||
|
||||
output_gen = saxutils.XMLGenerator(output)
|
||||
parser = make_parser()
|
||||
filter = SVGFilter(parser, output_gen, mode_objs)
|
||||
filter.setFeature(handler.feature_namespaces, False)
|
||||
filter.setErrorHandler(handler.ErrorHandler())
|
||||
# This little I/O dance is here to ensure that SAX parser does not stash away
|
||||
# an open file descriptor for the input file, which would prevent us from unlinking it later
|
||||
with open (input, 'rb') as inp:
|
||||
contents = inp.read ()
|
||||
contents_io = io.BytesIO (contents)
|
||||
source_object = saxutils.prepare_input_source (contents_io)
|
||||
filter.parse(source_object)
|
||||
del filter
|
||||
del parser
|
||||
del output_gen
|
||||
|
||||
def autodetect_threadcount ():
|
||||
try:
|
||||
count = multiprocessing.cpu_count()
|
||||
except NotImplementedError:
|
||||
count = 1
|
||||
return count
|
||||
|
||||
if __name__ == '__main__':
|
||||
# parse command line into arguments and options
|
||||
(options, args) = optParser.parse_args()
|
||||
|
||||
if len(args) != 1:
|
||||
fatalError("\nCall me with the SVG as a parameter.\n\n")
|
||||
originalFilename = args[0]
|
||||
|
||||
svgFilename = originalFilename + '.svg'
|
||||
hotsvgFilename = originalFilename + '.hotspots.svg'
|
||||
modes = ['slices']
|
||||
if options.remove_shadows:
|
||||
modes.append ('shadows')
|
||||
if options.invert:
|
||||
modes.append ('invert')
|
||||
|
||||
with open (svgFilename, 'wb') as output:
|
||||
filter_svg(originalFilename, output, modes)
|
||||
|
||||
if options.hotspots:
|
||||
with open (hotsvgFilename, 'wb') as output:
|
||||
filter_svg(originalFilename, output, ['hotspots'])
|
||||
# setup program variables from command line (in other words, handle non-option args)
|
||||
basename = os.path.splitext(svgFilename)[0]
|
||||
|
||||
if options.sliceprefix:
|
||||
sliceprefix = options.sliceprefix
|
||||
else:
|
||||
sliceprefix = ''
|
||||
|
||||
if not options.scales:
|
||||
del scale_pairs[:]
|
||||
|
||||
if options.number_of_renderers <= 0:
|
||||
options.number_of_renderers = autodetect_threadcount ()
|
||||
|
||||
inkscape_instances = []
|
||||
|
||||
for i in range (0, options.number_of_renderers):
|
||||
inkscape = subprocess.Popen (['inkscape', '--without-gui', '--shell'], stdin=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
if inkscape is None:
|
||||
fatalError("Failed to start Inkscape shell process")
|
||||
inkscape_stderr = inkscape.stderr
|
||||
inkscape_stderr_thread = Thread (target = stderr_reader, args=(inkscape, inkscape_stderr))
|
||||
inkscape_instances.append ([inkscape, inkscape_stderr, inkscape_stderr_thread])
|
||||
|
||||
# initialise results before actually attempting to parse the SVG file
|
||||
svgBounds = SVGRect(0,0,0,0)
|
||||
rectList = []
|
||||
|
||||
# Try to parse the svg file
|
||||
xmlParser = make_parser()
|
||||
xmlParser.setFeature(feature_namespaces, 0)
|
||||
|
||||
# setup XML Parser with an SVGLayerHandler class as a callback parser ####
|
||||
svgLayerHandler = SVGLayerHandler()
|
||||
xmlParser.setContentHandler(svgLayerHandler)
|
||||
try:
|
||||
xmlParser.parse(svgFilename)
|
||||
except SAXParseException as e:
|
||||
fatalError("Error parsing SVG file '%s': line %d,col %d: %s. If you're seeing this within inkscape, it probably indicates a bug that should be reported." % (svgFilename, e.getLineNumber(), e.getColumnNumber(), e.getMessage()))
|
||||
|
||||
# verify that the svg file actually contained some rectangles.
|
||||
if len(svgLayerHandler.svg_rects) == 0:
|
||||
fatalError("""No slices were found in this SVG file. Please refer to the documentation for guidance on how to use this SVGSlice. As a quick summary:
|
||||
|
||||
""" + usageMsg)
|
||||
else:
|
||||
dbg("Parsing successful.")
|
||||
|
||||
#svgLayerHandler.generateXHTMLPage()
|
||||
del xmlParser
|
||||
|
||||
skipped = {}
|
||||
roundrobin = [0]
|
||||
|
||||
# loop through each slice rectangle, and render a PNG image for it
|
||||
svgLayerHandler.svg_rects
|
||||
for rect in svgLayerHandler.svg_rects:
|
||||
slicename = sliceprefix + rect.name
|
||||
rect.renderFromSVG(svgFilename, slicename, skipped, roundrobin, hotsvgFilename)
|
||||
|
||||
cleanup()
|
||||
|
||||
for rect in svgLayerHandler.svg_rects:
|
||||
slicename = sliceprefix + rect.name
|
||||
postprocess_slice(slicename, skipped)
|
||||
if options.hotspots:
|
||||
write_xcur(slicename)
|
||||
|
||||
if options.hotspots:
|
||||
passed = {}
|
||||
for rect in svgLayerHandler.svg_rects:
|
||||
slicename = sliceprefix + rect.name
|
||||
sort_xcur(slicename, passed)
|
||||
#if not option.testing:
|
||||
# delete_hotspot(slicename)
|
||||
|
||||
dbg('Slicing complete.')
|
Loading…
Add table
Add a link
Reference in a new issue