From ea71e4cbf94a30101a2a4fdf28f0e71f7350d734 Mon Sep 17 00:00:00 2001 From: glazenbol <5520120+glazenbol@users.noreply.github.com> Date: Sat, 21 Mar 2026 04:26:45 +0100 Subject: [PATCH] split huge image into tiles, load tiles as grayscale images --- .gitignore | 3 +- frontend/.gitignore | 4 +- frontend/js/sketch.js | 82 +++++++++++++++++++++++++++----------- frontend/package.json | 3 +- frontend/scripts/tile.sh | 36 +++++++++++++++++ frontend/shader/sobel.frag | 34 ++++++++++++++++ 6 files changed, 136 insertions(+), 26 deletions(-) create mode 100755 frontend/scripts/tile.sh create mode 100644 frontend/shader/sobel.frag diff --git a/.gitignore b/.gitignore index f09cd16..43bed69 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ yarn.lock .env.*.local # IDE +.zed/ .vscode/ .idea/ *.swp @@ -28,4 +29,4 @@ logs/ *.log # OS -Thumbs.db \ No newline at end of file +Thumbs.db diff --git a/frontend/.gitignore b/frontend/.gitignore index a0bc8cc..7e627fb 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -14,6 +14,7 @@ out/ .env.*.local # IDE +.zed/ .vscode/ .idea/ *.swp @@ -28,4 +29,5 @@ Thumbs.db .cache/ # big assets -assets/scans/ \ No newline at end of file +assets/scans/ +assets/img/ diff --git a/frontend/js/sketch.js b/frontend/js/sketch.js index c598a1b..15878a0 100644 --- a/frontend/js/sketch.js +++ b/frontend/js/sketch.js @@ -1,5 +1,6 @@ import p5 from "p5"; import io from "socket.io-client"; +import UPNG from "upng-js"; const socket = io("http://localhost:3000"); const params = { @@ -35,32 +36,65 @@ const realText = poem.split(" ").filter((t) => t.length > 0); const sketch = (p) => { let sobel; + const imageW = 37418, + imageH = 27178; + const tileW = 8192; + const tileH = 4096; + const tileCols = Math.ceil(imageW / tileW); + const tileRows = Math.ceil(imageH / tileH); + const tileCount = tileCols * tileRows; + const tiles = []; p.setup = async () => { // bg = await p.loadImage("assets/scans/1200dpi 16bit grayscale.png"); - bg = await p.loadImage("assets/scans/portable.png"); + const promises = []; + let n_downloaded = 0; + for (let row = 0; row < tileRows; row++) { + for (let column = 0; column < tileCols; column++) { + const x = column * tileW; + const y = row * tileH; + const w = Math.min(tileW, imageW - x); + const h = Math.min(tileH, imageH - y); + promises.push( + fetch( + `assets/img/tiles/37418x27178_3200dpi_16bit_grayscale/tile_x${x}_y${y}_w${w}_h${h}.png`, + ) + .then((res) => res.arrayBuffer()) + .then((buffer) => { + // divide width by 2 because we are colorpacking + const p5img = p.createImage(w / 2, h); + p5img.loadPixels(); + + console.log( + `p5img.pixels.length: ${p5img.pixels.length}, p5img.width: ${p5img.width}, p5img.height: ${p5img.height}`, + ); + + const img = UPNG.decode(buffer); + console.log( + `img.data.length: ${img.data.length}, img.width: ${img.width}, img.height: ${img.height}`, + ); + + // BEWARE: weird things happening with dimension of the loaded image (4096 values too many) + for (let i = 0; i < w * h * 4; i++) { + p5img.pixels[i] = img.data[i]; + } + tiles[row * tileCols + column] = img; + n_downloaded++; + console.log( + `Downloaded ${n_downloaded} / ${tileCount} (${Math.round((n_downloaded / tileCount) * 100)}%) tiles`, + ); + }), + ); + } + } + await Promise.all(promises); p.createCanvas(p.windowWidth, p.windowHeight, p.WEBGL); - sobel = p.createFilterShader(` - precision highp float; - - // x,y coordinates, given from the vertex shader - varying vec2 vTexCoord; - - // the canvas contents, given from filter() - uniform sampler2D tex0; - // other useful information from the canvas - uniform vec2 texelSize; - uniform vec2 canvasSize; - // a custom variable from this sketch - uniform float pulse; - - void main() { - // get the color at current pixel - vec4 color = texture2D(tex0, vTexCoord); - // set the output color - color.b = 1.0; - color *= pulse; - gl_FragColor = vec4(color.rgb, 1.0); - }`); + const shaderFile = await fetch("shader/sobel.frag"); + const shaderText = await shaderFile.text(); + sobel = p.createFilterShader(shaderText); + console.log( + "Max texture size:", + p._renderer.GL.getParameter(p._renderer.GL.MAX_TEXTURE_SIZE), + ); // const stream = c.elt.captureStream(60); }; @@ -125,6 +159,8 @@ const sketch = (p) => { p.fill(0, 0, 0, params.textAlpha); p.text(word, bounds.x, bounds.y + p.textAscent()); } + + function drawTiles(x, y, width, height) {} }; new p5(sketch); diff --git a/frontend/package.json b/frontend/package.json index 73a3a99..79d2d50 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -7,7 +7,8 @@ "license": "ISC", "dependencies": { "p5": "^2.2.2", - "socket.io-client": "^4.8.3" + "socket.io-client": "^4.8.3", + "upng-js": "^2.1.0" }, "devDependencies": { "lil-gui": "^0.21.0", diff --git a/frontend/scripts/tile.sh b/frontend/scripts/tile.sh new file mode 100755 index 0000000..3a77d2d --- /dev/null +++ b/frontend/scripts/tile.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +INPUT="$1" +OUTPUT_DIR="tiles" + +#Create OUTPUT_DIR if it doesn't exist +mkdir -p "$OUTPUT_DIR" + +#Get image dimensions +WIDTH=$(identify -format "%w" "$INPUT") +HEIGHT=$(identify -format "%h" "$INPUT") +echo "Image dimensions: $WIDTH x $HEIGHT" + +#Tile dimensions +TILE_WIDTH=8192 +TILE_HEIGHT=4096 + +echo "Cutting image into tiles of ${TILE_WIDTH}x${TILE_HEIGHT}..." + +# Loop over vertical and horizontal positions +count=0 +for (( y=0; y_y_w_h.png + TILE_FILE="$OUTPUT_DIR/tile_x${x}_y${y}_w${W}_h${H}.png" + echo "Creating $TILE_FILE" + convert "$INPUT" -crop "${TILE_WIDTH}x${TILE_HEIGHT}+${x}+${y}" +repage "$TILE_FILE" + ((count++)) + done +done + +echo "Done. $count tiles created." diff --git a/frontend/shader/sobel.frag b/frontend/shader/sobel.frag new file mode 100644 index 0000000..964707a --- /dev/null +++ b/frontend/shader/sobel.frag @@ -0,0 +1,34 @@ +precision highp float; + +// x,y coordinates, given from the vertex shader +varying vec2 vTexCoord; + +// the canvas contents, given from filter() +uniform sampler2D tex0; +// other useful information from the canvas +uniform vec2 texelSize; +uniform vec2 canvasSize; +// a custom variable from this sketch +uniform float pulse; + +void main() { + // Sample neighboring pixels + float tl = texture2D(tex0, vTexCoord + vec2(-texelSize.x, -texelSize.y)).r; + float l = texture2D(tex0, vTexCoord + vec2(-texelSize.x, 0.0)).r; + float bl = texture2D(tex0, vTexCoord + vec2(-texelSize.x, texelSize.y)).r; + + float t = texture2D(tex0, vTexCoord + vec2(0.0, -texelSize.y)).r; + float b = texture2D(tex0, vTexCoord + vec2(0.0, texelSize.y)).r; + + float tr = texture2D(tex0, vTexCoord + vec2(texelSize.x, -texelSize.y)).r; + float r = texture2D(tex0, vTexCoord + vec2(texelSize.x, 0.0)).r; + float br = texture2D(tex0, vTexCoord + vec2(texelSize.x, texelSize.y)).r; + + // Sobel kernels + float gx = -tl - 2.0 * l - bl + tr + 2.0 * r + br; + float gy = -tl - 2.0 * t - tr + bl + 2.0 * b + br; + + float edge = length(vec2(gx, gy)); + + gl_FragColor = vec4(vec3(edge), 1.0); +}