@someaspy/pet-pet-gif
Version:
Given an avatar, generate a petting gif (known as "petpet" or "pet the").
78 lines (62 loc) • 1.94 kB
text/typescript
import { resolve } from "node:path";
import { buffer } from "node:stream/consumers";
import { GifEncoder } from "@skyra/gifenc";
import { type Canvas, createCanvas, type Image, loadImage } from "canvas";
const FRAMES = 10;
const petGifCache: (Canvas | Image)[] = [];
export default async function petPetGif(
avatarURL: string | Buffer,
options: {
resolution: number;
delay: number;
backgroundColor: string | null;
} = { resolution: 128, delay: 20, backgroundColor: null },
) {
// Create GIF encoder
const encoder = new GifEncoder(options.resolution, options.resolution);
const outputStream = encoder.createReadStream();
encoder.start();
encoder.setRepeat(0);
encoder.setDelay(options.delay);
encoder.setTransparent(null);
// Create canvas and its context
const canvas = createCanvas(options.resolution, options.resolution);
const ctx = canvas.getContext("2d");
const avatar = await loadImage(avatarURL);
// Loop and create each frame
for (let i = 0; i < FRAMES; i++) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (options.backgroundColor) {
ctx.fillStyle = options.backgroundColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
const j = i < FRAMES / 2 ? i : FRAMES - i;
const width = 0.8 + j * 0.02;
const height = 0.8 - j * 0.05;
const offsetX = (1 - width) * 0.5 + 0.1;
const offsetY = 1 - height - 0.08;
if (i === petGifCache.length)
petGifCache.push(
await loadImage(
resolve(import.meta.dirname, `../img/pet${i.toString()}.gif`),
),
);
ctx.drawImage(
avatar,
options.resolution * offsetX,
options.resolution * offsetY,
options.resolution * width,
options.resolution * height,
);
ctx.drawImage(
petGifCache[i]!,
0,
0,
options.resolution,
options.resolution,
);
encoder.addFrame(ctx.getImageData(0, 0, canvas.width, canvas.height).data);
}
encoder.finish();
return await buffer(outputStream);
}