Skip to content

Instantly share code, notes, and snippets.

@skytomo221
Created February 13, 2023 10:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save skytomo221/6ba8fca6fea1f680c2c9ed187fc1392f to your computer and use it in GitHub Desktop.
Save skytomo221/6ba8fca6fea1f680c2c9ed187fc1392f to your computer and use it in GitHub Desktop.
LED表示機風SVGジェネレーター
/*!
* "led-type-destination-sign-svg-generator.ts" by skytomo in 2022
*
* This program is released under the CC0 1.0 Universal.
* See https://creativecommons.org/publicdomain/zero/1.0/legalcode
*/
import * as fs from "node:fs/promises";
import * as path from "node:path";
import { Canvas, Image, ImageData } from "canvas";
const sum = <T>(
arr: T[],
fn?: (value: T, index: number, array: T[]) => number
): number =>
fn
? sum(arr.map(fn))
: (arr as number[]).reduce((prev, current) => prev + current);
const average = <T>(
arr: T[],
fn?: (value: T, index: number, array: T[]) => number
) => sum(arr, fn) / arr.length;
const range = (start: number, stop: number, step: number) =>
Array.from({ length: (stop - start) / step + 1 }, (_, i) => start + i * step);
type Color = {
red: number;
green: number;
blue: number;
alpha: number;
};
class LedTypeDestinationSignSvgGenerator {
imagedata: ImageData;
width: number;
height: number;
constructor(imagedata: ImageData, width: number, height: number) {
this.imagedata = imagedata;
this.width = width;
this.height = height;
}
static range = (start: number, stop: number, step: number) =>
Array.from(
{ length: (stop - start) / step + 1 },
(_, i) => start + i * step
);
color(index: number): Color {
return {
red: this.imagedata.data[index],
green: this.imagedata.data[index + 1],
blue: this.imagedata.data[index + 2],
alpha: this.imagedata.data[index + 3],
};
}
dx() {
return Math.round(this.imagedata.width / this.width);
}
dy() {
return Math.round(this.imagedata.height / this.height);
}
average_color(x: number, y: number) {
const index = (x: number, y: number) => (y * this.imagedata.width + x) * 4;
const colors = range(x, x + this.dx(), 1)
.map((x) =>
range(y, y + this.dy(), 1).map((y) => this.color(index(x, y)))
)
.flat();
return {
red: Math.round(average(colors, ({ red }) => red)),
green: Math.round(average(colors, ({ green }) => green)),
blue: Math.round(average(colors, ({ blue }) => blue)),
alpha: Math.round(average(colors, ({ alpha }) => alpha)),
};
}
generate() {
return range(0, this.imagedata.height, this.dy())
.map((y) =>
range(0, this.imagedata.width, this.dx()).map((x) =>
this.average_color(x, y)
)
)
.filter((y) =>
y.every(
(x) =>
!isNaN(x.red) &&
!isNaN(x.blue) &&
!isNaN(x.green) &&
!isNaN(x.alpha)
)
);
}
to_svg() {
const circles = this.generate()
.map((line, y) => {
return line
.map(
(pixel, x) =>
`<circle cx="${x}" cy="${y}" r="0.5" fill="rgba(${pixel.red}, ${
pixel.green
}, ${pixel.blue}, ${pixel.alpha / 255})"></circle>`
)
.join("");
})
.join("");
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${this.width} ${this.height}" height="">${circles}</svg>`;
}
}
fs.readFile(path.join(__dirname, "..", "images", "sakura-miko2.png"))
.then((data: Buffer) => {
const img = new Image();
img.src = data;
const canvas = new Canvas(img.width, img.height);
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, img.width, img.height);
const imagedata = ctx.getImageData(0, 0, img.width, img.height);
const generator = new LedTypeDestinationSignSvgGenerator(imagedata, 40, 40);
console.log(generator.to_svg());
})
.catch((reason: any) => {
console.error(reason);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment