diff --git a/extensions/fablabchemnitz_triangulation.inx b/extensions/fablabchemnitz_triangulation.inx new file mode 100644 index 00000000..50992cf3 --- /dev/null +++ b/extensions/fablabchemnitz_triangulation.inx @@ -0,0 +1,26 @@ + + + <_name>Image Triangulation + fablabchemnitz.de.triangulation + + + 150 + 200 + 255 + 0 + 0 + + + + + image + + + + + + + + diff --git a/extensions/fablabchemnitz_triangulation.py b/extensions/fablabchemnitz_triangulation.py new file mode 100644 index 00000000..7673330e --- /dev/null +++ b/extensions/fablabchemnitz_triangulation.py @@ -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() \ No newline at end of file