Last active
January 2, 2022 12:05
-
-
Save mattdesl/447feabf4c819889a5e73de0da37abc0 to your computer and use it in GitHub Desktop.
canvas-sketch + motion blur + canvas2D (NOTE: Only blurs on sequence export) adapted from @delucis
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Adapted from @delucis | |
// https://github.com/delucis/pellicola/blob/735bd7487bdc597ac7272e4ddce9473c15f68d09/lib/frame-maker.js#L99-L134 | |
const canvasSketch = require('canvas-sketch'); | |
const settings = { | |
dimensions: [ 512, 512 ], | |
duration: 3, | |
animate: true, | |
fps: 24 | |
}; | |
const sketch = () => { | |
const render = ({ context, width, height, playhead }) => { | |
context.fillStyle = 'white'; | |
context.fillRect(0, 0, width, height); | |
// Draw a rotated rectangle | |
const x = width / 2; | |
const y = height / 2; | |
const rectWidth = width * 0.5; | |
const rectHeight = width * 0.05; | |
const rotation = playhead * Math.PI * 2; | |
context.save(); | |
context.fillStyle = 'black'; | |
context.translate(x, y); | |
context.rotate(rotation); | |
context.translate(-rectWidth / 2, -rectHeight / 2); | |
context.fillRect(0, 0, rectWidth, rectHeight); | |
context.restore(); | |
}; | |
return createMotionBlur(render, { | |
samplesPerFrame: 4, | |
shutterAngle: 0.5 | |
}); | |
}; | |
canvasSketch(sketch, settings); | |
// A utility that blurs the render() function by averaging samples over many frames | |
function createMotionBlur (render, opts = {}) { | |
const { | |
samplesPerFrame = 4, | |
shutterAngle = 0.5 | |
} = opts; | |
const otherCanvas = document.createElement('canvas'); | |
const otherContext = otherCanvas.getContext('2d'); | |
return (props = {}) => { | |
const { | |
context, | |
width, | |
height, | |
canvasWidth, | |
canvasHeight, | |
totalFrames, | |
exporting, | |
frame | |
} = props; | |
if (!exporting) return render(props); | |
otherCanvas.width = canvasWidth; | |
otherCanvas.height = canvasHeight; | |
otherContext.clearRect(0, 0, canvasWidth, canvasHeight); | |
const size = canvasWidth * canvasHeight * 4; | |
const composite = new Float32Array(size); | |
for (let i = 0; i < size; i++) { | |
composite[i] = 0; | |
} | |
// For every sample, run the render function and add the new pixel values to | |
// the composite array | |
for (let sample = 0; sample < samplesPerFrame; sample++) { | |
const sampleFrame = frame + sample * shutterAngle / samplesPerFrame; | |
render(Object.assign({}, props, { | |
frame: sampleFrame % totalFrames, | |
playhead: (sampleFrame / (totalFrames - 1)) % 1 | |
})); | |
const pixels = context.getImageData(0, 0, canvasWidth, canvasHeight).data; | |
for (let idx = 0; idx < pixels.length; idx++) { | |
composite[idx] += pixels[idx]; | |
} | |
} | |
// Fill the pixels in `imageData` with averaged values from `composite` | |
const imageData = context.createImageData(canvasWidth, canvasHeight); | |
for (let idx = 0; idx < imageData.data.length; idx++) { | |
imageData.data[idx] = composite[idx] / samplesPerFrame; | |
} | |
// Draw the averaged image data to the canvas | |
context.putImageData(imageData, 0, 0); | |
}; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment