Skip to content

Instantly share code, notes, and snippets.

@JeffreyPalmer
Last active December 2, 2022 18:27
Show Gist options
  • Save JeffreyPalmer/5dd7c9e0ec6e4e5380d6a3229bd738c4 to your computer and use it in GitHub Desktop.
Save JeffreyPalmer/5dd7c9e0ec6e4e5380d6a3229bd738c4 to your computer and use it in GitHub Desktop.
Using dekapng to export a tiled version of an image
////////////////////////////////////////////////////////////////////////////////
// example of writing an RGBA png
////////////////////////////////////////////////////////////////////////////////
// TODO: Define the contract that this expects the draw function to adhere to
// - it should clear the screen if necessary
// - it should render to completion
// - it should be given a clean random state
//
// For this example, RenderContext contains a PicoGL App instance, which
// contains a WebGL context.
//
// This function will call the provided "renderFn" repeatedly until all of the
// tiles are rendered, and it will then return a blob that can be saved via
// window.URL.createObjectURL(blob)
//
// renderContext: stores information about the current WebGL configuration/buffers, etc.
// scale: the number of tiles to render in each direction
// chunkSize: the width of each tile (this currently doesn't support other aspect ratios)
// renderFn: a function that will be repeatedly called to render the scene.
// Note: This function needs to reset the RNG state if that's used
export function renderTiledExportBlob(
renderContext: RenderContext,
scale: number,
chunkSize: number,
renderFn: (context: RenderContext) => void
) {
const pngRGBAWriter = new PNGRGBAWriter(
chunkSize * scale,
chunkSize * scale
)
// iteration proceeds in rows
for (let chunkY = scale - 1; chunkY >= 0; chunkY--) {
// My coordinate system is from 0 to 1 on each axis
// fit is a linear interpolation function that maps from one interval to another
const yOffset = fit(
chunkY,
0,
scale - 1,
1.0 - 1.0 / scale,
-1.0 + 1.0 / scale
)
const rowChunks = []
for (let chunkX = 0; chunkX < scale; chunkX++) {
console.log(`Rendering chunk [${chunkX}, ${chunkY}]`)
const xOffset = fit(
chunkX,
0,
scale - 1,
1.0 - 1.0 / scale,
-1.0 + 1.0 / scale
)
// This tells WebGL to zoom into the current tile via scaling and translation
setScaleAndOffset(renderContext, scale, xOffset, yOffset)
renderFn(renderContext)
const data = new Uint8Array(chunkSize * chunkSize * scale * 4)
const gl = renderContext.app.gl
gl.readPixels(
0,
0,
chunkSize,
chunkSize,
gl.RGBA,
gl.UNSIGNED_BYTE,
data
)
rowChunks.push(data)
}
for (let row = chunkSize - 1; row >= 0; --row) {
rowChunks.forEach((chunk, _) => {
const rowSize = chunkSize * 4
const chunkOffset = rowSize * row
pngRGBAWriter.addPixels(chunk, chunkOffset, chunkSize)
})
}
}
return pngRGBAWriter.finishAndGetBlob()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment