Added triangulate
This commit is contained in:
parent
83dce82604
commit
3d6eb5fe6e
26
extensions/fablabchemnitz_triangulation.inx
Normal file
26
extensions/fablabchemnitz_triangulation.inx
Normal file
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension>
|
||||
<_name>Image Triangulation</_name>
|
||||
<id>fablabchemnitz.de.triangulation</id>
|
||||
<param name="tab" type="notebook">
|
||||
<page name="tab" _gui-text="Options">
|
||||
<param name="num_points" type="int" min="20" max="10000" _gui-text="Sampled points:">150</param>
|
||||
<param name="edge_thresh_min" type="int" min="0" max="255" _gui-text="Edge detection min">200</param>
|
||||
<param name="edge_thresh_max" type="int" min="0" max="255" _gui-text="Edge detection max">255</param>
|
||||
<param name="gradient_fill" type="boolean" _gui-text="Gradient fill">0</param>
|
||||
<param name="add_corners" type="boolean" _gui-text="Add corners">0</param>
|
||||
</page>
|
||||
</param>
|
||||
|
||||
<effect>
|
||||
<object-type>image</object-type>
|
||||
<effects-menu>
|
||||
<submenu _name="FabLab Chemnitz">
|
||||
<submenu _name="Various" />
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command reldir="extensions" interpreter="python">fablabchemnitz_triangulation.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
253
extensions/fablabchemnitz_triangulation.py
Normal file
253
extensions/fablabchemnitz_triangulation.py
Normal file
@ -0,0 +1,253 @@
|
||||
#!/usr/bin/env python3
|
||||
'''
|
||||
Copyright (C) 2014 Nicola Romano', romano.nicola@gmail.com
|
||||
|
||||
version 0.1
|
||||
0.1: first working version
|
||||
|
||||
------------------------------------------------------------------------
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
------------------------------------------------------------------------
|
||||
|
||||
'''
|
||||
|
||||
import inkex
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
import os
|
||||
import random
|
||||
from PIL import Image
|
||||
from lxml import etree
|
||||
from inkex import Transform
|
||||
import numpy as np
|
||||
from scipy.spatial import Delaunay
|
||||
from scipy.cluster.vq import kmeans2
|
||||
import cv2
|
||||
|
||||
class Triangulation(inkex.Effect):
|
||||
def __init__(self):
|
||||
# Call base class construtor.
|
||||
inkex.Effect.__init__(self)
|
||||
# Option parser:
|
||||
# -n, --num_points
|
||||
# -m, --edge_thresh_min
|
||||
# -M, --edge_thresh_max
|
||||
# -c, --add_corners
|
||||
# -g, --gradient_fill
|
||||
# -b, --tab
|
||||
self.arg_parser.add_argument("-n", "--num_points", type=int, default=100, help="Number of points to be sampled")
|
||||
self.arg_parser.add_argument("-m", "--edge_thresh_min", type=int, default=200, help="Minimum threshold for edge detection")
|
||||
self.arg_parser.add_argument("-M", "--edge_thresh_max", type=int, default=255, help="Maximum threshold for edge detection")
|
||||
self.arg_parser.add_argument("-c", "--add_corners", type=inkex.Boolean, default=0, help="Use corners for triangulation?")
|
||||
self.arg_parser.add_argument("-g", "--gradient_fill", type=inkex.Boolean, default=0, help="Fill triangles with gradient?")
|
||||
self.arg_parser.add_argument("-b", "--tab", default='', help="The tab of the interface")
|
||||
|
||||
def draw_SVG_path(self, points, closed, style, parent):
|
||||
pathdesc = "M "
|
||||
for p in points:
|
||||
pathdesc = pathdesc + str(p[0]) + "," + str(p[1]) + " "
|
||||
if closed == 1:
|
||||
pathdesc = pathdesc + "Z"
|
||||
path = etree.SubElement(parent, inkex.addNS('path','svg'), {'style' : str(inkex.Style(style)), 'd' : pathdesc})
|
||||
return path
|
||||
|
||||
def effect(self):
|
||||
|
||||
# Check we have something selected
|
||||
if len(self.svg.selected) == 0:
|
||||
inkex.errormsg("Please select an image.")
|
||||
exit()
|
||||
else:
|
||||
# Check it is an image
|
||||
for id, obj in self.svg.selected.items():
|
||||
if obj.tag[len(obj.tag)-5:] != "image":
|
||||
inkex.errormsg("The selected object (" + id + ") is not an image, skipping.")
|
||||
continue
|
||||
else:
|
||||
(self.path, errcode) = self.checkImagePath(obj) # This also ensures the file exists
|
||||
if errcode==1:
|
||||
inkex.errormsg("Embedded images are not (yet?) supported, please use a linked image. Skipping.")
|
||||
continue
|
||||
elif errcode==2:
|
||||
inkex.errormsg("The image points to a file, which seems to be missing: "+self.path+". Skipping.")
|
||||
continue
|
||||
|
||||
grpname = 'img_triangles'
|
||||
# Make sure that the id/name is unique
|
||||
index = 0
|
||||
while (str(self.svg.get_ids()) in grpname):
|
||||
grpname = 'axis' + str(index)
|
||||
index = index + 1
|
||||
|
||||
grp_name = grpname
|
||||
grp_attribs = {inkex.addNS('label','inkscape'):grp_name}
|
||||
# The group to put everything in
|
||||
grp = etree.SubElement(self.svg.get_current_layer(), 'g', grp_attribs)
|
||||
|
||||
# Find image size and position in Inkscape
|
||||
try:
|
||||
self.img_x_pos = float(obj.get("x"))
|
||||
self.img_y_pos = float(obj.get("y"))
|
||||
except:
|
||||
self.img_x_pos = 0
|
||||
self.img_y_pos = 0
|
||||
self.img_width = float(obj.get("width"))
|
||||
self.img_height = float(obj.get("height"))
|
||||
im = Image.open(self.path)
|
||||
# IMPORTANT!
|
||||
# The numpy array is accessed as im.data[row,column], that is data[y_coord, x_coord]
|
||||
# Be careful not to pass coordinates as (x,y): rather use (y,x)!
|
||||
im.data = np.asarray(im)
|
||||
# The RGB components of all the pixels in the image
|
||||
self.red, self.green, self.blue = im.data[:,:,0], im.data[:,:,1], im.data[:,:,2]
|
||||
|
||||
# Find real image size
|
||||
(self.img_real_width, self.img_real_height) = im.size
|
||||
|
||||
self.doTriangulation(grp)
|
||||
|
||||
# Check file exists and returns its path
|
||||
def checkImagePath(self, obj):
|
||||
xlink = obj.get(inkex.addNS('href','xlink'))
|
||||
if xlink[:5] == 'data:': # Embedded image
|
||||
return (None, 1)
|
||||
|
||||
# Code shamelessly copied from the Embed image extension :)
|
||||
if xlink is None or xlink[:5] != 'data:':
|
||||
absref = obj.get(inkex.addNS('absref','sodipodi'))
|
||||
url = urllib.parse.urlparse(xlink)
|
||||
href = urllib.request.url2pathname(url.path)
|
||||
|
||||
path=''
|
||||
#path selection strategy:
|
||||
# 1. href if absolute
|
||||
# 2. realpath-ified href
|
||||
# 3. absref, only if the above does not point to a file
|
||||
if (href != None):
|
||||
path = os.path.realpath(href)
|
||||
if (not os.path.isfile(path)):
|
||||
if (absref != None):
|
||||
path=absref
|
||||
if (not os.path.isfile(path)):
|
||||
return (path, 2)
|
||||
|
||||
return (path, 0)
|
||||
|
||||
# Converts image coordinates to screen coordinates
|
||||
def imgToScreen(self, x, y):
|
||||
newx = x / (self.img_real_width/self.img_width) + self.img_x_pos
|
||||
newy = y / (self.img_real_height/self.img_height) + self.img_y_pos
|
||||
return (newx, newy)
|
||||
|
||||
def createLinearGradient(self, x1, y1, x2, y2, color1, color2, gradID):
|
||||
attribs = {
|
||||
'x1' : str(x1),
|
||||
'y1' : str(y1),
|
||||
'x2' : str(x2),
|
||||
'y2' : str(y2),
|
||||
'id' : gradID,
|
||||
'gradientUnits' : "userSpaceOnUse",
|
||||
'{'+inkex.NSS[u'xlink']+'}href': "#"+gradID
|
||||
}
|
||||
|
||||
svgdefs = self.document.getroot().find(inkex.addNS('defs', 'svg'))
|
||||
gradient = etree.SubElement(svgdefs, inkex.addNS('linearGradient','svg'), attribs)
|
||||
|
||||
attribs = {
|
||||
'offset' : "0%",
|
||||
'style' : "stop-color:"+color1+"; stop-opacity:1"
|
||||
}
|
||||
stop1 = etree.SubElement(gradient, inkex.addNS('stop','svg'), attribs)
|
||||
attribs = {
|
||||
'offset' : "100%",
|
||||
'style' : "stop-color:"+color2+"; stop-opacity:1"
|
||||
}
|
||||
stop2 = etree.SubElement(gradient, inkex.addNS('stop','svg'), attribs)
|
||||
return gradient
|
||||
|
||||
def doTriangulation (self, grp):
|
||||
#inkex.utils.debug(self.path)
|
||||
# Read image with OpenCV
|
||||
imcv = cv2.imread(self.path)
|
||||
# Convert to grayscale
|
||||
gray = cv2.cvtColor(imcv,cv2.COLOR_RGB2GRAY)
|
||||
gray = np.float32(gray)
|
||||
# Find edges
|
||||
edges = cv2.Canny(imcv, self.options.edge_thresh_min, self.options.edge_thresh_max, 100)
|
||||
# Find coordinates of the edges
|
||||
coords = [(float(x),float(y)) for y, row in enumerate(edges) for x, col in enumerate(row) if col>0]
|
||||
#pt = random.sample(coords, self.options.num_points)
|
||||
pt, idx = kmeans2(np.array(coords), self.options.num_points, minit="points")
|
||||
|
||||
if self.options.add_corners:
|
||||
# Add the four corners
|
||||
corners = [(0, 0),
|
||||
(self.img_real_width-1, 0),
|
||||
(0, self.img_real_height-1),
|
||||
(self.img_real_width-1, self.img_real_height-1)]
|
||||
|
||||
pt = np.vstack((pt, corners))
|
||||
|
||||
# Perform Delaunay triangulation
|
||||
tri = Delaunay(pt)
|
||||
tri_coord = [(pt[t[0]], pt[t[1]], pt[t[2]]) for t in tri.simplices]
|
||||
tri_colors = [(
|
||||
(self.red[int(t[0][1]),int(t[0][0])], self.green[int(t[0][1]),int(t[0][0])], self.blue[int(t[0][1]),int(t[0][0])]),
|
||||
(self.red[int(t[1][1]),int(t[1][0])], self.green[int(t[1][1]),int(t[1][0])], self.blue[int(t[1][1]),int(t[1][0])]),
|
||||
(self.red[int(t[2][1]),int(t[2][0])], self.green[int(t[2][1]),int(t[2][0])], self.blue[int(t[2][1]),int(t[2][0])])
|
||||
)
|
||||
for t in tri_coord]
|
||||
|
||||
for i, c in enumerate(tri_coord):
|
||||
# Convert to screen coordinates
|
||||
v0 = self.imgToScreen(c[0][0], c[0][1])
|
||||
v1 = self.imgToScreen(c[1][0], c[1][1])
|
||||
v2 = self.imgToScreen(c[2][0], c[2][1])
|
||||
col = tri_colors[i]
|
||||
fill = ""
|
||||
|
||||
if self.options.gradient_fill:
|
||||
color1 = "rgb("+str(col[0][0])+","+str(col[0][1])+","+str(col[0][2])+")"
|
||||
color2 = "rgb("+str(0.5*col[1][0]+0.5*col[2][0])+","+ \
|
||||
str(0.5*col[1][1]+0.5*col[2][1])+","+ \
|
||||
str(0.5*col[1][2]+0.5*col[2][2])+")"
|
||||
gradID = 'linearGradient'
|
||||
# Make sure that the id is inique
|
||||
index = 0
|
||||
while (str(self.svg.get_ids()) in gradID):
|
||||
gradID = 'linearGradient' + str(index)
|
||||
index = index + 1
|
||||
|
||||
#self.doc_ids[gradID]=1
|
||||
|
||||
gradient = self.createLinearGradient(v0[0], v0[1],
|
||||
0.5*(v1[0]+v2[0]), 0.5*(v1[1]+v2[1]),
|
||||
color1, color2, gradID)
|
||||
fill = "url(#"+gradient.get("id")+")"
|
||||
else:
|
||||
fill = "rgb("+str(col[0][0])+","+str(col[0][1])+","+str(col[0][2])+")"
|
||||
|
||||
tri_style = {
|
||||
'stroke-width' : '1px',
|
||||
'stroke-linecap' : 'round',
|
||||
'stroke-opacity' : '1',
|
||||
'fill' : fill,
|
||||
'fill-opacity' : '1',
|
||||
'stroke' : fill
|
||||
}
|
||||
|
||||
self.draw_SVG_path([v0, v1, v2], 1, tri_style, grp)
|
||||
|
||||
Triangulation().run()
|
Reference in New Issue
Block a user