🖼️ Animated pngs render

This commit is contained in:
ful1e5 2021-02-20 19:32:39 +05:30
parent 05a7d723f6
commit 3116d8f50d
4 changed files with 141 additions and 45 deletions

View file

@ -1,8 +1,10 @@
import fs from "fs"; import fs from "fs";
import path from "path"; import path from "path";
import puppeteer, { Browser } from "puppeteer"; import puppeteer, { Browser, ElementHandle, Page } from "puppeteer";
import { frameNumber } from "./util/frameNumber";
import { matchImages } from "./util/matchImages";
import { toHTML } from "./util/toHTML"; import { toHTML } from "./util/toHTML";
class BitmapsGenerator { class BitmapsGenerator {
@ -26,34 +28,6 @@ class BitmapsGenerator {
} }
} }
// private async screenshot(
// element: ElementHandle<Element>
// ): Promise<string | void | Buffer> {
// return element.screenshot({
// omitBackground: true,
// encoding: "binary",
// });
// }
// private async stopAnimation(page: Page) {
// // @ts-ignore
// await page._client.send("Animation.setPlaybackRate", {
// playbackRate: 0,
// });
// }
//
// private async resumeAnimation(page: Page, playbackRate: number = 0.1) {
// // @ts-ignore
// await page._client.send("Animation.setPlaybackRate", {
// playbackRate,
// });
// }
//
// private async saveFrameImage(key: string, frame: Buffer) {
// const out_path = path.resolve(outDir, key);
// fs.writeFileSync(out_path, frame, { encoding: "binary" });
// }
/** /**
* Prepare headless browser. * Prepare headless browser.
*/ */
@ -63,17 +37,15 @@ class BitmapsGenerator {
headless: true, headless: true,
}); });
} }
public async generate(
browser: Browser, private async getSvgElement(
content: string, page: Page,
out: string, content: string
animated: boolean = false ): Promise<ElementHandle<Element>> {
) {
if (!content) { if (!content) {
throw new Error(`${content} File Read error`); throw new Error(`${content} File Read error`);
} }
const page = await browser.newPage();
const html = toHTML(content); const html = toHTML(content);
await page.setContent(html); await page.setContent(html);
@ -82,12 +54,109 @@ class BitmapsGenerator {
if (!svg) { if (!svg) {
throw new Error("svg element not found!"); throw new Error("svg element not found!");
} }
return svg;
if (animated) {
console.log("animated");
} else {
await svg.screenshot({ omitBackground: true, path: out });
} }
public async generateStatic(
browser: Browser,
content: string,
key: string
) {
const page = await browser.newPage();
const svg = await this.getSvgElement(page, content);
const out = path.resolve(this.bitmapsDir, key);
console.log("Saving", key, "...");
await svg.screenshot({ omitBackground: true, path: out });
await page.close();
}
private async screenshot(
element: ElementHandle<Element>
): Promise<Buffer | string> {
const buffer = await element.screenshot({
encoding: "binary",
omitBackground: true,
});
if (!buffer) {
throw new Error("SVG element screenshot not working");
}
return buffer;
}
private async stopAnimation(page: Page) {
//@ts-ignore
await page._client.send("Animation.setPlaybackRate", {
playbackRate: 0,
});
}
private async resumeAnimation(page: Page, playbackRate: number) {
//@ts-ignore
await page._client.send("Animation.setPlaybackRate", {
playbackRate,
});
}
private async saveFrameImage(key: string, frame: Buffer | string) {
const out_path = path.resolve(this.bitmapsDir, key);
fs.writeFileSync(out_path, frame);
}
public async generateAnimated(
browser: Browser,
content: string,
key: string,
options: {
playbackRate: number;
diff: number;
frameLimit: number;
framePadding: number;
} = {
playbackRate: 0.3,
diff: 0,
frameLimit: 300,
framePadding: 3,
}
) {
const page = await browser.newPage();
const svg = await this.getSvgElement(page, content);
await this.stopAnimation(page);
let index = 1;
let breakRendering = false;
let prevImg: Buffer | string;
// Rendering frames till `imgN` matched to `imgN-1` (When Animation is done)
while (!breakRendering) {
if (index > options.frameLimit) {
throw new Error("Reached the frame limit.");
}
this.resumeAnimation(page, options.playbackRate);
const img: string | Buffer = await this.screenshot(svg);
this.stopAnimation(page);
if (index > 1) {
// @ts-ignore
const diff = matchImages(prevImg, img);
if (diff <= options.diff) {
breakRendering = !breakRendering;
}
}
const number = frameNumber(index, options.framePadding);
const frame = `${key}-${number}.png`;
console.log("Saving", frame, "...");
this.saveFrameImage(frame, img);
prevImg = img;
++index;
}
await page.close();
} }
} }
export { BitmapsGenerator }; export { BitmapsGenerator };

View file

@ -0,0 +1,7 @@
export const frameNumber = (index: number, padding: number) => {
let result = "" + index;
while (result.length < padding) {
result = "0" + result;
}
return result;
};

View file

@ -0,0 +1,11 @@
import Pixelmatch from "pixelmatch";
import { PNG } from "pngjs";
export const matchImages = (img1: Buffer, img2: Buffer): number => {
const { data: img1Data, width, height } = PNG.sync.read(img1);
const { data: imgNData } = PNG.sync.read(img2);
return Pixelmatch(img1Data, imgNData, null, width, height, {
threshold: 0.1,
});
};

View file

@ -24,16 +24,25 @@ const main = async () => {
const png = new BitmapsGenerator(bitmapsDir); const png = new BitmapsGenerator(bitmapsDir);
const browser = await png.getBrowser(); const browser = await png.getBrowser();
SVG.getStatic().forEach(async (svg) => { for (const svg of SVG.getStatic()) {
const key = `${path.basename(svg, ".svg")}.png`; const key = `${path.basename(svg, ".svg")}.png`;
console.log("Saving", key, "...");
const out = path.resolve(bitmapsDir, key);
let content = fs.readFileSync(svg, "utf-8"); let content = fs.readFileSync(svg, "utf-8");
content = SVGHandler.colorSvg(content, color); content = SVGHandler.colorSvg(content, color);
await png.generate(browser, content, out); await png.generateStatic(browser, content, key);
}); }
for (const svg of SVG.getAnimated()) {
const key = `${path.basename(svg, ".svg")}.png`;
let content = fs.readFileSync(svg, "utf-8");
content = SVGHandler.colorSvg(content, color);
await png.generateAnimated(browser, content, key);
}
await browser.close();
}; };
main(); main();