This repository has been archived on 2023-03-25. You can view files and clone it, but cannot push or open issues or pull requests.
mightyscape-1.1-deprecated/extensions/fablabchemnitz/triangulation/triangulation.py

248 lines
10 KiB
Python
Raw Normal View History

2020-08-01 05:02:40 +02:00
#!/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
------------------------------------------------------------------------
'''
2020-08-18 14:29:20 +02:00
import base64
from io import BytesIO
2020-08-01 05:02:40 +02:00
import inkex
import os
from PIL import Image
from lxml import etree
import numpy as np
from scipy.spatial import Delaunay
from scipy.cluster.vq import kmeans2
import cv2
2020-08-18 14:29:20 +02:00
import urllib.request as urllib
2020-08-01 05:02:40 +02:00
2021-04-16 14:41:12 +02:00
class Triangulation(inkex.EffectExtension):
def add_arguments(self, pars):
pars.add_argument("-n", "--num_points", type=int, default=100, help="Number of points to be sampled")
pars.add_argument("-m", "--edge_thresh_min", type=int, default=200, help="Minimum threshold for edge detection")
pars.add_argument("-M", "--edge_thresh_max", type=int, default=255, help="Maximum threshold for edge detection")
pars.add_argument("-c", "--add_corners", type=inkex.Boolean, default=0, help="Use corners for triangulation?")
pars.add_argument("-g", "--gradient_fill", type=inkex.Boolean, default=0, help="Fill triangles with gradient?")
pars.add_argument("-b", "--tab", default='', help="The tab of the interface")
2020-08-01 05:02:40 +02:00
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
2020-08-18 14:29:20 +02:00
def checkImagePath(self, node):
"""Embed the data of the selected Image Tag element"""
xlink = node.get('xlink:href')
if xlink and xlink[:5] == 'data:':
# No need, data alread embedded
return
url = urllib.urlparse(xlink)
href = urllib.url2pathname(url.path)
# Primary location always the filename itself.
path = self.absolute_href(href or '')
# Backup directory where we can find the image
if not os.path.isfile(path):
path = node.get('sodipodi:absref', path)
if not os.path.isfile(path):
inkex.errormsg('File not found "{}". Unable to embed image.').format(path)
2020-08-18 14:29:20 +02:00
return
if (os.path.isfile(path)):
return path
2020-08-01 05:02:40 +02:00
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:
2020-08-18 14:29:20 +02:00
self.path = self.checkImagePath(obj) # This also ensures the file exists
2020-08-01 05:02:40 +02:00
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"))
2020-08-18 14:29:20 +02:00
if self.path is None: #check if image is embedded or linked
image_string = obj.get('{http://www.w3.org/1999/xlink}href')
# find comma position
i = 0
while i < 40:
if image_string[i] == ',':
break
i = i + 1
im = Image.open(BytesIO(base64.b64decode(image_string[i + 1:len(image_string)])))
else:
im = Image.open(self.path)
# IMPORTANT!
2020-08-01 05:02:40 +02:00
# 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
2020-08-18 14:29:20 +02:00
self.doTriangulation(im, grp)
2020-08-01 05:02:40 +02:00
# 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
2020-08-18 14:29:20 +02:00
def doTriangulation (self, im, grp):
2020-08-01 05:02:40 +02:00
# Read image with OpenCV
2020-08-18 14:29:20 +02:00
imcv = np.array(im)
#imcv = cv2.imread(self.path)
2020-08-01 05:02:40 +02:00
# 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]
2020-08-18 14:29:20 +02:00
try:
pt, idx = kmeans2(np.array(coords), self.options.num_points, minit="points")
except ValueError:
inkex.utils.debug("Too much points. Reduce sampled points and try again!")
exit(1)
2020-08-01 05:02:40 +02:00
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)
if __name__ == '__main__':
Triangulation().run()