From 3116d8f50d718ebc5b566eb2ba258a79b3c0e272 Mon Sep 17 00:00:00 2001 From: ful1e5 <24286590+ful1e5@users.noreply.github.com> Date: Sat, 20 Feb 2021 19:32:39 +0530 Subject: [PATCH] =?UTF-8?q?=F0=9F=96=BC=EF=B8=8F=20Animated=20pngs=20rende?= =?UTF-8?q?r?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../packages/core/src/BitmapsGenerator.ts | 149 +++++++++++++----- .../packages/core/src/util/frameNumber.ts | 7 + .../packages/core/src/util/matchImages.ts | 11 ++ bitmapper/packages/modern/src/index.ts | 19 ++- 4 files changed, 141 insertions(+), 45 deletions(-) create mode 100644 bitmapper/packages/core/src/util/frameNumber.ts create mode 100644 bitmapper/packages/core/src/util/matchImages.ts diff --git a/bitmapper/packages/core/src/BitmapsGenerator.ts b/bitmapper/packages/core/src/BitmapsGenerator.ts index b3403175..8d6b946c 100644 --- a/bitmapper/packages/core/src/BitmapsGenerator.ts +++ b/bitmapper/packages/core/src/BitmapsGenerator.ts @@ -1,8 +1,10 @@ import fs from "fs"; 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"; class BitmapsGenerator { @@ -26,34 +28,6 @@ class BitmapsGenerator { } } - // private async screenshot( - // element: ElementHandle - // ): Promise { - // 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. */ @@ -63,17 +37,15 @@ class BitmapsGenerator { headless: true, }); } - public async generate( - browser: Browser, - content: string, - out: string, - animated: boolean = false - ) { + + private async getSvgElement( + page: Page, + content: string + ): Promise> { if (!content) { throw new Error(`${content} File Read error`); } - const page = await browser.newPage(); const html = toHTML(content); await page.setContent(html); @@ -82,12 +54,109 @@ class BitmapsGenerator { if (!svg) { 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 + ): Promise { + 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 }; diff --git a/bitmapper/packages/core/src/util/frameNumber.ts b/bitmapper/packages/core/src/util/frameNumber.ts new file mode 100644 index 00000000..76d1165c --- /dev/null +++ b/bitmapper/packages/core/src/util/frameNumber.ts @@ -0,0 +1,7 @@ +export const frameNumber = (index: number, padding: number) => { + let result = "" + index; + while (result.length < padding) { + result = "0" + result; + } + return result; +}; diff --git a/bitmapper/packages/core/src/util/matchImages.ts b/bitmapper/packages/core/src/util/matchImages.ts new file mode 100644 index 00000000..625d2bd5 --- /dev/null +++ b/bitmapper/packages/core/src/util/matchImages.ts @@ -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, + }); +}; diff --git a/bitmapper/packages/modern/src/index.ts b/bitmapper/packages/modern/src/index.ts index 5e7040f5..cc63480e 100644 --- a/bitmapper/packages/modern/src/index.ts +++ b/bitmapper/packages/modern/src/index.ts @@ -24,16 +24,25 @@ const main = async () => { const png = new BitmapsGenerator(bitmapsDir); const browser = await png.getBrowser(); - SVG.getStatic().forEach(async (svg) => { + for (const svg of SVG.getStatic()) { const key = `${path.basename(svg, ".svg")}.png`; - console.log("Saving", key, "..."); - const out = path.resolve(bitmapsDir, key); let content = fs.readFileSync(svg, "utf-8"); 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();