Skip to content

Instantly share code, notes, and snippets.

@mattdesl
Last active January 2, 2022 12:05
Show Gist options
  • Save mattdesl/447feabf4c819889a5e73de0da37abc0 to your computer and use it in GitHub Desktop.
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
// 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