├── Low Polygon Art Demo.gif ├── LowPolygonArt.com.jsx └── README.md /Low Polygon Art Demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryamansharda/LowPolygonArt/5f9197c948e82cb4b632f2a06de1dd371d0579fb/Low Polygon Art Demo.gif -------------------------------------------------------------------------------- /LowPolygonArt.com.jsx: -------------------------------------------------------------------------------- 1 | #target photoshop 2 | 3 | /* 4 | * Delaunay Transformation Math 5 | */ 6 | 7 | var Delaunay; 8 | 9 | (function() { 10 | "use strict"; 11 | 12 | var EPSILON = 1.0 / 1048576.0; 13 | 14 | function supertriangle(vertices) { 15 | var xmin = Number.POSITIVE_INFINITY, 16 | ymin = Number.POSITIVE_INFINITY, 17 | xmax = Number.NEGATIVE_INFINITY, 18 | ymax = Number.NEGATIVE_INFINITY, 19 | i, dx, dy, dmax, xmid, ymid; 20 | 21 | for(i = vertices.length; i--; ) { 22 | if(vertices[i][0] < xmin) xmin = vertices[i][0]; 23 | if(vertices[i][0] > xmax) xmax = vertices[i][0]; 24 | if(vertices[i][1] < ymin) ymin = vertices[i][1]; 25 | if(vertices[i][1] > ymax) ymax = vertices[i][1]; 26 | } 27 | 28 | dx = xmax - xmin; 29 | dy = ymax - ymin; 30 | dmax = Math.max(dx, dy); 31 | xmid = xmin + dx * 0.5; 32 | ymid = ymin + dy * 0.5; 33 | 34 | return [ 35 | [xmid - 20 * dmax, ymid - dmax], 36 | [xmid , ymid + 20 * dmax], 37 | [xmid + 20 * dmax, ymid - dmax] 38 | ]; 39 | } 40 | 41 | function circumcircle(vertices, i, j, k) { 42 | var x1 = vertices[i][0], 43 | y1 = vertices[i][1], 44 | x2 = vertices[j][0], 45 | y2 = vertices[j][1], 46 | x3 = vertices[k][0], 47 | y3 = vertices[k][1], 48 | fabsy1y2 = Math.abs(y1 - y2), 49 | fabsy2y3 = Math.abs(y2 - y3), 50 | xc, yc, m1, m2, mx1, mx2, my1, my2, dx, dy; 51 | 52 | /* Check for coincident points */ 53 | if(fabsy1y2 < EPSILON && fabsy2y3 < EPSILON) 54 | throw new Error("Eek! Coincident points!"); 55 | 56 | if(fabsy1y2 < EPSILON) { 57 | m2 = -((x3 - x2) / (y3 - y2)); 58 | mx2 = (x2 + x3) / 2.0; 59 | my2 = (y2 + y3) / 2.0; 60 | xc = (x2 + x1) / 2.0; 61 | yc = m2 * (xc - mx2) + my2; 62 | } 63 | 64 | else if(fabsy2y3 < EPSILON) { 65 | m1 = -((x2 - x1) / (y2 - y1)); 66 | mx1 = (x1 + x2) / 2.0; 67 | my1 = (y1 + y2) / 2.0; 68 | xc = (x3 + x2) / 2.0; 69 | yc = m1 * (xc - mx1) + my1; 70 | } 71 | 72 | else { 73 | m1 = -((x2 - x1) / (y2 - y1)); 74 | m2 = -((x3 - x2) / (y3 - y2)); 75 | mx1 = (x1 + x2) / 2.0; 76 | mx2 = (x2 + x3) / 2.0; 77 | my1 = (y1 + y2) / 2.0; 78 | my2 = (y2 + y3) / 2.0; 79 | xc = (m1 * mx1 - m2 * mx2 + my2 - my1) / (m1 - m2); 80 | yc = (fabsy1y2 > fabsy2y3) ? 81 | m1 * (xc - mx1) + my1 : 82 | m2 * (xc - mx2) + my2; 83 | } 84 | 85 | dx = x2 - xc; 86 | dy = y2 - yc; 87 | return {i: i, j: j, k: k, x: xc, y: yc, r: dx * dx + dy * dy}; 88 | } 89 | 90 | function dedup(edges) { 91 | var i, j, a, b, m, n; 92 | 93 | for(j = edges.length; j; ) { 94 | b = edges[--j]; 95 | a = edges[--j]; 96 | 97 | for(i = j; i; ) { 98 | n = edges[--i]; 99 | m = edges[--i]; 100 | 101 | if((a === m && b === n) || (a === n && b === m)) { 102 | edges.splice(j, 2); 103 | edges.splice(i, 2); 104 | break; 105 | } 106 | } 107 | } 108 | } 109 | 110 | Delaunay = { 111 | triangulate: function(vertices, key) { 112 | var n = vertices.length, 113 | i, j, indices, st, open, closed, edges, dx, dy, a, b, c; 114 | 115 | /* Bail if there aren't enough vertices to form any triangles. */ 116 | if(n < 3) 117 | return []; 118 | 119 | /* Slice out the actual vertices from the passed objects. (Duplicate the 120 | * array even if we don't, though, since we need to make a supertriangle 121 | * later on!) */ 122 | vertices = vertices.slice(0); 123 | 124 | if(key) 125 | for(i = n; i--; ) 126 | vertices[i] = vertices[i][key]; 127 | 128 | /* Make an array of indices into the vertex array, sorted by the 129 | * vertices' x-position. Force stable sorting by comparing indices if 130 | * the x-positions are equal. */ 131 | indices = new Array(n); 132 | 133 | for(i = n; i--; ) 134 | indices[i] = i; 135 | 136 | indices.sort(function(i, j) { 137 | var diff = vertices[j][0] - vertices[i][0]; 138 | return diff !== 0 ? diff : i - j; 139 | }); 140 | 141 | /* Next, find the vertices of the supertriangle (which contains all other 142 | * triangles), and append them onto the end of a (copy of) the vertex 143 | * array. */ 144 | st = supertriangle(vertices); 145 | vertices.push(st[0], st[1], st[2]); 146 | 147 | /* Initialize the open list (containing the supertriangle and nothing 148 | * else) and the closed list (which is empty since we havn't processed 149 | * any triangles yet). */ 150 | open = [circumcircle(vertices, n + 0, n + 1, n + 2)]; 151 | closed = []; 152 | edges = []; 153 | 154 | /* Incrementally add each vertex to the mesh. */ 155 | for(i = indices.length; i--; edges.length = 0) { 156 | c = indices[i]; 157 | 158 | /* For each open triangle, check to see if the current point is 159 | * inside it's circumcircle. If it is, remove the triangle and add 160 | * it's edges to an edge list. */ 161 | for(j = open.length; j--; ) { 162 | /* If this point is to the right of this triangle's circumcircle, 163 | * then this triangle should never get checked again. Remove it 164 | * from the open list, add it to the closed list, and skip. */ 165 | dx = vertices[c][0] - open[j].x; 166 | if(dx > 0.0 && dx * dx > open[j].r) { 167 | closed.push(open[j]); 168 | open.splice(j, 1); 169 | continue; 170 | } 171 | 172 | /* If we're outside the circumcircle, skip this triangle. */ 173 | dy = vertices[c][1] - open[j].y; 174 | if(dx * dx + dy * dy - open[j].r > EPSILON) 175 | continue; 176 | 177 | /* Remove the triangle and add it's edges to the edge list. */ 178 | edges.push( 179 | open[j].i, open[j].j, 180 | open[j].j, open[j].k, 181 | open[j].k, open[j].i 182 | ); 183 | open.splice(j, 1); 184 | } 185 | 186 | /* Remove any doubled edges. */ 187 | dedup(edges); 188 | 189 | /* Add a new triangle for each edge. */ 190 | for(j = edges.length; j; ) { 191 | b = edges[--j]; 192 | a = edges[--j]; 193 | open.push(circumcircle(vertices, a, b, c)); 194 | } 195 | } 196 | 197 | /* Copy any remaining open triangles to the closed list, and then 198 | * remove any triangles that share a vertex with the supertriangle, 199 | * building a list of triplets that represent triangles. */ 200 | for(i = open.length; i--; ) 201 | closed.push(open[i]); 202 | open.length = 0; 203 | 204 | for(i = closed.length; i--; ) 205 | if(closed[i].i < n && closed[i].j < n && closed[i].k < n) 206 | open.push(closed[i].i, closed[i].j, closed[i].k); 207 | 208 | /* Yay, we're done! */ 209 | return open; 210 | }, 211 | contains: function(tri, p) { 212 | /* Bounding box test first, for quick rejections. */ 213 | if((p[0] < tri[0][0] && p[0] < tri[1][0] && p[0] < tri[2][0]) || 214 | (p[0] > tri[0][0] && p[0] > tri[1][0] && p[0] > tri[2][0]) || 215 | (p[1] < tri[0][1] && p[1] < tri[1][1] && p[1] < tri[2][1]) || 216 | (p[1] > tri[0][1] && p[1] > tri[1][1] && p[1] > tri[2][1])) 217 | return null; 218 | 219 | var a = tri[1][0] - tri[0][0], 220 | b = tri[2][0] - tri[0][0], 221 | c = tri[1][1] - tri[0][1], 222 | d = tri[2][1] - tri[0][1], 223 | i = a * d - b * c; 224 | 225 | /* Degenerate tri. */ 226 | if(i === 0.0) 227 | return null; 228 | 229 | var u = (d * (p[0] - tri[0][0]) - b * (p[1] - tri[0][1])) / i, 230 | v = (a * (p[1] - tri[0][1]) - c * (p[0] - tri[0][0])) / i; 231 | 232 | /* If we're outside the tri, fail. */ 233 | if(u < 0.0 || v < 0.0 || (u + v) > 1.0) 234 | return null; 235 | 236 | return [u, v]; 237 | } 238 | }; 239 | 240 | if(typeof module !== "undefined") 241 | module.exports = Delaunay; 242 | })(); 243 | 244 | 245 | /* 246 | * The following code creates the low polygon art background. 247 | * It takes in a vertex value which specifies the details/number of triangles in an image 248 | * Recommended to start with 100 vertices 249 | * 250 | * Aryaman Sharda 251 | * January 5th, 2018 252 | */ 253 | 254 | // Set the ruler type 255 | if (app.preferences.rulerUnits != Units.PIXELS) { 256 | app.preferences.rulerUnits = Units.PIXELS; 257 | } 258 | 259 | // Keep a reference to the document and original layer 260 | var docRef = app.activeDocument; 261 | var layerRef = docRef.activeLayer; 262 | 263 | app.bringToFront(); 264 | 265 | var mainWindow = new Window ("dialog", "Generate Polygon Art", [0,0,0,0]); 266 | mainWindow.size = [200,100]; 267 | 268 | var exec = mainWindow.add("button", [0,0,0,0], "Run", {name:"ok"}); 269 | exec.size = [100,50]; 270 | exec.location = [50,25]; 271 | 272 | exec.onClick = function () { 273 | 274 | mainWindow.visible = false; 275 | mainWindow.close(); 276 | 277 | //Display progress bar 278 | var value = 0; 279 | var win = new Window("palette{text:'Generating artwork...',bounds:[0,0,580,140]," + 280 | "progress:Progressbar{bounds:[20,10,460,30] , minvalue:0,value:" + value + "}};" ); 281 | var d = win.graphics; 282 | d.backgroundColor = d.newBrush(d.BrushType.SOLID_COLOR, [0.00, 0.00, 0.00, 1]); 283 | 284 | //Ask for variable vertexCount 285 | var vertexCount = prompt("How many vertices would you like? \n The more the detailed the output image is.", "100"); 286 | if (vertexCount == null || vertexCount == "") { 287 | return; 288 | } 289 | 290 | //Generated random distribution of vertices 291 | var vertices = new Array(Number(vertexCount)); 292 | for(var i = vertices.length; i--; ) { 293 | vertices[i] = [parseInt(Math.random() * docRef.width), parseInt(Math.random() * docRef.height)]; 294 | 295 | } 296 | 297 | //Triangulation returns an array of 3-tuples 298 | var triangulation = Delaunay.triangulate(vertices, null); 299 | win.progress.maxvalue = triangulation.length; 300 | 301 | for (var x = 0; x < triangulation.length; x += 3) { 302 | 303 | //Controls progress bar 304 | win.center(); 305 | win.show(); 306 | win.progress.value += 3; 307 | win.layout.layout(true); 308 | 309 | docRef.activeLayer = layerRef; 310 | 311 | try { 312 | 313 | //Create a selection given the triangle coordinates returned at this index in the triangulation array 314 | var response = [vertices[triangulation[x]], vertices[triangulation[x + 1]], vertices[triangulation[x + 2]]]; 315 | docRef.selection.select(response, SelectionType.REPLACE, 0, false); 316 | var selectionBounds = getSelectionBounds(docRef); 317 | 318 | //Checks that the selection is within bounds and valid 319 | if (selectionBounds[0] != 0 && selectionBounds[1] != 0 && selectionBounds[0] <= docRef.width && selectionBounds[1] <= docRef.height) { 320 | if (detectSelection()) { 321 | 322 | //Averages the colors in a region 323 | docRef.activeLayer.applyAverage(); 324 | } 325 | } 326 | } catch (err) {} 327 | } 328 | } 329 | 330 | mainWindow.center(); 331 | mainWindow.show(); 332 | 333 | function detectSelection() { 334 | // declare local variables 335 | var userHistory = activeDocument.activeHistoryState; 336 | var isSelection = false; 337 | 338 | // try Selection, Modify, Border and then undo if selection is detected 339 | try { 340 | activeDocument.selection.selectBorder(1); 341 | activeDocument.activeHistoryState = userHistory; 342 | isSelection = true; 343 | } 344 | // catch errors - no selection detected 345 | catch (e) {} 346 | return isSelection; 347 | } 348 | 349 | function getSelectionBounds (doc) { 350 | var l = doc.artLayers.add(); 351 | doc.selection.fill(app.foregroundColor); 352 | 353 | var bnds = l.bounds; 354 | var hs = doc.historyStates; 355 | 356 | if (hs[hs.length-2].name == "Layer Order") { 357 | doc.activeHistoryState = hs[hs.length-4]; 358 | } else { 359 | doc.activeHistoryState = hs[hs.length-3]; 360 | } 361 | 362 | for (var i = 0; i < bnds.length; i++) { 363 | bnds[i] = bnds[i].value; 364 | } 365 | return bnds; 366 | } 367 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | LowPolygonArt 2 | Generates low polygon background images 3 | 4 | **Please see https://digitalbunker.dev/2021/06/30/how-i-created-low-polygon-art/ for updated code in Python** 5 | 6 | Drag the .jsx file into Applications/Photoshop/Preset/Scripts and restart Photoshop. 7 | 8 | The script will now be availabe under File>Scripts 9 | 10 | ![DEMO](https://github.com/aryamansharda/LowPolygonArt/blob/master/Low%20Polygon%20Art%20Demo.gif) 11 | --------------------------------------------------------------------------------