├── .gitignore ├── img.jpg ├── index.html ├── sketch-maze-interactive-borders.js ├── sketch-maze-interactive-local.js ├── sketch-maze-interactive.js ├── sketch-maze-new.js ├── sketch-maze.js ├── sketch-radial.js ├── sketch-swap-new.js ├── sketch-swap.js ├── sketch.js └── variants ├── sketch.js ├── sketch2.js └── sketch3.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.jpg 2 | 3 | -------------------------------------------------------------------------------- /img.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgolid/pixel-sorting/35a01886de843125823a2d9d3bef8bd118c4ca47/img.jpg -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /sketch-maze-interactive-borders.js: -------------------------------------------------------------------------------- 1 | const maxWidth = 1200; 2 | const maxHeight = 800; 3 | const speed = 500; 4 | const expand_with_diagonals = false; 5 | const expansion_candidates = 500; 6 | const selection_size = 30; 7 | const reversed = false; 8 | const include_original = false; 9 | 10 | let nx, ny; 11 | let img, imgpixels; 12 | let candidates; 13 | let pixels_placed; 14 | 15 | let ready_for_processing; 16 | let front; 17 | 18 | let sketch = function (p) { 19 | p.setup = function () { 20 | const c = p.createCanvas(600, 600); 21 | ready_for_processing = false; 22 | 23 | p.fill(255); 24 | p.textSize(21); 25 | p.textAlign(p.CENTER); 26 | 27 | p.pixelDensity(1); 28 | c.drop(gotFile, fileDropped); 29 | c.dragOver(fileDropped); 30 | }; 31 | 32 | p.draw = function () { 33 | if (ready_for_processing) { 34 | if (pixels_placed < nx * ny - speed) placePixels(speed); 35 | else if (pixels_placed < nx * ny) placePixels(nx * ny - pixels_placed); 36 | } else { 37 | p.background('#888'); 38 | p.text('Drop an image here!', p.width / 2, p.height / 2); 39 | p.noLoop(); 40 | } 41 | }; 42 | 43 | function fileDropped() { 44 | ready_for_processing = false; 45 | } 46 | 47 | function gotFile(file) { 48 | if (file.type === 'image') { 49 | p.loadImage(file.data, imageLoaded); 50 | } else { 51 | console.log('Not an image file!'); 52 | } 53 | } 54 | 55 | function imageLoaded(img) { 56 | if (img.height / maxHeight > img.width / maxWidth) { 57 | if (img.height > maxHeight) img.resize(0, maxHeight); 58 | } else { 59 | if (img.width > maxWidth) img.resize(maxWidth, 0); 60 | } 61 | 62 | img.loadPixels(); 63 | 64 | nx = Math.floor(img.width); 65 | ny = Math.floor(img.height); 66 | p.resizeCanvas(nx, ny); 67 | 68 | originalpixels = newArray(ny).map((_, j) => 69 | newArray(nx).map((_, i) => { 70 | var loc = (i + j * img.width) * 4; 71 | return [img.pixels[loc + 0], img.pixels[loc + 1], img.pixels[loc + 2]]; 72 | }) 73 | ); 74 | 75 | imgpixels = newArray(ny).map((_, j) => 76 | newArray(nx).map((_, i) => { 77 | var loc = (i + j * img.width) * 4; 78 | return [img.pixels[loc + 0], img.pixels[loc + 1], img.pixels[loc + 2], i, j]; 79 | }) 80 | ); 81 | 82 | front = newArray(ny).map((_, j) => 83 | newArray(nx).map((_, i) => { 84 | return { bx: i, by: j, filled: false, adjacent: false, dist: -1, ndiff: -1 }; 85 | }) 86 | ); 87 | drawImage(imgpixels); 88 | 89 | const centre = [Math.floor(Math.random() * nx), Math.floor(Math.random() * ny)]; 90 | front[centre[1]][centre[0]] = { 91 | bx: centre[0], 92 | by: centre[1], 93 | filled: true, 94 | neighbor: true, 95 | dist: 0, 96 | ndiff: 0, 97 | }; 98 | 99 | drawPixel(centre); 100 | candidates = expandNeighborhood([centre], centre); 101 | 102 | pixels_placed = 1; 103 | ready_for_processing = true; 104 | p.loop(); 105 | } 106 | 107 | function drawImage(image) { 108 | image.forEach((pxr, j) => 109 | pxr.forEach((px, i) => { 110 | drawPixel([i, j], px); 111 | }) 112 | ); 113 | } 114 | 115 | function drawPixel(pos) { 116 | const col = colorInBack(pos); 117 | p.stroke(...col); 118 | p.point(...pos); 119 | } 120 | 121 | function placePixels(n) { 122 | for (i = 0; i < n; i++) { 123 | // Find expansion pixel and expand candidates accordingly. 124 | const currentPos = getNearest(candidates); 125 | candidates = expandNeighborhood(candidates, currentPos); 126 | 127 | // Find best matching pixel among selection. 128 | let origin = getSurroundingColor(currentPos); 129 | let cands = getRandoms(selection_size, pixels_placed, nx * ny); 130 | let best = findBestMatch(origin, cands); 131 | let frontBest = positionInFront(best); 132 | 133 | // Swap expansion pixel with matching pixel. 134 | swap_refs(currentPos[0], currentPos[1], frontBest[0], frontBest[1], false); 135 | 136 | front[currentPos[1]][currentPos[0]].filled = true; 137 | 138 | // Draw the swapped pixels. 139 | drawPixel(currentPos); 140 | drawPixel(frontBest); 141 | 142 | let row = Math.floor(pixels_placed / nx); 143 | let col = pixels_placed % nx; 144 | let frontScanPos = positionInFront([col, row]); 145 | 146 | // Swap in back array to consolidate (critical for performance). 147 | swap_refs(currentPos[0], currentPos[1], frontScanPos[0], frontScanPos[1], true); 148 | 149 | pixels_placed++; 150 | } 151 | } 152 | 153 | function findBestMatch(origin, candidates) { 154 | let best_idx = -1; 155 | let best_val = Number.MAX_VALUE; 156 | 157 | for (let i = 0; i < candidates.length; i++) { 158 | let yy = Math.floor(candidates[i] / nx); 159 | let xx = candidates[i] % nx; 160 | let val = compareCols(origin, imgpixels[yy][xx]); 161 | 162 | if (val < best_val) { 163 | best_val = val; 164 | best_idx = [xx, yy]; 165 | } 166 | } 167 | return best_idx; 168 | } 169 | 170 | function compareCols(a, b) { 171 | let dx = a[0] - b[0]; 172 | let dy = a[1] - b[1]; 173 | let dz = a[2] - b[2]; 174 | return p.sqrt(p.pow(dx, 2) + p.pow(dy, 2) + p.pow(dz, 2)); 175 | } 176 | 177 | p.keyPressed = function () { 178 | if (p.keyCode === 80) p.saveCanvas('sketch.jpeg'); 179 | }; 180 | 181 | // --- UTILS --- 182 | 183 | function positionInFront([x, y]) { 184 | return [imgpixels[y][x][3], imgpixels[y][x][4]]; 185 | } 186 | 187 | function colorInBack([x, y]) { 188 | let front_item = front[y][x]; 189 | return imgpixels[front_item.by][front_item.bx].slice(0, 3); 190 | } 191 | 192 | function swap_refs(px, py, rx, ry, swap_colors_in_back) { 193 | const pfront = { bx: front[py][px].bx, by: front[py][px].by }; 194 | const rfront = { bx: front[ry][rx].bx, by: front[ry][rx].by }; 195 | 196 | const pcol = imgpixels[pfront.by][pfront.bx].slice(0); 197 | const rcol = imgpixels[rfront.by][rfront.bx].slice(0); 198 | 199 | front[py][px].bx = rfront.bx; 200 | front[py][px].by = rfront.by; 201 | front[ry][rx].bx = pfront.bx; 202 | front[ry][rx].by = pfront.by; 203 | 204 | imgpixels[pfront.by][pfront.bx][3] = rcol[3]; 205 | imgpixels[pfront.by][pfront.bx][4] = rcol[4]; 206 | imgpixels[rfront.by][rfront.bx][3] = pcol[3]; 207 | imgpixels[rfront.by][rfront.bx][4] = pcol[4]; 208 | 209 | if (swap_colors_in_back) { 210 | imgpixels[pfront.by][pfront.bx] = rcol; 211 | imgpixels[rfront.by][rfront.bx] = pcol; 212 | } 213 | } 214 | 215 | function newArray(n, value) { 216 | n = n || 0; 217 | var array = new Array(n); 218 | for (var i = 0; i < n; i++) { 219 | array[i] = value; 220 | } 221 | return array; 222 | } 223 | 224 | function getRandoms(n, from, to) { 225 | let arr = []; 226 | while (arr.length < n) { 227 | let rand = Math.floor(p.random() * (to - from)) + from; 228 | arr.push(rand); 229 | } 230 | return arr; 231 | } 232 | 233 | function getNearest(rs) { 234 | const reverse = p.random() > 0.97 ? !reversed : reversed; 235 | let closest = reverse ? 0 : Number.MAX_VALUE; 236 | let closest_item = null; 237 | 238 | let sign = reverse ? -1 : 1; 239 | 240 | let selection = getRandoms(expansion_candidates, 0, rs.length - 1); 241 | selection.forEach((r) => { 242 | let dist = front[rs[r][1]][rs[r][0]].ndiff; 243 | if (sign * dist < sign * closest) { 244 | closest_item = rs[r]; 245 | closest = dist; 246 | } 247 | }); 248 | return closest_item; 249 | } 250 | 251 | function getAdjacentIndices(q, include_diagonals) { 252 | let indices = []; 253 | if (q[0] < nx - 1) indices.push([q[0] + 1, q[1]]); 254 | if (q[1] < ny - 1) indices.push([q[0], q[1] + 1]); 255 | if (q[0] > 0) indices.push([q[0] - 1, q[1]]); 256 | if (q[1] > 0) indices.push([q[0], q[1] - 1]); 257 | 258 | if (include_diagonals) { 259 | if (q[0] < nx - 1) { 260 | if (q[1] < ny - 1) indices.push([q[0] + 1, q[1] + 1]); 261 | if (q[1] > 0) indices.push([q[0] + 1, q[1] - 1]); 262 | } 263 | if (q[0] > 0) { 264 | if (q[1] < ny - 1) indices.push([q[0] - 1, q[1] + 1]); 265 | if (q[1] > 0) indices.push([q[0] - 1, q[1] - 1]); 266 | } 267 | } 268 | return indices; 269 | } 270 | 271 | function expandNeighborhood(neighborhood, next) { 272 | if (!neighborhood.includes(next)) { 273 | console.error('Next pixel is not from the neighboorhood', neighborhood); 274 | return neighborhood; 275 | } 276 | 277 | let expansion = getAdjacentIndices(next, expand_with_diagonals).filter( 278 | (pos) => !front[pos[1]][pos[0]].filled 279 | ); 280 | 281 | expansion.forEach((pos) => { 282 | front[pos[1]][pos[0]].dist = front[next[1]][next[0]].dist + 1; 283 | front[pos[1]][pos[0]].ndiff = compareCols( 284 | originalpixels[pos[1]][pos[0]], 285 | originalpixels[next[1]][next[0]] 286 | ); 287 | }); 288 | 289 | expansion = expansion.filter((pos) => !front[pos[1]][pos[0]].adjacent); 290 | expansion.forEach((pos) => { 291 | front[pos[1]][pos[0]].adjacent = true; 292 | }); 293 | 294 | const next_index = neighborhood.indexOf(next); 295 | neighborhood.splice(next_index, 1); 296 | 297 | return neighborhood.concat(expansion); 298 | } 299 | 300 | function getSurroundingColor(q) { 301 | const adj = getAdjacentIndices(q, true) 302 | .filter((pos) => front[pos[1]][pos[0]].filled) 303 | .map((ind) => colorInBack(ind)); 304 | if (include_original) adj.push(originalpixels[q[1]][q[0]]); 305 | return meanColor(adj); 306 | } 307 | 308 | function meanColor(arr) { 309 | let sx = arr.map((x) => x[0]).reduce((a, b) => a + b, 0); 310 | let sy = arr.map((x) => x[1]).reduce((a, b) => a + b, 0); 311 | let sz = arr.map((x) => x[2]).reduce((a, b) => a + b, 0); 312 | 313 | return [sx / arr.length, sy / arr.length, sz / arr.length]; 314 | } 315 | }; 316 | new p5(sketch); 317 | -------------------------------------------------------------------------------- /sketch-maze-interactive-local.js: -------------------------------------------------------------------------------- 1 | const maxWidth = 1200; 2 | const maxHeight = 800; 3 | const speed = 500; 4 | const expand_with_diagonals = false; 5 | const expansion_candidates = 30; 6 | const selection_size = 18; 7 | const reversed = false; 8 | const include_original = false; 9 | 10 | const local_swap = true; 11 | const max_swap_dist = 50; 12 | 13 | let nx, ny; 14 | let img, imgpixels; 15 | let candidates; 16 | let pixels_placed; 17 | 18 | let ready_for_processing; 19 | let front; 20 | 21 | let sketch = function (p) { 22 | p.setup = function () { 23 | const c = p.createCanvas(600, 600); 24 | ready_for_processing = false; 25 | 26 | p.fill(255); 27 | p.textSize(21); 28 | p.textAlign(p.CENTER); 29 | 30 | p.pixelDensity(1); 31 | c.drop(gotFile, fileDropped); 32 | c.dragOver(fileDropped); 33 | }; 34 | 35 | p.draw = function () { 36 | if (ready_for_processing) { 37 | if (pixels_placed < nx * ny - speed) placePixels(speed); 38 | else if (pixels_placed < nx * ny) placePixels(nx * ny - pixels_placed); 39 | } else { 40 | p.background('#888'); 41 | p.text('Drop an image here!', p.width / 2, p.height / 2); 42 | p.noLoop(); 43 | } 44 | }; 45 | 46 | function fileDropped() { 47 | ready_for_processing = false; 48 | } 49 | 50 | function gotFile(file) { 51 | if (file.type === 'image') { 52 | p.loadImage(file.data, imageLoaded); 53 | } else { 54 | console.log('Not an image file!'); 55 | } 56 | } 57 | 58 | function imageLoaded(img) { 59 | if (img.height / maxHeight > img.width / maxWidth) { 60 | if (img.height > maxHeight) img.resize(0, maxHeight); 61 | } else { 62 | if (img.width > maxWidth) img.resize(maxWidth, 0); 63 | } 64 | 65 | img.loadPixels(); 66 | 67 | nx = Math.floor(img.width); 68 | ny = Math.floor(img.height); 69 | p.resizeCanvas(nx, ny); 70 | 71 | originalpixels = newArray(ny).map((_, j) => 72 | newArray(nx).map((_, i) => { 73 | var loc = (i + j * img.width) * 4; 74 | return [img.pixels[loc + 0], img.pixels[loc + 1], img.pixels[loc + 2]]; 75 | }) 76 | ); 77 | 78 | imgpixels = newArray(ny).map((_, j) => 79 | newArray(nx).map((_, i) => { 80 | var loc = (i + j * img.width) * 4; 81 | return [img.pixels[loc + 0], img.pixels[loc + 1], img.pixels[loc + 2], i, j]; 82 | }) 83 | ); 84 | 85 | front = newArray(ny).map((_, j) => 86 | newArray(nx).map((_, i) => { 87 | return { bx: i, by: j, filled: false, adjacent: false, dist: -1, ndiff: -1 }; 88 | }) 89 | ); 90 | drawImage(imgpixels); 91 | 92 | const centre = [Math.floor(Math.random() * nx), Math.floor(Math.random() * ny)]; 93 | //const centre = [Math.floor(nx / 2), Math.floor(ny / 2)]; 94 | front[centre[1]][centre[0]] = { 95 | bx: centre[0], 96 | by: centre[1], 97 | filled: true, 98 | neighbor: true, 99 | dist: 0, 100 | ndiff: 0, 101 | }; 102 | 103 | drawPixel(centre); 104 | candidates = expandNeighborhood([centre], centre); 105 | 106 | pixels_placed = 1; 107 | ready_for_processing = true; 108 | p.loop(); 109 | } 110 | 111 | function drawImage(image) { 112 | image.forEach((pxr, j) => 113 | pxr.forEach((px, i) => { 114 | drawPixel([i, j], px); 115 | }) 116 | ); 117 | } 118 | 119 | function drawPixel(pos) { 120 | const col = colorInBack(pos); 121 | p.stroke(...col); 122 | p.point(...pos); 123 | } 124 | 125 | function placePixels(n) { 126 | for (i = 0; i < n; i++) { 127 | // Find expansion pixel and expand candidates accordingly. 128 | const currentPos = getNearest(candidates); 129 | candidates = expandNeighborhood(candidates, currentPos); 130 | 131 | // Find best matching pixel among selection. 132 | let origin = getSurroundingColor(currentPos); 133 | let cands = getRandoms(selection_size, pixels_placed, nx * ny, currentPos); 134 | let best = findBestMatch(origin, cands, currentPos); 135 | let frontBest = positionInFront(best); 136 | 137 | // Swap expansion pixel with matching pixel. 138 | swap_refs(currentPos[0], currentPos[1], frontBest[0], frontBest[1], false); 139 | 140 | front[currentPos[1]][currentPos[0]].filled = true; 141 | 142 | // Draw the swapped pixels. 143 | drawPixel(currentPos); 144 | drawPixel(frontBest); 145 | 146 | let row = Math.floor(pixels_placed / nx); 147 | let col = pixels_placed % nx; 148 | let frontScanPos = positionInFront([col, row]); 149 | 150 | // Swap in back array to consolidate (critical for performance). 151 | swap_refs(currentPos[0], currentPos[1], frontScanPos[0], frontScanPos[1], true); 152 | 153 | pixels_placed++; 154 | } 155 | } 156 | 157 | function findBestMatch(origin, candidates) { 158 | let best_idx = -1; 159 | let best_val = Number.MAX_VALUE; 160 | 161 | for (let i = 0; i < candidates.length; i++) { 162 | let yy = Math.floor(candidates[i] / nx); 163 | let xx = candidates[i] % nx; 164 | let val = compareCols(origin, imgpixels[yy][xx]); 165 | 166 | if (val < best_val) { 167 | best_val = val; 168 | best_idx = [xx, yy]; 169 | } 170 | } 171 | return best_idx; 172 | } 173 | 174 | function compareCols(a, b) { 175 | let dx = a[0] - b[0]; 176 | let dy = a[1] - b[1]; 177 | let dz = a[2] - b[2]; 178 | return p.sqrt(p.pow(dx, 2) + p.pow(dy, 2) + p.pow(dz, 2)); 179 | } 180 | 181 | p.keyPressed = function () { 182 | if (p.keyCode === 80) p.saveCanvas('sketch_', 'jpeg'); 183 | }; 184 | 185 | // --- UTILS --- 186 | 187 | function positionInFront([x, y]) { 188 | return [imgpixels[y][x][3], imgpixels[y][x][4]]; 189 | } 190 | 191 | function colorInBack([x, y]) { 192 | let front_item = front[y][x]; 193 | return imgpixels[front_item.by][front_item.bx].slice(0, 3); 194 | } 195 | 196 | function swap_refs(px, py, rx, ry, swap_colors_in_back) { 197 | const pfront = { bx: front[py][px].bx, by: front[py][px].by }; 198 | const rfront = { bx: front[ry][rx].bx, by: front[ry][rx].by }; 199 | 200 | const pcol = imgpixels[pfront.by][pfront.bx].slice(0); 201 | const rcol = imgpixels[rfront.by][rfront.bx].slice(0); 202 | 203 | front[py][px].bx = rfront.bx; 204 | front[py][px].by = rfront.by; 205 | front[ry][rx].bx = pfront.bx; 206 | front[ry][rx].by = pfront.by; 207 | 208 | imgpixels[pfront.by][pfront.bx][3] = rcol[3]; 209 | imgpixels[pfront.by][pfront.bx][4] = rcol[4]; 210 | imgpixels[rfront.by][rfront.bx][3] = pcol[3]; 211 | imgpixels[rfront.by][rfront.bx][4] = pcol[4]; 212 | 213 | if (swap_colors_in_back) { 214 | imgpixels[pfront.by][pfront.bx] = rcol; 215 | imgpixels[rfront.by][rfront.bx] = pcol; 216 | } 217 | } 218 | 219 | function newArray(n, value) { 220 | n = n || 0; 221 | var array = new Array(n); 222 | for (var i = 0; i < n; i++) { 223 | array[i] = value; 224 | } 225 | return array; 226 | } 227 | 228 | function getRandoms(n, from, to, cpos) { 229 | let arr = []; 230 | let tick = 0; 231 | while (arr.length < n) { 232 | let rand = Math.floor(p.random() * (to - from)) + from; 233 | 234 | if (local_swap && cpos != null) { 235 | let yy = Math.floor(rand / nx); 236 | let xx = rand % nx; 237 | var pos = [imgpixels[yy][xx][3], imgpixels[yy][xx][4]]; 238 | 239 | if (dist(pos, cpos) < max_swap_dist || tick > 6000) arr.push(rand); 240 | tick++; 241 | } else { 242 | arr.push(rand); 243 | } 244 | } 245 | return arr; 246 | } 247 | 248 | function getNearest(rs) { 249 | //const reverse = p.random() > 0.97 ? !reversed : reversed; 250 | let closest = reversed ? 0 : Number.MAX_VALUE; 251 | let closest_item = null; 252 | 253 | let sign = reversed ? -1 : 1; 254 | 255 | let selection = getRandoms(expansion_candidates, 0, rs.length - 1); 256 | selection.forEach((r) => { 257 | let dist = front[rs[r][1]][rs[r][0]].ndiff; 258 | if (sign * dist < sign * closest) { 259 | closest_item = rs[r]; 260 | closest = dist; 261 | } 262 | }); 263 | return closest_item; 264 | } 265 | 266 | function getAdjacentIndices(q, include_diagonals) { 267 | let indices = []; 268 | if (q[0] < nx - 1) indices.push([q[0] + 1, q[1]]); 269 | if (q[1] < ny - 1) indices.push([q[0], q[1] + 1]); 270 | if (q[0] > 0) indices.push([q[0] - 1, q[1]]); 271 | if (q[1] > 0) indices.push([q[0], q[1] - 1]); 272 | 273 | if (include_diagonals) { 274 | if (q[0] < nx - 1) { 275 | if (q[1] < ny - 1) indices.push([q[0] + 1, q[1] + 1]); 276 | if (q[1] > 0) indices.push([q[0] + 1, q[1] - 1]); 277 | } 278 | if (q[0] > 0) { 279 | if (q[1] < ny - 1) indices.push([q[0] - 1, q[1] + 1]); 280 | if (q[1] > 0) indices.push([q[0] - 1, q[1] - 1]); 281 | } 282 | } 283 | return indices; 284 | } 285 | 286 | function expandNeighborhood(neighborhood, next) { 287 | if (!neighborhood.includes(next)) { 288 | console.error('Next pixel is not from the neighboorhood', neighborhood); 289 | return neighborhood; 290 | } 291 | 292 | let expansion = getAdjacentIndices(next, expand_with_diagonals).filter( 293 | (pos) => !front[pos[1]][pos[0]].filled 294 | ); 295 | 296 | expansion.forEach((pos) => { 297 | front[pos[1]][pos[0]].dist = front[next[1]][next[0]].dist + 1; 298 | front[pos[1]][pos[0]].ndiff = compareCols( 299 | originalpixels[pos[1]][pos[0]], 300 | originalpixels[next[1]][next[0]] 301 | ); 302 | }); 303 | 304 | expansion = expansion.filter((pos) => !front[pos[1]][pos[0]].adjacent); 305 | expansion.forEach((pos) => { 306 | front[pos[1]][pos[0]].adjacent = true; 307 | }); 308 | 309 | const next_index = neighborhood.indexOf(next); 310 | neighborhood.splice(next_index, 1); 311 | 312 | return neighborhood.concat(expansion); 313 | } 314 | 315 | function getSurroundingColor(q) { 316 | const adj = getAdjacentIndices(q, true) 317 | .filter((pos) => front[pos[1]][pos[0]].filled) 318 | .map((ind) => colorInBack(ind)); 319 | if (include_original) adj.push(originalpixels[q[1]][q[0]]); 320 | return meanColor(adj); 321 | } 322 | 323 | function meanColor(arr) { 324 | let sx = arr.map((x) => x[0]).reduce((a, b) => a + b, 0); 325 | let sy = arr.map((x) => x[1]).reduce((a, b) => a + b, 0); 326 | let sz = arr.map((x) => x[2]).reduce((a, b) => a + b, 0); 327 | 328 | return [sx / arr.length, sy / arr.length, sz / arr.length]; 329 | } 330 | 331 | function dist(a, b) { 332 | return Math.sqrt(Math.pow(b[0] - a[0], 2) + Math.pow(b[1] - a[1], 2)); 333 | } 334 | }; 335 | new p5(sketch); 336 | -------------------------------------------------------------------------------- /sketch-maze-interactive.js: -------------------------------------------------------------------------------- 1 | const maxWidth = 1200; 2 | const maxHeight = 800; 3 | const speed = 500; 4 | const expand_with_diagonals = false; 5 | const expansion_candidates = 10; 6 | const selection_size = 50; 7 | const reversed = true; 8 | const include_original = false; 9 | 10 | let nx, ny; 11 | let img, imgpixels; 12 | let candidates; 13 | let pixels_placed; 14 | 15 | let ready_for_processing; 16 | let front; 17 | 18 | let sketch = function(p) { 19 | p.setup = function() { 20 | const c = p.createCanvas(600, 600); 21 | ready_for_processing = false; 22 | 23 | p.fill(255); 24 | p.textSize(21); 25 | p.textAlign(p.CENTER); 26 | 27 | p.pixelDensity(1); 28 | c.drop(gotFile, fileDropped); 29 | c.dragOver(fileDropped); 30 | }; 31 | 32 | p.draw = function() { 33 | if (ready_for_processing) { 34 | if (pixels_placed < nx * ny - speed) placePixels(speed); 35 | else if (pixels_placed < nx * ny) placePixels(nx * ny - pixels_placed); 36 | } else { 37 | p.background('#888'); 38 | p.text('Drop an image here!', p.width / 2, p.height / 2); 39 | p.noLoop(); 40 | } 41 | }; 42 | 43 | function fileDropped() { 44 | ready_for_processing = false; 45 | } 46 | 47 | function gotFile(file) { 48 | if (file.type === 'image') { 49 | p.loadImage(file.data, imageLoaded); 50 | } else { 51 | console.log('Not an image file!'); 52 | } 53 | } 54 | 55 | function imageLoaded(img) { 56 | if (img.height / maxHeight > img.width / maxWidth) { 57 | if (img.height > maxHeight) img.resize(0, maxHeight); 58 | } else { 59 | if (img.width > maxWidth) img.resize(maxWidth, 0); 60 | } 61 | 62 | img.loadPixels(); 63 | 64 | nx = Math.floor(img.width); 65 | ny = Math.floor(img.height); 66 | p.resizeCanvas(nx, ny); 67 | 68 | originalpixels = newArray(ny).map((_, j) => 69 | newArray(nx).map((_, i) => { 70 | var loc = (i + j * img.width) * 4; 71 | return [img.pixels[loc + 0], img.pixels[loc + 1], img.pixels[loc + 2]]; 72 | }) 73 | ); 74 | 75 | imgpixels = newArray(ny).map((_, j) => 76 | newArray(nx).map((_, i) => { 77 | var loc = (i + j * img.width) * 4; 78 | return [ 79 | img.pixels[loc + 0], 80 | img.pixels[loc + 1], 81 | img.pixels[loc + 2], 82 | i, 83 | j 84 | ]; 85 | }) 86 | ); 87 | 88 | front = newArray(ny).map((_, j) => 89 | newArray(nx).map((_, i) => { 90 | return { bx: i, by: j, filled: false, adjacent: false, dist: -1 }; 91 | }) 92 | ); 93 | drawImage(imgpixels); 94 | 95 | const centre = [ 96 | Math.floor(Math.random() * nx), 97 | Math.floor(Math.random() * ny) 98 | ]; 99 | front[centre[1]][centre[0]] = { 100 | bx: centre[0], 101 | by: centre[1], 102 | filled: true, 103 | neighbor: true, 104 | dist: 0 105 | }; 106 | 107 | drawPixel(centre); 108 | candidates = expandNeighborhood([centre], centre); 109 | 110 | pixels_placed = 1; 111 | ready_for_processing = true; 112 | p.loop(); 113 | } 114 | 115 | function drawImage(image) { 116 | image.forEach((pxr, j) => 117 | pxr.forEach((px, i) => { 118 | drawPixel([i, j], px); 119 | }) 120 | ); 121 | } 122 | 123 | function drawPixel(pos) { 124 | const col = colorInBack(pos); 125 | p.stroke(...col); 126 | p.point(...pos); 127 | } 128 | 129 | function placePixels(n) { 130 | for (i = 0; i < n; i++) { 131 | // Find expansion pixel and expand candidates accordingly. 132 | const currentPos = getNearest(candidates); 133 | candidates = expandNeighborhood(candidates, currentPos); 134 | 135 | // Find best matching pixel among selection. 136 | let origin = getSurroundingColor(currentPos); 137 | let cands = getRandoms(selection_size, pixels_placed, nx * ny); 138 | let best = findBestMatch(origin, cands); 139 | let frontBest = positionInFront(best); 140 | 141 | // Swap expansion pixel with matching pixel. 142 | swap_refs( 143 | currentPos[0], 144 | currentPos[1], 145 | frontBest[0], 146 | frontBest[1], 147 | false 148 | ); 149 | 150 | front[currentPos[1]][currentPos[0]].filled = true; 151 | 152 | // Draw the swapped pixels. 153 | drawPixel(currentPos); 154 | drawPixel(frontBest); 155 | 156 | let row = Math.floor(pixels_placed / nx); 157 | let col = pixels_placed % nx; 158 | let frontScanPos = positionInFront([col, row]); 159 | 160 | // Swap in back array to consolidate (critical for performance). 161 | swap_refs( 162 | currentPos[0], 163 | currentPos[1], 164 | frontScanPos[0], 165 | frontScanPos[1], 166 | true 167 | ); 168 | 169 | pixels_placed++; 170 | } 171 | } 172 | 173 | function findBestMatch(origin, candidates) { 174 | let best_idx = -1; 175 | let best_val = Number.MAX_VALUE; 176 | 177 | for (let i = 0; i < candidates.length; i++) { 178 | let yy = Math.floor(candidates[i] / nx); 179 | let xx = candidates[i] % nx; 180 | let val = compareCols(origin, imgpixels[yy][xx]); 181 | 182 | if (val < best_val) { 183 | best_val = val; 184 | best_idx = [xx, yy]; 185 | } 186 | } 187 | return best_idx; 188 | } 189 | 190 | function compareCols(a, b) { 191 | let dx = a[0] - b[0]; 192 | let dy = a[1] - b[1]; 193 | let dz = a[2] - b[2]; 194 | return p.sqrt(p.pow(dx, 2) + p.pow(dy, 2) + p.pow(dz, 2)); 195 | } 196 | 197 | p.keyPressed = function() { 198 | if (p.keyCode === 80) p.saveCanvas('sketch_' + THE_SEED, 'jpeg'); 199 | }; 200 | 201 | // --- UTILS --- 202 | 203 | function positionInFront([x, y]) { 204 | return [imgpixels[y][x][3], imgpixels[y][x][4]]; 205 | } 206 | 207 | function colorInBack([x, y]) { 208 | let front_item = front[y][x]; 209 | return imgpixels[front_item.by][front_item.bx].slice(0, 3); 210 | } 211 | 212 | function swap_refs(px, py, rx, ry, swap_colors_in_back) { 213 | const pfront = { bx: front[py][px].bx, by: front[py][px].by }; 214 | const rfront = { bx: front[ry][rx].bx, by: front[ry][rx].by }; 215 | 216 | const pcol = imgpixels[pfront.by][pfront.bx].slice(0); 217 | const rcol = imgpixels[rfront.by][rfront.bx].slice(0); 218 | 219 | front[py][px].bx = rfront.bx; 220 | front[py][px].by = rfront.by; 221 | front[ry][rx].bx = pfront.bx; 222 | front[ry][rx].by = pfront.by; 223 | 224 | imgpixels[pfront.by][pfront.bx][3] = rcol[3]; 225 | imgpixels[pfront.by][pfront.bx][4] = rcol[4]; 226 | imgpixels[rfront.by][rfront.bx][3] = pcol[3]; 227 | imgpixels[rfront.by][rfront.bx][4] = pcol[4]; 228 | 229 | if (swap_colors_in_back) { 230 | imgpixels[pfront.by][pfront.bx] = rcol; 231 | imgpixels[rfront.by][rfront.bx] = pcol; 232 | } 233 | } 234 | 235 | function newArray(n, value) { 236 | n = n || 0; 237 | var array = new Array(n); 238 | for (var i = 0; i < n; i++) { 239 | array[i] = value; 240 | } 241 | return array; 242 | } 243 | 244 | function getRandoms(n, from, to) { 245 | let arr = []; 246 | while (arr.length < n) { 247 | let rand = Math.floor(p.random() * (to - from)) + from; 248 | arr.push(rand); 249 | } 250 | return arr; 251 | } 252 | 253 | function getNearest(rs) { 254 | const reverse = p.random() > 0.97 ? !reversed : reversed; 255 | let closest = reverse ? 0 : Number.MAX_VALUE; 256 | let closest_item = null; 257 | 258 | let sign = reverse ? -1 : 1; 259 | 260 | let selection = getRandoms(expansion_candidates, 0, rs.length - 1); 261 | selection.forEach(r => { 262 | let dist = front[rs[r][1]][rs[r][0]].dist; 263 | if (sign * dist < sign * closest) { 264 | closest_item = rs[r]; 265 | closest = dist; 266 | } 267 | }); 268 | return closest_item; 269 | } 270 | 271 | function getAdjacentIndices(q, include_diagonals) { 272 | let indices = []; 273 | if (q[0] < nx - 1) indices.push([q[0] + 1, q[1]]); 274 | if (q[1] < ny - 1) indices.push([q[0], q[1] + 1]); 275 | if (q[0] > 0) indices.push([q[0] - 1, q[1]]); 276 | if (q[1] > 0) indices.push([q[0], q[1] - 1]); 277 | 278 | if (include_diagonals) { 279 | if (q[0] < nx - 1) { 280 | if (q[1] < ny - 1) indices.push([q[0] + 1, q[1] + 1]); 281 | if (q[1] > 0) indices.push([q[0] + 1, q[1] - 1]); 282 | } 283 | if (q[0] > 0) { 284 | if (q[1] < ny - 1) indices.push([q[0] - 1, q[1] + 1]); 285 | if (q[1] > 0) indices.push([q[0] - 1, q[1] - 1]); 286 | } 287 | } 288 | return indices; 289 | } 290 | 291 | function expandNeighborhood(neighborhood, next) { 292 | if (!neighborhood.includes(next)) { 293 | console.error('Next pixel is not from the neighboorhood', neighborhood); 294 | return neighborhood; 295 | } 296 | 297 | let expansion = getAdjacentIndices(next, expand_with_diagonals).filter( 298 | pos => !front[pos[1]][pos[0]].filled 299 | ); 300 | 301 | expansion.forEach(pos => { 302 | front[pos[1]][pos[0]].dist = front[next[1]][next[0]].dist + 1; 303 | }); 304 | 305 | expansion = expansion.filter(pos => !front[pos[1]][pos[0]].adjacent); 306 | expansion.forEach(pos => { 307 | front[pos[1]][pos[0]].adjacent = true; 308 | }); 309 | 310 | const next_index = neighborhood.indexOf(next); 311 | neighborhood.splice(next_index, 1); 312 | 313 | return neighborhood.concat(expansion); 314 | } 315 | 316 | function getSurroundingColor(q) { 317 | const adj = getAdjacentIndices(q, true) 318 | .filter(pos => front[pos[1]][pos[0]].filled) 319 | .map(ind => colorInBack(ind)); 320 | if (include_original) adj.push(originalpixels[q[1]][q[0]]); 321 | return meanColor(adj); 322 | } 323 | 324 | function meanColor(arr) { 325 | let sx = arr.map(x => x[0]).reduce((a, b) => a + b, 0); 326 | let sy = arr.map(x => x[1]).reduce((a, b) => a + b, 0); 327 | let sz = arr.map(x => x[2]).reduce((a, b) => a + b, 0); 328 | 329 | return [sx / arr.length, sy / arr.length, sz / arr.length]; 330 | } 331 | }; 332 | new p5(sketch); 333 | -------------------------------------------------------------------------------- /sketch-maze-new.js: -------------------------------------------------------------------------------- 1 | let nx; 2 | let ny; 3 | let size = 1; 4 | let img_resolution = 4; 5 | let speed = 500; 6 | 7 | let expand_with_diagonals = false; 8 | let expansion_candidates = 10; 9 | let selection_size = 50; 10 | let reversed = true; 11 | let include_original = false; 12 | 13 | let img, imgpixels; 14 | let candidates; 15 | let pixels_placed; 16 | 17 | let front; 18 | 19 | let sketch = function(p) { 20 | let THE_SEED; 21 | 22 | p.preload = function() { 23 | img = p.loadImage( 24 | 'https://raw.githubusercontent.com/kgolid/pixel-sorting/master/img.jpg' 25 | ); 26 | }; 27 | 28 | p.setup = function() { 29 | nx = Math.floor(img.width / img_resolution); 30 | ny = Math.floor(img.height / img_resolution); 31 | p.createCanvas(nx * size, ny * size); 32 | p.pixelDensity(1); 33 | img.loadPixels(); 34 | p.loadPixels(); 35 | 36 | THE_SEED = p.floor(p.random(9999999)); 37 | console.log('Seed: ', THE_SEED); 38 | p.randomSeed(THE_SEED); 39 | p.noStroke(); 40 | 41 | originalpixels = newArray(ny).map((_, j) => 42 | newArray(nx).map((_, i) => { 43 | var loc = (i + j * img.width) * 4 * img_resolution; 44 | return [img.pixels[loc + 0], img.pixels[loc + 1], img.pixels[loc + 2]]; 45 | }) 46 | ); 47 | 48 | imgpixels = newArray(ny).map((_, j) => 49 | newArray(nx).map((_, i) => { 50 | var loc = (i + j * img.width) * 4 * img_resolution; 51 | return [ 52 | img.pixels[loc + 0], 53 | img.pixels[loc + 1], 54 | img.pixels[loc + 2], 55 | i, 56 | j 57 | ]; 58 | }) 59 | ); 60 | 61 | front = newArray(ny).map((_, j) => 62 | newArray(nx).map((_, i) => { 63 | return { bx: i, by: j, filled: false, adjacent: false, dist: -1 }; 64 | }) 65 | ); 66 | drawImage(imgpixels); 67 | 68 | const centre = [Math.floor(nx / 2), Math.floor(ny / 2)]; 69 | front[centre[1]][centre[0]] = { 70 | bx: centre[0], 71 | by: centre[1], 72 | filled: true, 73 | neighbor: true, 74 | dist: 0 75 | }; 76 | 77 | drawPixel(centre); 78 | candidates = expandNeighborhood([centre], centre); 79 | 80 | pixels_placed = 1; 81 | }; 82 | 83 | p.draw = function() { 84 | if (pixels_placed < nx * ny - speed) placePixels(speed); 85 | else if (pixels_placed < nx * ny) placePixels(nx * ny - pixels_placed); 86 | }; 87 | 88 | function drawImage(image) { 89 | image.forEach((pxr, j) => 90 | pxr.forEach((px, i) => { 91 | drawPixel([i, j], px); 92 | }) 93 | ); 94 | } 95 | 96 | function drawPixel(pos) { 97 | const col = colorInBack(pos); 98 | p.fill(col[0], col[1], col[2]); 99 | p.rect(size * pos[0], size * pos[1], size, size); 100 | } 101 | 102 | function placePixels(n) { 103 | for (i = 0; i < n; i++) { 104 | // Find expansion pixel and expand candidates accordingly. 105 | const currentPos = getNearest(candidates); 106 | candidates = expandNeighborhood(candidates, currentPos); 107 | 108 | // Find best matching pixel among selection. 109 | let origin = getSurroundingColor(currentPos); 110 | let cands = getRandoms(selection_size, pixels_placed, nx * ny); 111 | let best = findBestMatch(origin, cands); 112 | let frontBest = positionInFront(best); 113 | 114 | // Swap expansion pixel with matching pixel. 115 | swap_refs( 116 | currentPos[0], 117 | currentPos[1], 118 | frontBest[0], 119 | frontBest[1], 120 | false 121 | ); 122 | 123 | front[currentPos[1]][currentPos[0]].filled = true; 124 | 125 | // Draw the swapped pixels. 126 | drawPixel(currentPos); 127 | drawPixel(frontBest); 128 | 129 | let row = Math.floor(pixels_placed / nx); 130 | let col = pixels_placed % nx; 131 | let frontScanPos = positionInFront([col, row]); 132 | 133 | // Swap in back array to consolidate (critical for performance). 134 | swap_refs( 135 | currentPos[0], 136 | currentPos[1], 137 | frontScanPos[0], 138 | frontScanPos[1], 139 | true 140 | ); 141 | 142 | pixels_placed++; 143 | } 144 | } 145 | 146 | function findBestMatch(origin, candidates) { 147 | let best_idx = -1; 148 | let best_val = Number.MAX_VALUE; 149 | 150 | for (let i = 0; i < candidates.length; i++) { 151 | let yy = Math.floor(candidates[i] / nx); 152 | let xx = candidates[i] % nx; 153 | let val = compareCols(origin, imgpixels[yy][xx]); 154 | 155 | if (val < best_val) { 156 | best_val = val; 157 | best_idx = [xx, yy]; 158 | } 159 | } 160 | return best_idx; 161 | } 162 | 163 | function compareCols(a, b) { 164 | let dx = a[0] - b[0]; 165 | let dy = a[1] - b[1]; 166 | let dz = a[2] - b[2]; 167 | return p.sqrt(p.pow(dx, 2) + p.pow(dy, 2) + p.pow(dz, 2)); 168 | } 169 | 170 | p.keyPressed = function() { 171 | if (p.keyCode === 80) p.saveCanvas('sketch_' + THE_SEED, 'jpeg'); 172 | }; 173 | 174 | // --- UTILS --- 175 | 176 | function positionInFront([x, y]) { 177 | return [imgpixels[y][x][3], imgpixels[y][x][4]]; 178 | } 179 | 180 | function colorInBack([x, y]) { 181 | let front_item = front[y][x]; 182 | return imgpixels[front_item.by][front_item.bx]; 183 | } 184 | 185 | function swap_refs(px, py, rx, ry, swap_colors_in_back) { 186 | const pfront = { bx: front[py][px].bx, by: front[py][px].by }; 187 | const rfront = { bx: front[ry][rx].bx, by: front[ry][rx].by }; 188 | 189 | const pcol = imgpixels[pfront.by][pfront.bx].slice(0); 190 | const rcol = imgpixels[rfront.by][rfront.bx].slice(0); 191 | 192 | front[py][px].bx = rfront.bx; 193 | front[py][px].by = rfront.by; 194 | front[ry][rx].bx = pfront.bx; 195 | front[ry][rx].by = pfront.by; 196 | 197 | imgpixels[pfront.by][pfront.bx][3] = rcol[3]; 198 | imgpixels[pfront.by][pfront.bx][4] = rcol[4]; 199 | imgpixels[rfront.by][rfront.bx][3] = pcol[3]; 200 | imgpixels[rfront.by][rfront.bx][4] = pcol[4]; 201 | 202 | if (swap_colors_in_back) { 203 | imgpixels[pfront.by][pfront.bx] = rcol; 204 | imgpixels[rfront.by][rfront.bx] = pcol; 205 | } 206 | } 207 | 208 | function newArray(n, value) { 209 | n = n || 0; 210 | var array = new Array(n); 211 | for (var i = 0; i < n; i++) { 212 | array[i] = value; 213 | } 214 | return array; 215 | } 216 | 217 | function getRandoms(n, from, to) { 218 | let arr = []; 219 | while (arr.length < n) { 220 | let rand = Math.floor(p.random() * (to - from)) + from; 221 | arr.push(rand); 222 | } 223 | return arr; 224 | } 225 | 226 | function getNearest(rs) { 227 | const reverse = p.random() > 0.97 ? !reversed : reversed; 228 | let closest = reverse ? 0 : Number.MAX_VALUE; 229 | let closest_item = null; 230 | 231 | let sign = reverse ? -1 : 1; 232 | 233 | let selection = getRandoms(expansion_candidates, 0, rs.length - 1); 234 | selection.forEach(r => { 235 | let dist = front[rs[r][1]][rs[r][0]].dist; 236 | if (sign * dist < sign * closest) { 237 | closest_item = rs[r]; 238 | closest = dist; 239 | } 240 | }); 241 | return closest_item; 242 | } 243 | 244 | function getAdjacentIndices(q, include_diagonals) { 245 | let indices = []; 246 | if (q[0] < nx - 1) indices.push([q[0] + 1, q[1]]); 247 | if (q[1] < ny - 1) indices.push([q[0], q[1] + 1]); 248 | if (q[0] > 0) indices.push([q[0] - 1, q[1]]); 249 | if (q[1] > 0) indices.push([q[0], q[1] - 1]); 250 | 251 | if (include_diagonals) { 252 | if (q[0] < nx - 1) { 253 | if (q[1] < ny - 1) indices.push([q[0] + 1, q[1] + 1]); 254 | if (q[1] > 0) indices.push([q[0] + 1, q[1] - 1]); 255 | } 256 | if (q[0] > 0) { 257 | if (q[1] < ny - 1) indices.push([q[0] - 1, q[1] + 1]); 258 | if (q[1] > 0) indices.push([q[0] - 1, q[1] - 1]); 259 | } 260 | } 261 | return indices; 262 | } 263 | 264 | function expandNeighborhood(neighborhood, next) { 265 | if (!neighborhood.includes(next)) { 266 | console.error('Next pixel is not from the neighboorhood', neighborhood); 267 | return neighborhood; 268 | } 269 | 270 | let expansion = getAdjacentIndices(next, expand_with_diagonals).filter( 271 | pos => !front[pos[1]][pos[0]].filled 272 | ); 273 | 274 | expansion.forEach(pos => { 275 | front[pos[1]][pos[0]].dist = front[next[1]][next[0]].dist + 1; 276 | }); 277 | 278 | expansion = expansion.filter(pos => !front[pos[1]][pos[0]].adjacent); 279 | expansion.forEach(pos => { 280 | front[pos[1]][pos[0]].adjacent = true; 281 | }); 282 | 283 | const next_index = neighborhood.indexOf(next); 284 | neighborhood.splice(next_index, 1); 285 | 286 | return neighborhood.concat(expansion); 287 | } 288 | 289 | function getSurroundingColor(q) { 290 | const adj = getAdjacentIndices(q, true) 291 | .filter(pos => front[pos[1]][pos[0]].filled) 292 | .map(ind => colorInBack(ind)); 293 | if (include_original) adj.push(originalpixels[q[1]][q[0]]); 294 | return meanColor(adj); 295 | } 296 | 297 | function meanColor(arr) { 298 | let sx = arr.map(x => x[0]).reduce((a, b) => a + b, 0); 299 | let sy = arr.map(x => x[1]).reduce((a, b) => a + b, 0); 300 | let sz = arr.map(x => x[2]).reduce((a, b) => a + b, 0); 301 | 302 | return [sx / arr.length, sy / arr.length, sz / arr.length]; 303 | } 304 | 305 | function union(a, b) { 306 | let c = [...b]; 307 | a.forEach(x => { 308 | if (!b.some(y => eq(x, y))) c.push(x); 309 | }); 310 | return c; 311 | } 312 | 313 | function eq(a, b) { 314 | return a[0] === b[0] && a[1] === b[1]; 315 | } 316 | }; 317 | new p5(sketch); 318 | -------------------------------------------------------------------------------- /sketch-maze.js: -------------------------------------------------------------------------------- 1 | let nx = 512; 2 | let ny = 512; 3 | let size = 2; 4 | let img_resolution = 5; 5 | 6 | let expansion_candidates = 8; 7 | let selection_size = 500; 8 | let reversed = true; 9 | let include_original = true; 10 | 11 | let tick = 0; 12 | let img, imgpixels; 13 | let result; 14 | let candidates; 15 | let pixels_placed; 16 | 17 | let sketch = function(p) { 18 | let THE_SEED; 19 | 20 | p.preload = function() { 21 | img = p.loadImage( 22 | 'https://raw.githubusercontent.com/kgolid/pixel-sorting/master/img.jpg' 23 | ); 24 | }; 25 | 26 | p.setup = function() { 27 | p.createCanvas(nx * size, nx * size); 28 | p.pixelDensity(1); 29 | img.loadPixels(); 30 | p.loadPixels(); 31 | 32 | THE_SEED = p.floor(p.random(9999999)); 33 | p.randomSeed(THE_SEED); 34 | p.noStroke(); 35 | 36 | originalpixels = newArray(ny).map((_, j) => 37 | newArray(nx).map((_, i) => { 38 | var loc = (i + j * img.width) * 4 * img_resolution; 39 | return [img.pixels[loc + 0], img.pixels[loc + 1], img.pixels[loc + 2]]; 40 | }) 41 | ); 42 | 43 | imgpixels = newArray(ny).map((_, j) => 44 | newArray(nx).map((_, i) => { 45 | var loc = (i + j * img.width) * 4 * img_resolution; 46 | return [img.pixels[loc + 0], img.pixels[loc + 1], img.pixels[loc + 2]]; 47 | }) 48 | ); 49 | result = newArray(ny).map(() => newArray(nx, null)); 50 | 51 | const centre = [Math.floor(nx / 2), Math.floor(ny / 2), 0]; 52 | const start = [ 53 | ...imgpixels[Math.floor(Math.random() * nx)][ 54 | Math.floor(Math.random() * ny) 55 | ] 56 | ]; 57 | result[centre[0]][centre[1]] = start; 58 | 59 | drawPixel([centre[0], centre[1]], start); 60 | candidates = expandNeighborhood([centre], centre); 61 | 62 | pixels_placed = 0; 63 | 64 | //drawImage(imgpixels); 65 | }; 66 | 67 | p.draw = function() { 68 | if (pixels_placed < nx * ny - 500) placePixels(500); 69 | else if (pixels_placed < nx * ny) placePixels(nx * ny - pixels_placed - 1); 70 | }; 71 | 72 | function drawImage(image) { 73 | image.forEach((pxr, j) => 74 | pxr.forEach((px, i) => { 75 | drawPixel([j, i], px); 76 | }) 77 | ); 78 | } 79 | 80 | function drawPixel(pos, col) { 81 | p.fill(col[0], col[1], col[2]); 82 | p.rect(size * pos[1], size * pos[0], size, size); 83 | } 84 | 85 | function placePixels(n) { 86 | for (i = 0; i < n; i++) { 87 | const resultPos = getNearest( 88 | [Math.floor(nx / 2), Math.floor(ny / 2)], 89 | candidates, 90 | reversed 91 | ); 92 | 93 | let origin = getSurroundingColor(resultPos); 94 | let cands = getRandoms(selection_size, pixels_placed, nx * ny); 95 | let bi = findBestMatch(origin, cands); 96 | 97 | candidates = expandNeighborhood(candidates, resultPos); 98 | 99 | let pixel_to_swap = [...imgpixels[bi[0]][bi[1]]]; 100 | 101 | let row = Math.floor(pixels_placed / nx); 102 | let col = pixels_placed % nx; 103 | imgpixels[bi[0]][bi[1]] = [...imgpixels[row][col]]; 104 | 105 | result[resultPos[0]][resultPos[1]] = pixel_to_swap; 106 | 107 | drawPixel([...resultPos], pixel_to_swap); 108 | pixels_placed++; 109 | } 110 | } 111 | 112 | function findBestMatch(origin, candidates) { 113 | //console.log(candidates); 114 | let best_idx = -1; 115 | let best_val = Number.MAX_VALUE; 116 | 117 | for (let i = 0; i < candidates.length; i++) { 118 | let yy = p.floor(candidates[i] / nx); 119 | let xx = candidates[i] % nx; 120 | let val = compareCols(origin, imgpixels[yy][xx]); 121 | 122 | if (val < best_val) { 123 | best_val = val; 124 | best_idx = [yy, xx]; 125 | } 126 | } 127 | return best_idx; 128 | } 129 | 130 | function compareCols(a, b) { 131 | let dx = a[0] - b[0]; 132 | let dy = a[1] - b[1]; 133 | let dz = a[2] - b[2]; 134 | return p.sqrt(p.pow(dx, 2) + p.pow(dy, 2) + p.pow(dz, 2)); 135 | } 136 | 137 | p.keyPressed = function() { 138 | if (p.keyCode === 80) p.saveCanvas('sketch_' + THE_SEED, 'jpeg'); 139 | }; 140 | }; 141 | new p5(sketch); 142 | 143 | // --- UTILS --- 144 | 145 | function newArray(n, value) { 146 | n = n || 0; 147 | var array = new Array(n); 148 | for (var i = 0; i < n; i++) { 149 | array[i] = value; 150 | } 151 | return array; 152 | } 153 | 154 | function getRandoms(n, from, to) { 155 | let arr = []; 156 | while (arr.length < n) { 157 | let rand = Math.floor(Math.random() * (to - from)) + from; 158 | arr.push(rand); 159 | } 160 | return arr; 161 | } 162 | 163 | function getNearest(q, rs, reverse) { 164 | let closest = reverse ? 0 : Number.MAX_VALUE; 165 | let closest_item = null; 166 | 167 | let sign = reverse ? -1 : 1; 168 | 169 | let selection = getRandoms(expansion_candidates, 0, rs.length - 1); 170 | selection.forEach(r => { 171 | let dist = rs[r][2]; 172 | if (sign * dist < sign * closest) { 173 | closest_item = rs[r]; 174 | closest = dist; 175 | } 176 | }); 177 | return closest_item; 178 | } 179 | 180 | function distance(a, b) { 181 | return Math.sqrt(Math.pow(b[0] - a[0], 2) + Math.pow(b[1] - a[1], 2)); 182 | } 183 | 184 | function getAdjacentIndices(q, include_diagonals) { 185 | let indices = []; 186 | if (q[0] < nx - 1) indices.push([q[0] + 1, q[1], q[2] + 1]); 187 | if (q[1] < ny - 1) indices.push([q[0], q[1] + 1, q[2] + 1]); 188 | if (q[0] > 0) indices.push([q[0] - 1, q[1], q[2] + 1]); 189 | if (q[1] > 0) indices.push([q[0], q[1] - 1, q[2] + 1]); 190 | 191 | if (include_diagonals) { 192 | if (q[0] < nx - 1) { 193 | if (q[1] < nx - 1) indices.push([q[0] + 1, q[1] + 1, q[2] + 1]); 194 | if (q[1] > 0) indices.push([q[0] + 1, q[1] - 1, q[2] + 1]); 195 | } 196 | if (q[0] > 0) { 197 | if (q[1] < nx - 1) indices.push([q[0] - 1, q[1] + 1, q[2] + 1]); 198 | if (q[1] > 0) indices.push([q[0] - 1, q[1] - 1, q[2] + 1]); 199 | } 200 | } 201 | return indices; 202 | } 203 | 204 | function expandNeighborhood(neighborhood, next) { 205 | if (!neighborhood.includes(next)) { 206 | console.error('Next pixel is not from the neighboorhood', neighborhood); 207 | return neighborhood; 208 | } 209 | const expansion = getAdjacentIndices(next, false).filter( 210 | pos => result[pos[0]][pos[1]] === null 211 | ); 212 | 213 | const next_index = neighborhood.indexOf(next); 214 | neighborhood.splice(next_index, 1); 215 | 216 | return union(neighborhood, expansion); // return union 217 | } 218 | 219 | function getSurroundingColor(q) { 220 | const adj = getAdjacentIndices(q, true) 221 | .filter(pos => result[pos[0]][pos[1]] !== null) 222 | .map(ind => result[ind[0]][ind[1]]); 223 | if (include_original) adj.push(originalpixels[q[0]][q[1]]); 224 | return meanColor(adj); 225 | } 226 | function meanColor(arr) { 227 | let sx = arr.map(x => x[0]).reduce((a, b) => a + b, 0); 228 | let sy = arr.map(x => x[1]).reduce((a, b) => a + b, 0); 229 | let sz = arr.map(x => x[2]).reduce((a, b) => a + b, 0); 230 | 231 | return [sx / arr.length, sy / arr.length, sz / arr.length]; 232 | } 233 | 234 | function union(a, b) { 235 | let c = [...b]; 236 | a.forEach(x => { 237 | if (!b.some(y => eq(x, y))) c.push(x); 238 | }); 239 | return c; 240 | } 241 | 242 | function eq(a, b) { 243 | return a[0] === b[0] && a[1] === b[1]; 244 | } 245 | -------------------------------------------------------------------------------- /sketch-radial.js: -------------------------------------------------------------------------------- 1 | let nx = 512; 2 | let ny = 512; 3 | let size = 2; 4 | let img_resolution = 5; 5 | 6 | let expansion_candidates = 2; 7 | let selection_size = 30; 8 | let reversed = true; 9 | 10 | let tick = 0; 11 | let img, imgpixels; 12 | let result; 13 | let candidates; 14 | let pixels_placed; 15 | 16 | let sketch = function(p) { 17 | let THE_SEED; 18 | 19 | p.preload = function() { 20 | img = p.loadImage('https://raw.githubusercontent.com/kgolid/pixel-sorting/master/img.jpg'); 21 | }; 22 | 23 | p.setup = function() { 24 | p.createCanvas(nx * size, nx * size); 25 | p.pixelDensity(1); 26 | img.loadPixels(); 27 | p.loadPixels(); 28 | 29 | THE_SEED = p.floor(p.random(9999999)); 30 | p.randomSeed(THE_SEED); 31 | p.noStroke(); 32 | 33 | imgpixels = newArray(ny).map((_, j) => 34 | newArray(nx).map((_, i) => { 35 | var loc = (i + j * img.width) * 4 * img_resolution; 36 | return [ 37 | img.pixels[loc + 0], 38 | img.pixels[loc + 1], 39 | img.pixels[loc + 2], 40 | j, 41 | i 42 | ]; 43 | }) 44 | ); 45 | result = newArray(ny).map(() => newArray(nx, null)); 46 | 47 | const centre = [Math.floor(nx / 2), Math.floor(ny / 2)]; 48 | const start = [ 49 | ...imgpixels[Math.floor(Math.random() * nx)][ 50 | Math.floor(Math.random() * ny) 51 | ] 52 | ]; 53 | result[centre[0]][centre[1]] = start; 54 | 55 | drawPixel([...centre], start); 56 | candidates = expandNeighboorhood([centre], centre); 57 | 58 | pixels_placed = 0; 59 | 60 | drawImage(imgpixels); 61 | }; 62 | 63 | p.draw = function() { 64 | if (pixels_placed < nx * ny - 500) placePixels(500); 65 | else if (pixels_placed < nx * ny) placePixels(nx * ny - pixels_placed - 1); 66 | }; 67 | 68 | function drawImage(image) { 69 | image.forEach((pxr, j) => 70 | pxr.forEach((px, i) => { 71 | drawPixel([j, i], px); 72 | }) 73 | ); 74 | } 75 | 76 | function drawPixel(pos, col) { 77 | p.fill(col[0], col[1], col[2]); 78 | p.rect(size * pos[1], size * pos[0], size, size); 79 | } 80 | 81 | function placePixels(n) { 82 | for (i = 0; i < n; i++) { 83 | const resultPos = getNearest( 84 | [Math.floor(nx / 2), Math.floor(ny / 2)], 85 | candidates, 86 | reversed 87 | ); 88 | 89 | let origin = getSurroundingColor(resultPos); 90 | let cands = getRandoms(selection_size, pixels_placed, nx * ny); 91 | let bi = findBestMatch(origin, cands); 92 | 93 | candidates = expandNeighboorhood(candidates, resultPos); 94 | 95 | let pixel_to_swap = [...imgpixels[bi[0]][bi[1]]]; 96 | 97 | let row = Math.floor(pixels_placed / nx); 98 | let col = pixels_placed % nx; 99 | imgpixels[bi[0]][bi[1]] = [...imgpixels[row][col]]; 100 | //imgpixels[resultPos[0]][resultPos[1]][3] = pixel_to_swap[3]; 101 | //imgpixels[resultPos[0]][resultPos[1]][4] = pixel_to_swap[4]; 102 | 103 | result[resultPos[0]][resultPos[1]] = pixel_to_swap; 104 | 105 | drawPixel([...resultPos], pixel_to_swap); 106 | //drawPixel([pixel_to_swap[3], pixel_to_swap[4]], [255, 255, 255]); 107 | pixels_placed++; 108 | } 109 | } 110 | 111 | function findBestMatch(origin, candidates) { 112 | //console.log(candidates); 113 | let best_idx = -1; 114 | let best_val = Number.MAX_VALUE; 115 | 116 | for (let i = 0; i < candidates.length; i++) { 117 | let yy = p.floor(candidates[i] / nx); 118 | let xx = candidates[i] % nx; 119 | let val = compareCols(origin, imgpixels[yy][xx]); 120 | 121 | if (val < best_val) { 122 | best_val = val; 123 | best_idx = [yy, xx]; 124 | } 125 | } 126 | return best_idx; 127 | } 128 | 129 | function compareCols(a, b) { 130 | let dx = a[0] - b[0]; 131 | let dy = a[1] - b[1]; 132 | let dz = a[2] - b[2]; 133 | return p.sqrt(p.pow(dx, 2) + p.pow(dy, 2) + p.pow(dz, 2)); 134 | } 135 | 136 | p.keyPressed = function() { 137 | if (p.keyCode === 80) p.saveCanvas('sketch_' + THE_SEED, 'jpeg'); 138 | }; 139 | }; 140 | new p5(sketch); 141 | 142 | // --- UTILS --- 143 | 144 | function newArray(n, value) { 145 | n = n || 0; 146 | var array = new Array(n); 147 | for (var i = 0; i < n; i++) { 148 | array[i] = value; 149 | } 150 | return array; 151 | } 152 | 153 | function getRandoms(n, from, to) { 154 | let arr = []; 155 | while (arr.length < n) { 156 | let rand = Math.floor(Math.random() * (to - from)) + from; 157 | arr.push(rand); 158 | } 159 | return arr; 160 | } 161 | 162 | function getNearest(q, rs, reverse) { 163 | let closest = reverse ? 0 : Number.MAX_VALUE; 164 | let closest_item = null; 165 | 166 | let sign = reverse ? -1 : 1; 167 | 168 | let selection = getRandoms(expansion_candidates, 0, rs.length - 1); 169 | selection.forEach(r => { 170 | let dist = distance(q, rs[r]); 171 | if (sign * dist < sign * closest) { 172 | closest_item = rs[r]; 173 | closest = dist; 174 | } 175 | }); 176 | return closest_item; 177 | } 178 | 179 | function distance(a, b) { 180 | return Math.sqrt(Math.pow(b[0] - a[0], 2) + Math.pow(b[1] - a[1], 2)); 181 | } 182 | 183 | function getAdjacentIndices(q, include_diagonals) { 184 | let indices = []; 185 | if (q[0] < nx - 1) indices.push([q[0] + 1, q[1]]); 186 | if (q[1] < ny - 1) indices.push([q[0], q[1] + 1]); 187 | if (q[0] > 0) indices.push([q[0] - 1, q[1]]); 188 | if (q[1] > 0) indices.push([q[0], q[1] - 1]); 189 | 190 | if (include_diagonals) { 191 | if (q[0] < nx - 1) { 192 | if (q[1] < nx - 1) indices.push([q[0] + 1, q[1] + 1]); 193 | if (q[1] > 0) indices.push([q[0] + 1, q[1] - 1]); 194 | } 195 | if (q[0] > 0) { 196 | if (q[1] < nx - 1) indices.push([q[0] - 1, q[1] + 1]); 197 | if (q[1] > 0) indices.push([q[0] - 1, q[1] - 1]); 198 | } 199 | } 200 | return indices; 201 | } 202 | 203 | function expandNeighboorhood(neighborhood, next) { 204 | if (!neighborhood.includes(next)) { 205 | console.error('Next pixel is not from the neighboorhood', neighborhood); 206 | return neighborhood; 207 | } 208 | const expansion = getAdjacentIndices(next, false).filter( 209 | pos => result[pos[0]][pos[1]] === null 210 | ); 211 | 212 | const next_index = neighborhood.indexOf(next); 213 | neighborhood.splice(next_index, 1); 214 | 215 | //console.log(neighborhood[0], expansion); 216 | return union(neighborhood, expansion); // return union 217 | } 218 | 219 | function getSurroundingColor(q) { 220 | const adj = getAdjacentIndices(q, true).filter( 221 | pos => result[pos[0]][pos[1]] !== null 222 | ); 223 | return meanColor(adj.map(ind => result[ind[0]][ind[1]])); 224 | } 225 | 226 | function meanColor(arr) { 227 | //console.log('nonempty adj', arr); 228 | let sx = arr.map(x => x[0]).reduce((a, b) => a + b, 0); 229 | let sy = arr.map(x => x[1]).reduce((a, b) => a + b, 0); 230 | let sz = arr.map(x => x[2]).reduce((a, b) => a + b, 0); 231 | 232 | return [sx / arr.length, sy / arr.length, sz / arr.length]; 233 | } 234 | 235 | function union(a, b) { 236 | let c = [...a]; 237 | b.forEach(x => { 238 | if (!c.some(y => eq(x, y))) c.push(x); 239 | }); 240 | return c; 241 | } 242 | 243 | function eq(a, b) { 244 | return a[0] === b[0] && a[1] === b[1]; 245 | } 246 | -------------------------------------------------------------------------------- /sketch-swap-new.js: -------------------------------------------------------------------------------- 1 | let nx; 2 | let ny; 3 | let size = 1; 4 | let img_resolution = 2; 5 | let src_resolution = 2; 6 | let speed = 500; 7 | 8 | let expand_with_diagonals = false; 9 | let expansion_candidates = 2; 10 | let selection_size = 500; 11 | let reversed = true; 12 | let include_original = true; 13 | 14 | let img, imgpixels; 15 | let src, originalpixels; 16 | let candidates; 17 | let pixels_placed; 18 | 19 | let front; 20 | 21 | let sketch = function(p) { 22 | let THE_SEED; 23 | 24 | p.preload = function() { 25 | src = p.loadImage('monet.jpg'); 26 | img = p.loadImage('starry-night.jpg'); 27 | }; 28 | 29 | p.setup = function() { 30 | nx = Math.floor(src.width / src_resolution); 31 | ny = Math.floor(src.height / src_resolution); 32 | p.createCanvas(nx * size, ny * size); 33 | p.pixelDensity(1); 34 | img.loadPixels(); 35 | src.loadPixels(); 36 | p.loadPixels(); 37 | 38 | THE_SEED = p.floor(p.random(9999999)); 39 | console.log('Seed: ', THE_SEED); 40 | p.randomSeed(THE_SEED); 41 | p.noStroke(); 42 | 43 | originalpixels = newArray(ny).map((_, j) => 44 | newArray(nx).map((_, i) => { 45 | var loc = (i + j * src.width) * 4 * src_resolution; 46 | return [src.pixels[loc + 0], src.pixels[loc + 1], src.pixels[loc + 2]]; 47 | }) 48 | ); 49 | 50 | imgpixels = newArray(ny).map((_, j) => 51 | newArray(nx).map((_, i) => { 52 | var loc = (i + j * img.width) * 4 * img_resolution; 53 | return [ 54 | img.pixels[loc + 0], 55 | img.pixels[loc + 1], 56 | img.pixels[loc + 2], 57 | i, 58 | j 59 | ]; 60 | }) 61 | ); 62 | 63 | front = newArray(ny).map((_, j) => 64 | newArray(nx).map((_, i) => { 65 | return { bx: i, by: j, filled: false, adjacent: false, dist: -1 }; 66 | }) 67 | ); 68 | drawImage(imgpixels); 69 | 70 | const centre = [Math.floor(nx / 2), Math.floor(ny / 2)]; 71 | front[centre[1]][centre[0]] = { 72 | bx: centre[0], 73 | by: centre[1], 74 | filled: true, 75 | neighbor: true, 76 | dist: 0 77 | }; 78 | 79 | drawPixel(centre); 80 | candidates = expandNeighborhood([centre], centre); 81 | 82 | pixels_placed = 1; 83 | }; 84 | 85 | p.draw = function() { 86 | if (pixels_placed < nx * ny - speed) placePixels(speed); 87 | else if (pixels_placed < nx * ny) placePixels(nx * ny - pixels_placed); 88 | }; 89 | 90 | function drawImage(image) { 91 | image.forEach((pxr, j) => 92 | pxr.forEach((px, i) => { 93 | drawPixel([i, j], px); 94 | }) 95 | ); 96 | } 97 | 98 | function drawPixel(pos) { 99 | const col = colorInBack(pos); 100 | p.fill(col[0], col[1], col[2]); 101 | p.rect(size * pos[0], size * pos[1], size, size); 102 | } 103 | 104 | function placePixels(n) { 105 | for (i = 0; i < n; i++) { 106 | // Find expansion pixel and expand candidates accordingly. 107 | const currentPos = getNearest(candidates); 108 | candidates = expandNeighborhood(candidates, currentPos); 109 | 110 | // Find best matching pixel among selection. 111 | let origin = getSurroundingColor(currentPos); 112 | let cands = getRandoms(selection_size, pixels_placed, nx * ny); 113 | let best = findBestMatch(origin, cands); 114 | let frontBest = positionInFront(best); 115 | 116 | // Swap expansion pixel with matching pixel. 117 | swap_refs( 118 | currentPos[0], 119 | currentPos[1], 120 | frontBest[0], 121 | frontBest[1], 122 | false 123 | ); 124 | 125 | front[currentPos[1]][currentPos[0]].filled = true; 126 | 127 | // Draw the swapped pixels. 128 | drawPixel(currentPos); 129 | drawPixel(frontBest); 130 | 131 | let row = Math.floor(pixels_placed / nx); 132 | let col = pixels_placed % nx; 133 | let frontScanPos = positionInFront([col, row]); 134 | 135 | // Swap in back array to consolidate (critical for performance). 136 | swap_refs( 137 | currentPos[0], 138 | currentPos[1], 139 | frontScanPos[0], 140 | frontScanPos[1], 141 | true 142 | ); 143 | 144 | pixels_placed++; 145 | } 146 | } 147 | 148 | function findBestMatch(origin, candidates) { 149 | let best_idx = -1; 150 | let best_val = Number.MAX_VALUE; 151 | 152 | for (let i = 0; i < candidates.length; i++) { 153 | let yy = Math.floor(candidates[i] / nx); 154 | let xx = candidates[i] % nx; 155 | let val = compareCols(origin, imgpixels[yy][xx]); 156 | 157 | if (val < best_val) { 158 | best_val = val; 159 | best_idx = [xx, yy]; 160 | } 161 | } 162 | return best_idx; 163 | } 164 | 165 | function compareCols(a, b) { 166 | let dx = a[0] - b[0]; 167 | let dy = a[1] - b[1]; 168 | let dz = a[2] - b[2]; 169 | return p.sqrt(p.pow(dx, 2) + p.pow(dy, 2) + p.pow(dz, 2)); 170 | } 171 | 172 | p.keyPressed = function() { 173 | if (p.keyCode === 80) p.saveCanvas('sketch_' + THE_SEED, 'jpeg'); 174 | }; 175 | 176 | // --- UTILS --- 177 | 178 | function positionInFront([x, y]) { 179 | return [imgpixels[y][x][3], imgpixels[y][x][4]]; 180 | } 181 | 182 | function colorInBack([x, y]) { 183 | let front_item = front[y][x]; 184 | return imgpixels[front_item.by][front_item.bx]; 185 | } 186 | 187 | function swap_refs(px, py, rx, ry, swap_colors_in_back) { 188 | const pfront = { bx: front[py][px].bx, by: front[py][px].by }; 189 | const rfront = { bx: front[ry][rx].bx, by: front[ry][rx].by }; 190 | 191 | const pcol = imgpixels[pfront.by][pfront.bx].slice(0); 192 | const rcol = imgpixels[rfront.by][rfront.bx].slice(0); 193 | 194 | front[py][px].bx = rfront.bx; 195 | front[py][px].by = rfront.by; 196 | front[ry][rx].bx = pfront.bx; 197 | front[ry][rx].by = pfront.by; 198 | 199 | imgpixels[pfront.by][pfront.bx][3] = rcol[3]; 200 | imgpixels[pfront.by][pfront.bx][4] = rcol[4]; 201 | imgpixels[rfront.by][rfront.bx][3] = pcol[3]; 202 | imgpixels[rfront.by][rfront.bx][4] = pcol[4]; 203 | 204 | if (swap_colors_in_back) { 205 | imgpixels[pfront.by][pfront.bx] = rcol; 206 | imgpixels[rfront.by][rfront.bx] = pcol; 207 | } 208 | } 209 | 210 | function newArray(n, value) { 211 | n = n || 0; 212 | var array = new Array(n); 213 | for (var i = 0; i < n; i++) { 214 | array[i] = value; 215 | } 216 | return array; 217 | } 218 | 219 | function getRandoms(n, from, to) { 220 | let arr = []; 221 | while (arr.length < n) { 222 | let rand = Math.floor(p.random() * (to - from)) + from; 223 | arr.push(rand); 224 | } 225 | return arr; 226 | } 227 | 228 | function getNearest(rs) { 229 | const reverse = p.random() > 0.97 ? !reversed : reversed; 230 | let closest = reverse ? 0 : Number.MAX_VALUE; 231 | let closest_item = null; 232 | 233 | let sign = reverse ? -1 : 1; 234 | 235 | let selection = getRandoms(expansion_candidates, 0, rs.length - 1); 236 | selection.forEach(r => { 237 | let dist = front[rs[r][1]][rs[r][0]].dist; 238 | if (sign * dist < sign * closest) { 239 | closest_item = rs[r]; 240 | closest = dist; 241 | } 242 | }); 243 | return closest_item; 244 | } 245 | 246 | function getAdjacentIndices(q, include_diagonals) { 247 | let indices = []; 248 | if (q[0] < nx - 1) indices.push([q[0] + 1, q[1]]); 249 | if (q[1] < ny - 1) indices.push([q[0], q[1] + 1]); 250 | if (q[0] > 0) indices.push([q[0] - 1, q[1]]); 251 | if (q[1] > 0) indices.push([q[0], q[1] - 1]); 252 | 253 | if (include_diagonals) { 254 | if (q[0] < nx - 1) { 255 | if (q[1] < ny - 1) indices.push([q[0] + 1, q[1] + 1]); 256 | if (q[1] > 0) indices.push([q[0] + 1, q[1] - 1]); 257 | } 258 | if (q[0] > 0) { 259 | if (q[1] < ny - 1) indices.push([q[0] - 1, q[1] + 1]); 260 | if (q[1] > 0) indices.push([q[0] - 1, q[1] - 1]); 261 | } 262 | } 263 | return indices; 264 | } 265 | 266 | function expandNeighborhood(neighborhood, next) { 267 | if (!neighborhood.includes(next)) { 268 | console.error('Next pixel is not from the neighboorhood', neighborhood); 269 | return neighborhood; 270 | } 271 | 272 | let expansion = getAdjacentIndices(next, expand_with_diagonals).filter( 273 | pos => !front[pos[1]][pos[0]].filled 274 | ); 275 | 276 | expansion.forEach(pos => { 277 | front[pos[1]][pos[0]].dist = front[next[1]][next[0]].dist + 1; 278 | }); 279 | 280 | expansion = expansion.filter(pos => !front[pos[1]][pos[0]].adjacent); 281 | expansion.forEach(pos => { 282 | front[pos[1]][pos[0]].adjacent = true; 283 | }); 284 | 285 | const next_index = neighborhood.indexOf(next); 286 | neighborhood.splice(next_index, 1); 287 | 288 | return neighborhood.concat(expansion); 289 | } 290 | 291 | function getSurroundingColor(q) { 292 | const adj = getAdjacentIndices(q, true) 293 | .filter(pos => front[pos[1]][pos[0]].filled) 294 | .map(ind => colorInBack(ind)); 295 | if (include_original) adj.push(originalpixels[q[1]][q[0]]); 296 | return meanColor(adj); 297 | } 298 | 299 | function meanColor(arr) { 300 | let sx = arr.map(x => x[0]).reduce((a, b) => a + b, 0); 301 | let sy = arr.map(x => x[1]).reduce((a, b) => a + b, 0); 302 | let sz = arr.map(x => x[2]).reduce((a, b) => a + b, 0); 303 | 304 | return [sx / arr.length, sy / arr.length, sz / arr.length]; 305 | } 306 | 307 | function union(a, b) { 308 | let c = [...b]; 309 | a.forEach(x => { 310 | if (!b.some(y => eq(x, y))) c.push(x); 311 | }); 312 | return c; 313 | } 314 | 315 | function eq(a, b) { 316 | return a[0] === b[0] && a[1] === b[1]; 317 | } 318 | }; 319 | new p5(sketch); 320 | -------------------------------------------------------------------------------- /sketch-swap.js: -------------------------------------------------------------------------------- 1 | let nx = 1024; 2 | let ny = 1024; 3 | let size = 1; 4 | let img_resolution = 2; 5 | let src_resolution = 1; 6 | 7 | let expansion_candidates = 2; 8 | let selection_size = 800; 9 | let reversed = true; 10 | let include_original = true; 11 | 12 | let rgbColor = false; 13 | 14 | let tick = 0; 15 | let img, imgpixels; 16 | let src, originalpixels; 17 | let result; 18 | let candidates; 19 | let pixels_placed; 20 | 21 | let sketch = function(p) { 22 | let THE_SEED; 23 | 24 | p.preload = function() { 25 | img = p.loadImage('img2.jpg'); 26 | src = p.loadImage('space.jpg'); 27 | }; 28 | 29 | p.setup = function() { 30 | p.createCanvas(nx * size, nx * size); 31 | p.pixelDensity(1); 32 | img.loadPixels(); 33 | src.loadPixels(); 34 | p.loadPixels(); 35 | p.colorMode(p.HSB, 100); 36 | 37 | THE_SEED = p.floor(p.random(9999999)); 38 | p.randomSeed(THE_SEED); 39 | p.noStroke(); 40 | 41 | originalpixels = newArray(ny).map((_, j) => 42 | newArray(nx).map((_, i) => { 43 | var loc = (i + j * src.width) * 4 * src_resolution; 44 | return rgbColor 45 | ? [src.pixels[loc + 0], src.pixels[loc + 1], src.pixels[loc + 2]] 46 | : RGBtoHSV( 47 | src.pixels[loc + 0], 48 | src.pixels[loc + 1], 49 | src.pixels[loc + 2] 50 | ); 51 | }) 52 | ); 53 | 54 | imgpixels = newArray(ny).map((_, j) => 55 | newArray(nx).map((_, i) => { 56 | var loc = (i + j * img.width) * 4 * img_resolution; 57 | return rgbColor 58 | ? [img.pixels[loc + 0], img.pixels[loc + 1], img.pixels[loc + 2]] 59 | : RGBtoHSV( 60 | img.pixels[loc + 0], 61 | img.pixels[loc + 1], 62 | img.pixels[loc + 2] 63 | ); 64 | }) 65 | ); 66 | result = newArray(ny).map(() => newArray(nx, null)); 67 | 68 | const centre = [Math.floor(nx / 2), Math.floor(ny / 2), 0]; 69 | const start = [ 70 | ...imgpixels[Math.floor(Math.random() * nx)][ 71 | Math.floor(Math.random() * ny) 72 | ] 73 | ]; 74 | result[centre[0]][centre[1]] = start; 75 | 76 | drawPixel([centre[0], centre[1]], start); 77 | candidates = expandNeighborhood([centre], centre); 78 | 79 | pixels_placed = 0; 80 | 81 | //drawImage(imgpixels); 82 | }; 83 | 84 | p.draw = function() { 85 | if (pixels_placed < nx * ny - 500) placePixels(500); 86 | else if (pixels_placed < nx * ny) placePixels(nx * ny - pixels_placed - 1); 87 | }; 88 | 89 | function drawImage(image) { 90 | image.forEach((pxr, j) => 91 | pxr.forEach((px, i) => { 92 | drawPixel([j, i], px); 93 | }) 94 | ); 95 | } 96 | 97 | function drawPixel(pos, col) { 98 | p.fill(col[0], col[1], col[2]); 99 | p.rect(size * pos[1], size * pos[0], size, size); 100 | } 101 | 102 | function placePixels(n) { 103 | for (i = 0; i < n; i++) { 104 | const resultPos = getNearest( 105 | [Math.floor(nx / 2), Math.floor(ny / 2)], 106 | candidates, 107 | reversed 108 | ); 109 | 110 | let origin = getSurroundingColor(resultPos); 111 | let cands = getRandoms(selection_size, pixels_placed, nx * ny); 112 | let bi = findBestMatch(origin, cands); 113 | 114 | candidates = expandNeighborhood(candidates, resultPos); 115 | let pixel_to_swap = [...imgpixels[bi[0]][bi[1]]]; 116 | 117 | let row = Math.floor(pixels_placed / nx); 118 | let col = pixels_placed % nx; 119 | imgpixels[bi[0]][bi[1]] = [...imgpixels[row][col]]; 120 | 121 | result[resultPos[0]][resultPos[1]] = pixel_to_swap; 122 | 123 | drawPixel([...resultPos], pixel_to_swap); 124 | pixels_placed++; 125 | } 126 | } 127 | 128 | function findBestMatch(origin, candidates) { 129 | //console.log(candidates); 130 | let best_idx = -1; 131 | let best_val = Number.MAX_VALUE; 132 | 133 | for (let i = 0; i < candidates.length; i++) { 134 | let yy = p.floor(candidates[i] / nx); 135 | let xx = candidates[i] % nx; 136 | let val = compareCols(origin, imgpixels[yy][xx]); 137 | 138 | if (val < best_val) { 139 | best_val = val; 140 | best_idx = [yy, xx]; 141 | } 142 | } 143 | return best_idx; 144 | } 145 | 146 | function compareCols(a, b) { 147 | let dx = a[0] - b[0]; 148 | let dy = a[1] - b[1]; 149 | let dz = a[2] - b[2]; 150 | return p.sqrt(p.pow(dx, 2) + p.pow(dy, 2) + p.pow(dz, 2)); 151 | } 152 | 153 | p.keyPressed = function() { 154 | if (p.keyCode === 80) p.saveCanvas('sketch_' + THE_SEED, 'jpeg'); 155 | }; 156 | }; 157 | new p5(sketch); 158 | 159 | // --- UTILS --- 160 | 161 | function newArray(n, value) { 162 | n = n || 0; 163 | var array = new Array(n); 164 | for (var i = 0; i < n; i++) { 165 | array[i] = value; 166 | } 167 | return array; 168 | } 169 | 170 | function getRandoms(n, from, to) { 171 | let arr = []; 172 | while (arr.length < n) { 173 | let rand = Math.floor(Math.random() * (to - from)) + from; 174 | arr.push(rand); 175 | } 176 | return arr; 177 | } 178 | 179 | function getNearest(q, rs, reversed) { 180 | const reverse = Math.random() > 0.96 ? !reversed : reversed; 181 | let closest = reverse ? 0 : Number.MAX_VALUE; 182 | let closest_item = null; 183 | 184 | let sign = reverse ? -1 : 1; 185 | 186 | let selection = getRandoms(expansion_candidates, 0, rs.length - 1); 187 | selection.forEach(r => { 188 | let dist = rs[r][2]; 189 | if (sign * dist < sign * closest) { 190 | closest_item = rs[r]; 191 | closest = dist; 192 | } 193 | }); 194 | return closest_item; 195 | } 196 | 197 | function distance(a, b) { 198 | return Math.sqrt(Math.pow(b[0] - a[0], 2) + Math.pow(b[1] - a[1], 2)); 199 | } 200 | 201 | function getAdjacentIndices(q, include_diagonals) { 202 | let indices = []; 203 | if (q[0] < nx - 1) indices.push([q[0] + 1, q[1], q[2] + 1]); 204 | if (q[1] < ny - 1) indices.push([q[0], q[1] + 1, q[2] + 1]); 205 | if (q[0] > 0) indices.push([q[0] - 1, q[1], q[2] + 1]); 206 | if (q[1] > 0) indices.push([q[0], q[1] - 1, q[2] + 1]); 207 | 208 | if (include_diagonals) { 209 | if (q[0] < nx - 1) { 210 | if (q[1] < nx - 1) indices.push([q[0] + 1, q[1] + 1, q[2] + 1]); 211 | if (q[1] > 0) indices.push([q[0] + 1, q[1] - 1, q[2] + 1]); 212 | } 213 | if (q[0] > 0) { 214 | if (q[1] < nx - 1) indices.push([q[0] - 1, q[1] + 1, q[2] + 1]); 215 | if (q[1] > 0) indices.push([q[0] - 1, q[1] - 1, q[2] + 1]); 216 | } 217 | } 218 | return indices; 219 | } 220 | 221 | function expandNeighborhood(neighborhood, next) { 222 | if (!neighborhood.includes(next)) { 223 | console.error('Next pixel is not from the neighboorhood', neighborhood); 224 | return neighborhood; 225 | } 226 | const expansion = getAdjacentIndices(next, false).filter( 227 | pos => result[pos[0]][pos[1]] === null 228 | ); 229 | 230 | const next_index = neighborhood.indexOf(next); 231 | neighborhood.splice(next_index, 1); 232 | 233 | return union(neighborhood, expansion); // return union 234 | } 235 | 236 | function getSurroundingColor(q) { 237 | const adj = getAdjacentIndices(q, true) 238 | .filter(pos => result[pos[0]][pos[1]] !== null) 239 | .map(ind => result[ind[0]][ind[1]]); 240 | if (include_original) adj.push(originalpixels[q[0]][q[1]]); 241 | return meanColor(adj); 242 | } 243 | function meanColor(arr) { 244 | let sx = arr.map(x => x[0]).reduce((a, b) => a + b, 0); 245 | let sy = arr.map(x => x[1]).reduce((a, b) => a + b, 0); 246 | let sz = arr.map(x => x[2]).reduce((a, b) => a + b, 0); 247 | 248 | return [sx / arr.length, sy / arr.length, sz / arr.length]; 249 | } 250 | 251 | function union(a, b) { 252 | let c = [...b]; 253 | a.forEach(x => { 254 | if (!b.some(y => eq(x, y))) c.push(x); 255 | }); 256 | return c; 257 | } 258 | 259 | function eq(a, b) { 260 | return a[0] === b[0] && a[1] === b[1]; 261 | } 262 | 263 | function RGBtoHSV(r, g, b) { 264 | if (arguments.length === 1) { 265 | (g = r.g), (b = r.b), (r = r.r); 266 | } 267 | var max = Math.max(r, g, b), 268 | min = Math.min(r, g, b), 269 | d = max - min, 270 | h, 271 | s = max === 0 ? 0 : d / max, 272 | v = max / 255; 273 | 274 | switch (max) { 275 | case min: 276 | h = 0; 277 | break; 278 | case r: 279 | h = g - b + d * (g < b ? 6 : 0); 280 | h /= 6 * d; 281 | break; 282 | case g: 283 | h = b - r + d * 2; 284 | h /= 6 * d; 285 | break; 286 | case b: 287 | h = r - g + d * 4; 288 | h /= 6 * d; 289 | break; 290 | } 291 | return [h * 100, s * 100, v * 100]; 292 | } 293 | -------------------------------------------------------------------------------- /sketch.js: -------------------------------------------------------------------------------- 1 | let nx = 512; 2 | let ny = 512; 3 | let size = 2; 4 | let img_resolution = 5; 5 | 6 | let selection_size = 6; 7 | let diagonal = false; 8 | 9 | let tick = 0; 10 | let img; 11 | 12 | let sketch = function(p) { 13 | let THE_SEED; 14 | 15 | p.preload = function() { 16 | img = p.loadImage( 17 | 'https://raw.githubusercontent.com/kgolid/pixel-sorting/master/img.jpg' 18 | ); 19 | }; 20 | 21 | p.setup = function() { 22 | p.createCanvas(nx * size, nx * size); 23 | p.pixelDensity(1); 24 | img.loadPixels(); 25 | p.loadPixels(); 26 | 27 | THE_SEED = p.floor(p.random(9999999)); 28 | p.randomSeed(THE_SEED); 29 | p.noStroke(); 30 | 31 | imgpixels = newArray(ny).map((_, j) => 32 | newArray(nx).map((_, i) => { 33 | var loc = (i + j * img.width) * 4 * img_resolution; 34 | return [img.pixels[loc + 0], img.pixels[loc + 1], img.pixels[loc + 2]]; 35 | }) 36 | ); 37 | 38 | drawImage(imgpixels); 39 | }; 40 | 41 | p.draw = function() { 42 | if (tick < ny) { 43 | sortRow(imgpixels, tick); 44 | tick++; 45 | sortRow(imgpixels, tick); 46 | tick++; 47 | } 48 | }; 49 | 50 | function drawImage(image) { 51 | image.forEach((pxr, j) => 52 | pxr.forEach((px, i) => { 53 | drawPixel([j, i], px); 54 | }) 55 | ); 56 | } 57 | 58 | function drawPixel(pos, col) { 59 | p.fill(...col); 60 | p.rect(size * pos[1], size * pos[0], size, size); 61 | } 62 | 63 | function sortRow(mat, row) { 64 | for (let col = 0; col < mat[row].length; col++) { 65 | if (row !== 0) { 66 | let origin = diagonal 67 | ? getDiagonalOrigin(mat, col, row) 68 | : getOrigin(mat, col, row); 69 | let cands = getRandoms(selection_size, nx * row + col, nx * ny); 70 | let bi = findBestMatch(origin, mat, cands); 71 | 72 | let pixel_to_swap = [...mat[bi[0]][bi[1]]]; 73 | 74 | drawPixel(bi, mat[row][col]); 75 | drawPixel([row, col], pixel_to_swap); 76 | 77 | mat[bi[0]][bi[1]] = [...mat[row][col]]; 78 | mat[row][col] = pixel_to_swap; 79 | } 80 | } 81 | } 82 | 83 | function getOrigin(mat, x, y) { 84 | let row_above = mat[y - 1]; 85 | if (x < 1) return meanCol(row_above[x], row_above[x + 1]); 86 | if (x >= mat[y].length - 1) return meanCol(row_above[x - 1], row_above[x]); 87 | return meanCol(row_above[x], meanCol(row_above[x - 1], row_above[x + 1])); 88 | } 89 | 90 | function getDiagonalOrigin(mat, x, y) { 91 | if (x === 0) return mat[y - 1][x]; 92 | return meanCol(mat[y - 1][x], mat[y][x - 1]); 93 | } 94 | 95 | function findBestMatch(origin, arr, candidates) { 96 | let best_idx = -1; 97 | let best_val = Number.MAX_VALUE; 98 | 99 | for (let i = 0; i < candidates.length; i++) { 100 | let yy = p.floor(candidates[i] / nx); 101 | let xx = candidates[i] % nx; 102 | let val = compareCols(origin, arr[yy][xx]); 103 | 104 | if (val < best_val) { 105 | best_val = val; 106 | best_idx = [yy, xx]; 107 | } 108 | } 109 | return best_idx; 110 | } 111 | 112 | function compareCols(a, b) { 113 | let dx = a[0] - b[0]; 114 | let dy = a[1] - b[1]; 115 | let dz = a[2] - b[2]; 116 | return p.sqrt(p.pow(dx, 2) + p.pow(dy, 2) + p.pow(dz, 2)); 117 | } 118 | 119 | function meanCol(a, b) { 120 | let sx = a[0] + b[0]; 121 | let sy = a[1] + b[1]; 122 | let sz = a[2] + b[2]; 123 | return [sx / 2, sy / 2, sz / 2]; 124 | } 125 | 126 | p.keyPressed = function() { 127 | if (p.keyCode === 80) p.saveCanvas('sketch_' + THE_SEED, 'jpeg'); 128 | }; 129 | }; 130 | new p5(sketch); 131 | 132 | // --- UTILS --- 133 | 134 | function newArray(n, value) { 135 | n = n || 0; 136 | var array = new Array(n); 137 | for (var i = 0; i < n; i++) { 138 | array[i] = value; 139 | } 140 | return array; 141 | } 142 | 143 | function getRandoms(n, from, to) { 144 | let arr = []; 145 | while (arr.length < n) { 146 | let rand = Math.floor(Math.random() * (to - from)) + from; 147 | arr.push(rand); 148 | } 149 | return arr; 150 | } 151 | -------------------------------------------------------------------------------- /variants/sketch.js: -------------------------------------------------------------------------------- 1 | let nx = 512; 2 | let ny = 512; 3 | let size = 2; 4 | 5 | let selection_size = 4; 6 | 7 | let img; 8 | 9 | let sketch = function(p) { 10 | let THE_SEED; 11 | 12 | p.preload = function() { 13 | img = p.loadImage('./img.jpg'); 14 | }; 15 | 16 | p.setup = function() { 17 | p.createCanvas(1024, 1024); 18 | 19 | p.pixelDensity(1); 20 | img.loadPixels(); 21 | p.loadPixels(); 22 | 23 | THE_SEED = p.floor(p.random(9999999)); 24 | p.randomSeed(THE_SEED); 25 | p.noStroke(); 26 | 27 | imgpixels = newArray(ny).map((x, j) => 28 | newArray(nx).map((y, i) => { 29 | var loc = (i + j * img.width) * 4 * 5; 30 | return [img.pixels[loc + 0], img.pixels[loc + 1], img.pixels[loc + 2]]; 31 | }) 32 | ); 33 | 34 | imgpixels = sortMatrix(imgpixels); 35 | }; 36 | 37 | p.draw = function() { 38 | imgpixels.forEach((pxr, j) => 39 | pxr.forEach((px, i) => { 40 | p.fill(px[0], px[1], px[2]); 41 | p.rect(size * i, size * j, size, size); 42 | }) 43 | ); 44 | }; 45 | 46 | p.keyPressed = function() { 47 | if (p.keyCode === 80) p.saveCanvas('sketch_' + THE_SEED, 'jpeg'); 48 | }; 49 | 50 | function sortMatrix(mat) { 51 | let sorted = []; 52 | mat.forEach((pxr, i) => sorted.push(sortRow(mat, i, sorted))); 53 | return sorted; 54 | } 55 | 56 | function sortRow(mat, row_idx, so_far) { 57 | let arr = mat[row_idx]; 58 | let sorted = [arr[0]]; 59 | for (let i = 1; i < arr.length; i++) { 60 | let vert_arr = mat.map(row => row[i]); 61 | let origin = 62 | row_idx == 0 63 | ? sorted[i - 1] 64 | : meanCol(sorted[i - 1], so_far[row_idx - 1][i]); 65 | let cands = getRandoms(selection_size, i, arr.length); 66 | let vert_cands = getRandoms(selection_size, row_idx, mat.length); 67 | 68 | let bi_h = findBestMatch(origin, arr, cands); 69 | let bi_v = findBestMatch(origin, vert_arr, vert_cands); 70 | 71 | if ( 72 | compareCols(origin, arr[bi_h]) < compareCols(origin, vert_arr[bi_v]) 73 | ) { 74 | sorted.push([...arr[bi_h]]); 75 | arr[bi_h] = [...arr[i]]; 76 | } else { 77 | sorted.push([...mat[bi_v][i]]); 78 | mat[bi_v][i] = [...arr[i]]; 79 | } 80 | } 81 | return sorted; 82 | } 83 | 84 | function getRandoms(n, from, to) { 85 | let arr = newArray(to - from).map((x, i) => i + from); 86 | return p.shuffle(arr).slice(0, n); 87 | } 88 | 89 | function findBestMatch(origin, arr, idxs) { 90 | let best_idx = -1; 91 | let best_val = 100000; 92 | 93 | for (let i = 0; i < idxs.length; i++) { 94 | let val = compareCols(origin, arr[idxs[i]]); 95 | if (val < best_val) { 96 | best_val = val; 97 | best_idx = idxs[i]; 98 | } 99 | } 100 | 101 | return best_idx; 102 | } 103 | 104 | function compareCols(a, b) { 105 | //console.log(a, b); 106 | let dx = a[0] - b[0]; 107 | let dy = a[1] - b[1]; 108 | let dz = a[2] - b[2]; 109 | return p.sqrt(p.pow(dx, 2) + p.pow(dy, 2) + p.pow(dz, 2)); 110 | } 111 | 112 | function meanCol(a, b) { 113 | let sx = a[0] + b[0]; 114 | let sy = a[1] + b[1]; 115 | let sz = a[2] + b[2]; 116 | 117 | return [sx / 2, sy / 2, sz / 2]; 118 | } 119 | }; 120 | new p5(sketch); 121 | 122 | // --- UTILS --- 123 | 124 | function swap(arr, i1, i2) { 125 | let v1 = arr[i1]; 126 | let v2 = arr[i2]; 127 | 128 | arr[i1] = v2; 129 | arr[i2] = v1; 130 | } 131 | 132 | function newArray(n, value) { 133 | n = n || 0; 134 | var array = new Array(n); 135 | for (var i = 0; i < n; i++) { 136 | array[i] = value; 137 | } 138 | return array; 139 | } 140 | -------------------------------------------------------------------------------- /variants/sketch2.js: -------------------------------------------------------------------------------- 1 | let nx = 512; 2 | let ny = 512; 3 | let size = 2; 4 | let tick; 5 | 6 | let selection_size = 80; 7 | 8 | let img; 9 | 10 | let sketch = function(p) { 11 | let THE_SEED; 12 | 13 | p.preload = function() { 14 | img = p.loadImage('./insta4.jpg'); 15 | }; 16 | 17 | p.setup = function() { 18 | p.createCanvas(1024, 1024); 19 | 20 | p.pixelDensity(1); 21 | img.loadPixels(); 22 | p.loadPixels(); 23 | 24 | THE_SEED = p.floor(p.random(9999999)); 25 | p.randomSeed(THE_SEED); 26 | p.noStroke(); 27 | //p.noLoop(); 28 | 29 | tick = 0; 30 | 31 | imgpixels = newArray(ny).map((x, j) => 32 | newArray(nx).map((y, i) => { 33 | var loc = (i + j * img.width) * 4 * 2; 34 | return [img.pixels[loc + 0], img.pixels[loc + 1], img.pixels[loc + 2]]; 35 | }) 36 | ); 37 | 38 | imgpixels.forEach((pxr, j) => 39 | pxr.forEach((px, i) => { 40 | p.fill(px[0], px[1], px[2]); 41 | p.rect(size * i, size * j, size, size); 42 | }) 43 | ); 44 | 45 | //imgpixels = sortMatrix(imgpixels); 46 | }; 47 | 48 | p.draw = function() { 49 | if (tick < ny) { 50 | imgpixels[tick] = sortRow(imgpixels, tick); 51 | 52 | imgpixels[tick].forEach((px, i) => { 53 | p.fill(px[0], px[1], px[2]); 54 | p.rect(size * i, size * tick, size, size); 55 | }); 56 | tick++; 57 | } 58 | }; 59 | 60 | p.keyPressed = function() { 61 | if (p.keyCode === 80) p.saveCanvas('sketch_' + THE_SEED, 'jpeg'); 62 | }; 63 | 64 | function sortRow(mat, row_idx) { 65 | let sorted = []; 66 | for (let i = 0; i < mat[row_idx].length; i++) { 67 | if (i === 0 && row_idx === 0) { 68 | sorted.push(mat[row_idx][i]); 69 | } else { 70 | let origin = getOrigin(i, row_idx, mat, sorted); 71 | 72 | let cands = getRandoms(selection_size, nx * row_idx + i, nx * ny); 73 | let bi = findBestMatch(origin, mat, cands); 74 | 75 | drawPixel(bi, mat[row_idx][i]); 76 | sorted.push([...mat[bi[0]][bi[1]]]); 77 | mat[bi[0]][bi[1]] = [...mat[row_idx][i]]; 78 | } 79 | } 80 | return sorted; 81 | } 82 | 83 | function getOrigin(col_idx, row_idx, mat, sorted) { 84 | if (col_idx === 0) return mat[row_idx - 1][col_idx]; 85 | 86 | if (row_idx === 0) return sorted[col_idx - 1]; 87 | return meanCol(sorted[col_idx - 1], mat[row_idx - 1][col_idx]); 88 | } 89 | 90 | function getUniqueRandoms(n, from, to) { 91 | let arr = newArray(to - from).map((x, i) => i + from); 92 | return p.shuffle(arr).slice(0, n); 93 | } 94 | 95 | function getRandoms(n, from, to) { 96 | let arr = []; 97 | while (arr.length < n) { 98 | let randomnumber = Math.floor(Math.random() * (to - from)) + from; 99 | arr.push(randomnumber); 100 | } 101 | 102 | return arr; 103 | } 104 | 105 | function findBestMatch(origin, arr, idxs) { 106 | let best_idx = -1; 107 | let best_val = 100000; 108 | 109 | for (let i = 0; i < idxs.length; i++) { 110 | let yy = p.floor(idxs[i] / nx); 111 | let xx = idxs[i] % nx; 112 | let val = compareCols(origin, arr[yy][xx]); 113 | if (val < best_val) { 114 | best_val = val; 115 | best_idx = [yy, xx]; 116 | } 117 | } 118 | 119 | return best_idx; 120 | } 121 | 122 | function drawPixel(pos, col) { 123 | p.fill(col[0], col[1], col[2]); 124 | p.rect(size * pos[1], size * pos[0], size, size); 125 | } 126 | 127 | function compareCols(a, b) { 128 | //console.log(a, b); 129 | let dx = a[0] - b[0]; 130 | let dy = a[1] - b[1]; 131 | let dz = a[2] - b[2]; 132 | return p.sqrt(p.pow(dx, 2) + p.pow(dy, 2) + p.pow(dz, 2)); 133 | } 134 | 135 | function meanCol(a, b) { 136 | let sx = a[0] + b[0]; 137 | let sy = a[1] + b[1]; 138 | let sz = a[2] + b[2]; 139 | 140 | return [sx / 2, sy / 2, sz / 2]; 141 | } 142 | }; 143 | new p5(sketch); 144 | 145 | // --- UTILS --- 146 | 147 | function swap(arr, i1, i2) { 148 | let v1 = arr[i1]; 149 | let v2 = arr[i2]; 150 | 151 | arr[i1] = v2; 152 | arr[i2] = v1; 153 | } 154 | 155 | function newArray(n, value) { 156 | n = n || 0; 157 | var array = new Array(n); 158 | for (var i = 0; i < n; i++) { 159 | array[i] = value; 160 | } 161 | return array; 162 | } 163 | -------------------------------------------------------------------------------- /variants/sketch3.js: -------------------------------------------------------------------------------- 1 | let nx = 512; 2 | let ny = 384; 3 | let size = 2; 4 | let tick; 5 | 6 | let selection_size = 100; 7 | 8 | let img; 9 | 10 | let sketch = function(p) { 11 | let THE_SEED; 12 | 13 | p.preload = function() { 14 | img = p.loadImage('./img13.jpg'); 15 | }; 16 | 17 | p.setup = function() { 18 | p.createCanvas(1024, 1024); 19 | 20 | p.pixelDensity(1); 21 | img.loadPixels(); 22 | p.loadPixels(); 23 | 24 | THE_SEED = p.floor(p.random(9999999)); 25 | p.randomSeed(THE_SEED); 26 | p.noStroke(); 27 | //p.noLoop(); 28 | 29 | tick = 0; 30 | 31 | imgpixels = newArray(ny).map((x, j) => 32 | newArray(nx).map((y, i) => { 33 | var loc = (i + j * img.width) * 4 * 2; 34 | return [img.pixels[loc + 0], img.pixels[loc + 1], img.pixels[loc + 2]]; 35 | }) 36 | ); 37 | 38 | imgpixels.forEach((pxr, j) => 39 | pxr.forEach((px, i) => { 40 | p.fill(px[0], px[1], px[2]); 41 | p.rect(size * i, size * j, size, size); 42 | }) 43 | ); 44 | 45 | //imgpixels = sortMatrix(imgpixels); 46 | }; 47 | 48 | p.draw = function() { 49 | if (tick < ny) { 50 | imgpixels[tick] = sortRow(imgpixels, tick); 51 | 52 | imgpixels[tick].forEach((px, i) => { 53 | p.fill(px[0], px[1], px[2]); 54 | p.rect(size * i, size * tick, size, size); 55 | }); 56 | tick++; 57 | } 58 | }; 59 | 60 | p.keyPressed = function() { 61 | if (p.keyCode === 80) p.saveCanvas('sketch_' + THE_SEED, 'jpeg'); 62 | }; 63 | 64 | function sortRow(mat, row_idx) { 65 | let sorted = []; 66 | for (let i = 0; i < mat[row_idx].length; i++) { 67 | if (i === 0 && row_idx === 0) { 68 | sorted.push(mat[row_idx][i]); 69 | } else { 70 | let origin = getOrigin(i, row_idx, mat, sorted); 71 | 72 | let cands = getRandoms(selection_size, nx * row_idx + i, nx * ny); 73 | let bi = findBestMatch(origin, mat, cands); 74 | let pixel_to_swap = [...mat[bi[0]][bi[1]]]; 75 | drawPixel(bi, mat[row_idx][i]); 76 | sorted.push(pixel_to_swap); 77 | mat[bi[0]][bi[1]] = [...mat[row_idx][i]]; 78 | } 79 | } 80 | return sorted; 81 | } 82 | 83 | function getOrigin(col_idx, row_idx, mat, sorted) { 84 | //return mat[row_idx][col_idx]; 85 | if (col_idx === 0) 86 | return meanCol(mat[row_idx][col_idx], mat[row_idx - 1][col_idx]); 87 | if (row_idx === 0) 88 | return meanCol(mat[row_idx][col_idx], sorted[col_idx - 1]); 89 | return meanCol( 90 | sorted[col_idx - 1], 91 | meanCol(mat[row_idx][col_idx], mat[row_idx - 1][col_idx]) 92 | ); 93 | } 94 | 95 | function getUniqueRandoms(n, from, to) { 96 | let arr = newArray(to - from).map((x, i) => i + from); 97 | return p.shuffle(arr).slice(0, n); 98 | } 99 | 100 | function getRandoms(n, from, to) { 101 | let arr = []; 102 | while (arr.length < n) { 103 | let randomnumber = Math.floor(Math.random() * (to - from)) + from; 104 | arr.push(randomnumber); 105 | } 106 | 107 | return arr; 108 | } 109 | 110 | function findBestMatch(origin, arr, idxs) { 111 | let best_idx = -1; 112 | let best_val = 100000; 113 | 114 | for (let i = 0; i < idxs.length; i++) { 115 | let yy = p.floor(idxs[i] / nx); 116 | let xx = idxs[i] % nx; 117 | let val = compareCols(origin, arr[yy][xx]); 118 | if (val < best_val) { 119 | best_val = val; 120 | best_idx = [yy, xx]; 121 | } 122 | } 123 | 124 | return best_idx; 125 | } 126 | 127 | function drawPixel(pos, col) { 128 | p.fill(col[0], col[1], col[2]); 129 | p.rect(size * pos[1], size * pos[0], size, size); 130 | } 131 | 132 | function compareCols(a, b) { 133 | //console.log(a, b); 134 | let dx = a[0] - b[0]; 135 | let dy = a[1] - b[1]; 136 | let dz = a[2] - b[2]; 137 | return p.sqrt(p.pow(dx, 2) + p.pow(dy, 2) + p.pow(dz, 2)); 138 | } 139 | 140 | function meanCol(a, b) { 141 | let sx = a[0] + b[0]; 142 | let sy = a[1] + b[1]; 143 | let sz = a[2] + b[2]; 144 | 145 | return [sx / 2, sy / 2, sz / 2]; 146 | } 147 | }; 148 | new p5(sketch); 149 | 150 | // --- UTILS --- 151 | 152 | function swap(arr, i1, i2) { 153 | let v1 = arr[i1]; 154 | let v2 = arr[i2]; 155 | 156 | arr[i1] = v2; 157 | arr[i2] = v1; 158 | } 159 | 160 | function newArray(n, value) { 161 | n = n || 0; 162 | var array = new Array(n); 163 | for (var i = 0; i < n; i++) { 164 | array[i] = value; 165 | } 166 | return array; 167 | } 168 | --------------------------------------------------------------------------------