Skip to content

Instantly share code, notes, and snippets.

@mattdesl
Created June 5, 2017 16:19
Show Gist options
  • Save mattdesl/cd13e41b39983e8c822f56fb3a7bb114 to your computer and use it in GitHub Desktop.
Save mattdesl/cd13e41b39983e8c822f56fb3a7bb114 to your computer and use it in GitHub Desktop.
CPU-side "shaders" for really big image rendering
/*
This streams a CPU-side "shader" function into a really huge PNG image.
*/
const PNGEncoder = require('png-stream/encoder');
const fs = require('fs');
const path = require('path');
const Readable = require('readable-stream').Readable;
const vec2 = require('gl-vec2');
const smoothstep = require('smoothstep');
const shape = [ 6000, 6000 ];
const encoder = new PNGEncoder(shape[0], shape[1], {
colorSpace: 'rgba'
});
const output = fs.createWriteStream(path.resolve(__dirname, 'test.png'));
const stream = createShader(shape, (uv) => {
// Write your shader function here with any cool code you want...
const length = vec2.length([ uv[0] - 0.5, uv[1] - 0.5 ]);
const d = smoothstep(0.25, 0.5, length);
return [ d, d, d, 1 ];
}, 4);
stream.pipe(encoder);
encoder.pipe(output);
function createShader (size, fn, stride = 4) {
const width = size[0];
const height = size[1];
const totalPixels = width * height;
const chunkSize = Math.min(2048, height);
const totalChunks = Math.ceil(height / chunkSize);
let currentChunk = 0;
var stream = new Readable()
stream._read = read
return stream
function read () {
if (currentChunk > totalChunks - 1) {
return process.nextTick(() => {
stream.push(null)
})
}
var yOffset = chunkSize * currentChunk;
var dataHeight = Math.min(chunkSize, height - yOffset);
var pixelBuffer = new Buffer(width * dataHeight * stride);
var pixelsInChunk = pixelBuffer.length / stride;
var offset = 0;
for (let i = 0; i < pixelsInChunk; i++) {
const x = Math.floor(i % width);
const y = Math.floor(i / width) + yOffset;
const uv = [ x / width, y / height ];
const color = fn(uv, x, y, width, height);
for (let c = 0; c < stride; c++) {
let component;
if (c < color.length) {
component = Math.max(0, Math.min(255, Math.floor(color[c] * 255)));
} else {
component = 255; // expand any missing components, like alpha
}
pixelBuffer[offset++] = component
}
}
currentChunk++;
console.log('Chunk %d / %d', currentChunk, totalChunks)
stream.push(pixelBuffer)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment