Commit 35e91271 authored by Jürgen Weigert's avatar Jürgen Weigert

initial draught. Beware.

parent 7a5f36bc
......@@ -2,3 +2,6 @@ inkscape-centerline-trace
=========================
Inkscape extension that uses 'autotrace -centerline' and an optimal threshold to vectorize a pixel image
Unfinished command line tool.
Works great with testdata, to be integrated into inkscape.
This diff is collapsed.
#! /usr/bin/python
#
# vectorize strokes in a graymap png file
# as a path along the centerline of the strokes.
#
# This is done with autotrace -centerline, as
# the builtin potrace in inkscape cannot do centerline.
# It will always draw a path around the contour of the
# stroke, resulting in double lines.
#
# Algorithm:
# autotrace needs a bi-level bitmap. In order to find the
# best threshold value, we run autotrace at multiple thresholds
# and evaluate the result.
#
# We count the number of line segments produced and
# measure the total path length drawn.
#
# The svg that has the longest path but the least number of
# segments is returned.
#
#
import sys, os, re, math, tempfile, subprocess
import xml.etree.ElementTree as ET
from PIL import Image
from PIL import ImageOps
from PIL import ImageStat
num_attempts = 15 # min 1, max 255, beware it gets much slower with more attempts.
autotrace_cmd = ['autotrace', '--centerline', '--input-format=pbm', '--output-format=svg' ]
autotrace_cmd += sys.argv[2:]
stroke_style_add = 'stroke-width:%.2f; fill:none; stroke-linecap:round;'
im = Image.open(sys.argv[1]).convert(mode='L', dither=None)
# print [im.format, im.size, im.mode]
im = ImageOps.equalize(im) # equalize histogram
#im.show()
def svg_pathstats(path_d):
""" calculate statistics from an svg path:
length (measuring bezier splines as straight lines through the handles).
points (all, including duplicates)
segments (number of not-connected!) path segments.
"""
path_d = path_d.lower()
p_points = 0
p_length = 0
p_segments = 0
for p in path_d.split('m'):
# print "xxxx",p
pp = re.sub('[cl,]', ' ', p)
pp,closed = re.subn('z\s*$','',pp)
xy = pp.split()
if len(xy) < 2:
# print len(pp)
# print "short path error"
continue
x0 = float(xy[0])
y0 = float(xy[1])
p_points += 1
x = xy[2::2]
y = xy[3::2]
if len(x):
p_segments += 1
if closed:
x.extend(x0)
y.extend(y0)
for i in range(len(x)):
p_points += 1
dx = float(x[i]) - x0
dy = float(y[i]) - y0
p_length += math.sqrt( dx * dx + dy * dy )
x0,y0 = float(x[i]),float(y[i])
return { 'points':p_points, 'segments':p_segments, 'length':p_length }
# slice with a list of histogram maps
# 1 -> 128
# 3 -> 64,128,192
# ...
candidate = {}
for i in range(num_attempts):
threshold = int(256.*(1+i)/(num_attempts+1))
lut = [ 255 for n in range(threshold) ] + [ 0 for n in range(threshold,256) ]
bw = im.point(lut, mode='1')
cand = { 'threshold':threshold, 'img_width':bw.size[0], 'img_height':bw.size[1], 'mean': ImageStat.Stat(im).mean[0] }
fp = tempfile.NamedTemporaryFile(suffix='.pbm', delete=False)
fp.write("P4\n%d %d\n" % (bw.size[0], bw.size[1]))
fp.write(bw.tobytes())
fp.close()
p = subprocess.Popen(autotrace_cmd + [fp.name], stdout=subprocess.PIPE)
cand['svg'] = p.communicate()[0]
os.unlink(fp.name)
# <?xml version="1.0" standalone="yes"?>\n<svg width="86" height="83">\n<path style="stroke:#000000; fill:none;" d="M36 15C37.9219 18.1496 41.7926 19.6686 43.2585 23.1042C47.9556 34.1128 39.524 32.0995 35.179 37.6034C32.6296 40.8328 34 48.1105 34 52M36 17C32.075 22.4565 31.8375 30.074 35 36M74 42L46 38C45.9991 46.1415 46.7299 56.0825 45.6319 64C44.1349 74.7955 23.7094 77.5566 16.044 72.3966C7.27363 66.4928 8.04426 45.0047 16.2276 38.7384C20.6362 35.3626 27.7809 36.0006 33 36M44 37L45 37"/>\n</svg>
xml = ET.fromstring(cand['svg'])
p_len,p_seg,p_pts = 0,0,0
for p in xml.findall('path'):
pstat = svg_pathstats(p.attrib['d'])
p_len += pstat['length']
p_seg += pstat['segments']
p_pts += pstat['points']
cand['length'] = p_len
cand['segments'] = p_seg
cand['points'] = p_pts
if cand['mean'] > 127:
cand['mean'] = 255 - cand['mean'] # should not happen
blackpixels = cand['img_width'] * cand['img_height'] * cand['mean'] / 255.
cand['strokewidth'] = blackpixels / cand['length']
candidate[i] = cand
def calc_weight(cand, idx):
offset = (num_attempts/2.-idx) * (num_attempts/2.-idx) * (cand['img_width']+cand['img_height'])
w = cand['length']*5 - offset*.005 - cand['points']*.2 - cand['segments']*20
# print "calc_weight(%d) = rl=%f o=%f p=%f s=%f -> w=%f" % (idx, cand['length']*5, offset*.005, cand['points']*.2, cand['segments']*20, w)
return w
best_weight_idx = 0
for n in candidate.keys():
# print "candidate ", n
c = candidate[n]
# print "\t mean=%d len=%d seg=%d width=%d" % (c['mean'], c['length'], c['segments'], c['strokewidth'])
if calc_weight(c,n) > calc_weight(candidate[best_weight_idx], best_weight_idx):
best_weight_idx = n
print >>sys.stderr, "best: %d/%d" % (best_weight_idx, num_attempts)
svg = re.sub('stroke:', (stroke_style_add % candidate[best_weight_idx]['strokewidth']) + ' stroke:', candidate[best_weight_idx]['svg'])
print svg
<?xml version="1.0" standalone="yes"?>
<svg width="86" height="83">
<path style="stroke:#000000; fill:none;" d="M35 13L36 13M33 36L46 38M17 38L13 41M33 55L34 55M9 62L10 62M46 62L42 71M12 69L17 74"/>
</svg>
<?xml version="1.0" standalone="yes"?>
<svg width="86" height="83">
<path style="stroke:#000000; fill:none;" d="M35 13L36 13M43 20L43 22M33 36L46 38M17 38L14 41M33 54L34 54M46 59L42 71M9 60L9 62M12 68L19 74"/>
</svg>
<?xml version="1.0" standalone="yes"?>
<svg width="86" height="83">
<path style="stroke:#000000; fill:none;" d="M35 13L36 13M43 20L44 26M33 29C33.1139 38.6073 38.7348 35.8525 46 38M14 41L21 37M34 37L34 41M70 41L72 41M34 53L34 54M9 59C9.98093 66.1326 13.3141 72.8132 21 74M46 61C45.3971 66.8472 42.8303 69.8563 38 73"/>
</svg>
<?xml version="1.0" standalone="yes"?>
<svg width="86" height="83">
<path style="stroke:#000000; fill:none;" d="M35 13C39.4363 16.3557 50.5621 30.252 43.3958 35.9722C41.0177 37.8705 36.7945 36.8274 34 38L34 39L34 40L34 41M32 24L33 24M33 29L33 36L23 36M12 47C12.9419 41.8145 15.2229 39.1518 20 37M66 40L68 40M70 41L72 41M45 49L46 49M34 52L34 55M9 58C10.7327 66.3723 13.3665 72.2934 22 75M46 58C45.9764 64.9047 44.2888 68.4832 39 73M33 74L34 74"/>
</svg>
<?xml version="1.0" standalone="yes"?>
<svg width="86" height="83">
<path style="stroke:#000000; fill:none;" d="M33 25L36 13C43.9827 19.668 45 27.1843 45 37L34 39L34 40L34 41M33 29L33 36C20.4877 36.0069 13.5568 37.8605 11 51M46 50L46 38L54 39M66 40L68 40M70 41L72 41M34 51L34 55M10 53L10 54M9 56C10.2275 65.505 13.2821 73.8951 24 75M46 58C45.9636 68.6225 40.4591 73.3997 30 74M26 74L26 75"/>
</svg>
<?xml version="1.0" standalone="yes"?>
<svg width="86" height="83">
<path style="stroke:#000000; fill:none;" d="M35 37C31.4877 30.4185 31.908 20.1995 36 14C44.4762 20.1014 45 27.3138 45 37C40.4755 37.0005 35.6539 36.017 34 41M26 75C9.11434 74.603 4.51452 51.9607 14.652 40.2778C18.6843 35.6307 26.4129 36.0123 32 36M46 52L46 38L57 39M65 40L73 41M34 49L34 55M45 55L46 55M46 58C45.9566 70.671 38.6845 72.3725 28 75"/>
</svg>
<?xml version="1.0" standalone="yes"?>
<svg width="86" height="83">
<path style="stroke:#000000; fill:none;" d="M35 13L34 54M37 14C44.2628 21.6611 50.3402 36.9458 35 37M45 36C46.7461 41.0174 46 46.7076 46 52C46 56.9639 47.1286 63.5993 44.2577 67.956C39.3377 75.4223 19.6016 77.5109 13.419 70.7716C7.11819 63.9036 8.76418 44.6208 16.2137 38.9699C20.8033 35.4885 26.5802 36 32 36M47 38L74 41"/>
</svg>
<?xml version="1.0" standalone="yes"?>
<svg width="86" height="83">
<path style="stroke:#000000; fill:none;" d="M33 36C33 28.5938 31.5034 20.2513 36 14C44.0717 20.2981 45 27.3227 45 37C37.7411 36.9808 30.2003 35.0728 23 36.4684C9.61578 39.0624 5.40403 61.4479 14.2245 70.8912C19.0138 76.0186 25.6687 75.2473 32 74.267C37.406 73.4299 42.9421 71.566 45.1065 65.9954C48.1485 58.1657 46 46.3247 46 38L73 41M34 37L34 55"/>
</svg>
<?xml version="1.0" standalone="yes"?>
<svg width="86" height="83">
<path style="stroke:#000000; fill:none;" d="M73 41L46 38C46 45.8995 46.6801 54.1394 45.8951 62C44.6743 74.2236 25.3043 78.6064 16.0941 72.2616C7.2986 66.2024 7.95918 45.1857 16.2276 38.7276C20.7209 35.2182 27.6709 36 33 36C33 28.4912 31.71 20.4996 36 14C45.6012 21.3822 49.7625 36.9878 34 37L34 53M44 36L46 38"/>
</svg>
<?xml version="1.0" standalone="yes"?>
<svg width="86" height="83">
<path style="stroke:#000000; fill:none;" d="M74 41L46 38C46 46.1497 46.9294 54.9243 45.811 63C44.2552 74.2335 24.447 78.476 16.044 72.436C7.08473 65.9961 7.45308 45.4103 16.2137 38.7276C20.7732 35.2496 27.6239 36 33 36C33 31.6993 30.9265 19.6726 34.6026 16.7369C42.5609 10.3814 44.9904 33.5296 45 37L34 37L34 54"/>
</svg>
<?xml version="1.0" standalone="yes"?>
<svg width="86" height="83">
<path style="stroke:#000000; fill:none;" d="M33 36C33 32.0923 30.9407 19.1244 35.3171 17.2562C42.0096 14.3993 46.9503 29.2045 43.9722 33.6821C40.986 38.1721 30.6349 35.8081 26 36.4259C12.1372 38.2738 8.52784 49.8971 10.1852 63C11.965 77.071 34.1185 79.0671 42.6057 69.7863C49.0763 62.7107 46 46.9275 46 38L73 41M44 36L46 38M34 37L34 55"/>
</svg>
<?xml version="1.0" standalone="yes"?>
<svg width="86" height="83">
<path style="stroke:#000000; fill:none;" d="M72 41L46 38C46 46.1288 48.4105 59.6384 44.6682 66.9846C40.4132 75.3372 22.5355 77.6622 15.2137 72.2585C7.05842 66.2396 8.5551 46.0701 15.4298 39.5131C20.1978 34.9655 28.2008 36.7142 34 35C32.897 31.2686 30.9637 20.3364 35.3179 17.9977C42.4448 14.1698 44.5271 27.9837 44.4275 31.9807C44.2638 38.5445 37.8848 34.9935 35.0278 38.6034C32.3281 42.0144 34 50.8315 34 55M44 36L46 38"/>
</svg>
<?xml version="1.0" standalone="yes"?>
<svg width="86" height="83">
<path style="stroke:#000000; fill:none;" d="M36 15C42.0071 22.4582 45.0622 25.9544 44 36C36.8923 36 28.9833 35.0349 22 36.4792C9.83826 38.9944 6.24672 60.9807 13.4992 69.8912C18.0216 75.4474 24.6807 74.3245 31 73.9645C36.7805 73.6352 42.8114 71.9202 45.1065 65.9961C48.1432 58.1577 46 46.3313 46 38L73 41M36 16C31.4635 22.3067 33.068 29.7267 34 37M44 36L46 38M34 37L34 56"/>
</svg>
<?xml version="1.0" standalone="yes"?>
<svg width="86" height="83">
<path style="stroke:#000000; fill:none;" d="M36 17C39.9633 21.1547 47.8096 27.1422 42.3974 33.6829C40.2733 36.2498 36.5595 35.9065 35.0093 39.1481C32.9903 43.3699 34 49.4398 34 54M36 18C31.9533 23.6257 31.9533 30.3743 36 36M74 41C68.5839 40.5726 50.7521 35.2826 46.7731 39.6427C44.8936 41.7025 45.9881 46.4559 45.9992 49C46.0469 59.895 47.2264 71.1836 34 73.7384C31.0693 74.3045 27.9943 74.9361 25 74.9205C8.45273 74.8343 5.39624 50.859 14.5139 40.2284C18.4906 35.5919 28.3438 36.0468 34 36M43 37L46 39"/>
</svg>
<?xml version="1.0" standalone="yes"?>
<svg width="86" height="83">
<path style="stroke:#000000; fill:none;" d="M36 17C37.3567 34.0483 51.3397 48.775 44.2577 66.9954C40.7937 75.9076 23.3402 77.3822 16.0941 72.7716C7.25806 67.1493 8.13482 47.6595 14.652 40.3943C20.1913 34.2192 31.0955 36.9811 38 33M39 35C33.2874 41.1783 34 47.0271 34 55M46 38L73 42"/>
</svg>
<?xml version="1.0" standalone="yes"?>
<svg width="86" height="83">
<path style="stroke:#000000; fill:none;" d="M36 16 C 36.234 21.3664 36.9638 29.0078 39.4336 33.8302 C 41.1582 37.1975 44.8057 39.1054 45.6204 43.0008C46.7713 48.5036 46.3265 64.1761 43.142 68.8117C38.4884 75.586 18.9647 77.0388 13.51 70.7716C7.59671 63.9773 8.84516 45.8137 15.4298 39.652C20.2345 35.156 31.9604 34.6914 38 36M38 36C32.1726 42.8915 32.0526 54.6445 43 55M45 38L74 42M35 47L45 47M35 49L45 49"/>
</svg>
<?xml version="1.0" standalone="yes"?>
<svg width="86" height="83">
<path style="stroke:#000000; fill:none;" d="M36 19C36.3429 23.1985 39.0833 29.0901 36.3819 32.8519C32.6638 38.0294 25.1617 34.9091 20.0008 36.8935C11.8802 40.0159 9.61304 52.2143 10.0941 60C11.0082 74.793 34.633 81.31 43.2577 68.8912C49.6961 59.6205 39.2147 44.404 39 34M40 41C49.4586 35.8802 62.8448 39.7155 73 41"/>
</svg>
<?xml version="1.0" standalone="yes"?>
<svg width="86" height="83">
<path style="stroke:#000000; fill:none;" d="M36 15L40 40C50.9739 35.8902 62.7142 40.7671 74 41M38 33C31.348 36.8355 19.639 34.329 14.652 40.2284C8.05006 48.0382 7.55239 65.9343 16.0941 72.3472C23.0368 77.5596 40.5267 75.638 44.142 66.9846C47.9081 57.97 40.001 49.2043 40 40"/>
</svg>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment