Archive commit

This commit is contained in:
Erik McClure
2019-08-28 21:15:02 -07:00
commit 735845746e
1435 changed files with 140724 additions and 0 deletions
+283
View File
@@ -0,0 +1,283 @@
import * as fs from 'fs';
import { createCanvas, Image } from 'canvas';
import { ExtCanvas, Point } from './types';
export function loadImage(filePath: string) {
const image = new Image();
image.src = fs.readFileSync(filePath);
(image as any).currentSrc = filePath;
return image;
}
export function createExtCanvas(width: number, height: number, info: string): ExtCanvas {
const canvas: ExtCanvas = createCanvas(width, height) as any;
canvas.info = info;
return canvas;
}
export function imageToCanvas(image: HTMLImageElement): ExtCanvas {
const canvas = createExtCanvas(image.width, image.height, `imageToCanvas(${image.currentSrc})`);
canvas.getContext('2d')!.drawImage(image, 0, 0);
return canvas;
}
export function cropCanvas(canvas: ExtCanvas | undefined, x: number, y: number, w: number, h: number): ExtCanvas {
if (Math.round(x) !== x || Math.round(y) !== y || Math.round(w) !== w || Math.round(h) !== h) {
throw new Error(`Invalid cropping dimentions (${x} ${y} ${w} ${h})`);
}
const result = createExtCanvas(w, h, `${canvas ? canvas.info : 'from null'} (cropped ${x} ${y} ${w} ${h})`);
if (canvas) {
w = Math.min(w, canvas.width - x);
h = Math.min(h, canvas.height - y);
result.getContext('2d')!.drawImage(canvas, x, y, w, h, 0, 0, w, h);
}
return result;
}
export function mirrorCanvas(canvas: ExtCanvas, offsetX = 0) {
const mirrored = createExtCanvas(canvas.width, canvas.height, `${canvas.info} (mirrored)`);
const context = mirrored.getContext('2d')!;
context.translate(canvas.width, 0);
context.scale(-1, 1);
context.translate(offsetX, 0);
context.drawImage(canvas, 0, 0);
return mirrored;
}
export function padCanvas(canvas: ExtCanvas, left: number, top: number, right = 0, bottom = 0, bg?: string) {
if (left === 0 && top === 0 && right === 0 && bottom === 0)
return canvas;
const result = createExtCanvas(
canvas.width + left + right, canvas.height + top + bottom, `${canvas.info} (pad ${left} ${top} ${right} ${bottom})`);
const context = result.getContext('2d')!;
if (bg) {
context.fillStyle = bg;
context.fillRect(0, 0, result.width, result.height);
}
context.drawImage(canvas, left, top);
return result;
}
export function clipCanvas(canvas: ExtCanvas, x: number, y: number, w: number, h: number) {
return padCanvas(cropCanvas(canvas, x, y, w, h), x, y, canvas.width - (w + x), canvas.height - (h + y));
}
export function mergeCanvases(...canvases: (ExtCanvas | undefined)[]): ExtCanvas {
const existing = canvases.filter(c => !!c) as ExtCanvas[];
const { width, height } = existing[0];
const result = createExtCanvas(width, height, existing.map(c => c.info).join(' + '));
const context = result.getContext('2d')!;
existing.forEach(c => context.drawImage(c, 0, 0));
return result;
}
export function reverseMaskCanvas(canvas: ExtCanvas): ExtCanvas;
export function reverseMaskCanvas(canvas: ExtCanvas | undefined): ExtCanvas | undefined;
export function reverseMaskCanvas(canvas: ExtCanvas | undefined) {
if (!canvas)
return undefined;
const result = createExtCanvas(canvas.width, canvas.height, `${canvas.info} reversed mask`);
const context = result.getContext('2d')!;
context.fillStyle = 'white';
context.fillRect(0, 0, canvas.width, canvas.height);
context.globalCompositeOperation = 'destination-out';
context.drawImage(canvas, 0, 0);
return result;
}
export function maskCanvas(canvas: ExtCanvas, mask: ExtCanvas): ExtCanvas;
export function maskCanvas(canvas: ExtCanvas | undefined, mask: ExtCanvas | undefined): ExtCanvas | undefined;
export function maskCanvas(canvas: ExtCanvas | undefined, mask: ExtCanvas | undefined) {
if (!canvas || !mask)
return undefined;
const result = createExtCanvas(canvas.width, canvas.height, `${canvas.info} masked by ${mask.info}`);
const context = result.getContext('2d')!;
context.drawImage(canvas, 0, 0);
context.globalCompositeOperation = 'destination-in';
context.drawImage(mask, 0, 0);
return result;
}
export function colorCanvas(canvas: ExtCanvas, color: string): ExtCanvas;
export function colorCanvas(canvas: ExtCanvas | undefined, color: string): ExtCanvas | undefined;
export function colorCanvas(canvas: ExtCanvas | undefined, color: string): ExtCanvas | undefined {
const copy = copyCanvas(canvas);
if (copy) {
const context = copy.getContext('2d')!;
context.globalCompositeOperation = 'source-in';
context.fillStyle = color;
context.fillRect(0, 0, copy.width, copy.height);
}
return copy;
}
export function copyCanvas(canvas: ExtCanvas): ExtCanvas;
export function copyCanvas(canvas: ExtCanvas | undefined): ExtCanvas | undefined;
export function copyCanvas(canvas: ExtCanvas | undefined): ExtCanvas | undefined {
if (!canvas)
return undefined;
const newCanvas = createExtCanvas(canvas.width, canvas.height, `${canvas.info} (copy)`);
newCanvas.getContext('2d')!.drawImage(canvas, 0, 0);
return newCanvas;
}
export function recolorCanvas(canvas: ExtCanvas, color: string) {
const result = createExtCanvas(canvas.width, canvas.height, `recolorCanvas(${canvas.info}, ${color})`);
const context = result.getContext('2d')!;
context.fillStyle = color;
context.fillRect(0, 0, result.width, result.height);
context.globalCompositeOperation = 'destination-in';
context.drawImage(canvas, 0, 0);
return result;
}
export function createColorCanvas(width: number, height: number, color: string) {
const canvas = createExtCanvas(width, height, `createColorCanvas(${color})`);
const context = canvas.getContext('2d')!;
context.fillStyle = color;
context.fillRect(0, 0, canvas.width, canvas.height);
return canvas;
}
export function isCanvasEmpty(canvas: ExtCanvas | undefined): boolean {
if (canvas && canvas.width > 0 && canvas.height > 0) {
const context = canvas.getContext('2d')!;
const data = context.getImageData(0, 0, canvas.width, canvas.height);
const size = data.width * data.height * 4;
for (let i = 0; i < size; i++) {
if (data.data[i] !== 0) {
return false;
}
}
}
return true;
}
export function saveCanvas(filePath: string, canvas: HTMLCanvasElement) {
fs.writeFileSync(filePath, canvas.toBuffer());
}
function getColorAt(d: Uint8ClampedArray, i: number) {
return ((d[i] << 24) | (d[i + 1] << 16) | (d[i + 2] << 8) | d[i + 3]) >>> 0;
}
export function forEachPixel(canvas: ExtCanvas, action: (color: number, x: number, y: number) => void) {
const data = canvas.getContext('2d')!.getImageData(0, 0, canvas.width, canvas.height);
for (let y = 0, i = 0; y < data.height; y++) {
for (let x = 0; x < data.width; x++ , i += 4) {
action(getColorAt(data.data, i), x, y);
}
}
}
export function forEachPixelOf2Canvases(
canvas1: ExtCanvas, canvas2: ExtCanvas, action: (color1: number, color2: number, x: number, y: number) => void
) {
if (canvas1.width !== canvas2.width || canvas1.height !== canvas2.height) {
throw new Error('Canvas not the same size');
}
const data1 = canvas1.getContext('2d')!.getImageData(0, 0, canvas1.width, canvas1.height);
const data2 = canvas2.getContext('2d')!.getImageData(0, 0, canvas2.width, canvas2.height);
for (let y = 0, i = 0; y < data1.height; y++) {
for (let x = 0; x < data1.width; x++ , i += 4) {
action(getColorAt(data1.data, i), getColorAt(data2.data, i), x, y);
}
}
}
type MapColor = (color: number, x: number, y: number) => number;
export function mapEachPixel(canvas: ExtCanvas, action: MapColor) {
const context = canvas.getContext('2d')!;
const data = context.getImageData(0, 0, canvas.width, canvas.height);
const d = data.data;
for (let y = 0, i = 0; y < data.height; y++) {
for (let x = 0; x < data.width; x++ , i += 4) {
const c = ((d[i] << 24) | (d[i + 1] << 16) | (d[i + 2] << 8) | d[i + 3]) >>> 0;
const out = action(c, x, y);
d[i] = (out >>> 24) & 0xff;
d[i + 1] = (out >>> 16) & 0xff;
d[i + 2] = (out >>> 8) & 0xff;
d[i + 3] = out & 0xff;
}
}
context.putImageData(data, 0, 0);
}
export function mapColors(canvas: ExtCanvas, map: MapColor): ExtCanvas;
export function mapColors(canvas: ExtCanvas | undefined, map: MapColor): ExtCanvas | undefined;
export function mapColors(canvas: ExtCanvas | undefined, map: MapColor): ExtCanvas | undefined {
const result = copyCanvas(canvas);
if (result) {
mapEachPixel(result, map);
}
return result;
}
function compareTemplate(canvas: ImageData, template: ImageData, ox: number, oy: number) {
for (let y = 0; y < template.height; y++) {
for (let x = 0; x < template.width; x++) {
for (let i = 0; i < 4; i++) {
if (canvas.data[i + (x + ox) * 4 + (y + oy) * canvas.width * 4] !== template.data[i + x * 4 + y * template.width * 4]) {
return false;
}
}
}
}
return true;
}
export function findTemplate(canvas: HTMLCanvasElement, template: HTMLCanvasElement) {
const canvasData = canvas.getContext('2d')!.getImageData(0, 0, canvas.width, canvas.height);
const templateData = template.getContext('2d')!.getImageData(0, 0, template.width, template.height);
const maxX = canvas.width - template.width;
const maxY = canvas.height - template.height;
for (let y = 0; y <= maxY; y++) {
for (let x = 0; x <= maxX; x++) {
if (compareTemplate(canvasData, templateData, x, y)) {
return { x, y };
}
}
}
return null;
}
export function offsetCanvas(canvas: ExtCanvas | undefined, { x, y }: Point) {
return canvas && padCanvas(canvas, x, y);
}
export type CanvasGetter = (Canvas: ExtCanvas, col: number, row: number) => ExtCanvas;
export type ByIndexGetter = (Canvas: ExtCanvas, index: number) => ExtCanvas;
export function cropAndPadByColRow(
x: number, y: number, w: number, h: number, dx: number, dy: number, padLeft = 0, padTop = 0
): CanvasGetter {
return (canvas, col, row) => padCanvas(cropCanvas(canvas, x + dx * col, y + dy * row, w, h), padLeft, padTop);
}
export function cropByIndex(get: CanvasGetter, perLine: number): ByIndexGetter {
return (canvas, i) => get(canvas, i % perLine, Math.floor(i / perLine));
}
+234
View File
@@ -0,0 +1,234 @@
/// <reference path="../../typings/my.d.ts" />
import * as fs from 'fs';
import * as path from 'path';
import { spawn } from 'child_process';
import { reduce, flatten, map, dropRightWhile } from 'lodash';
import { Layer, Psd, ExtCanvas, Sprite, Rect, Result, ColorExtra } from './types';
import { ColorsOutput, imageToPalette } from './palette-utils';
import { getSpriteRect } from './sprite-sheet';
import { createColorCanvas, isCanvasEmpty } from './canvas-utils';
(global as any).DEVELOPMENT = true;
export const TRANSPARENT = 0;
export const BLACK = 0x000000ff;
export const WHITE = 0xffffffff;
export const OUTLINE_COLOR = 0x9f9f9fff;
export const SHADE_COLOR = 0xccccccff;
export const LIGHT_SHADE_COLOR = 0xddddddff;
export const TEAR_COLOR = 0xc1eef0ff;
export const DARK_GRAY = 0x232728ff;
export const MOUTH_COLOR = 0x721946ff;
export const TONGUE_COLOR = 0xf39f4bff;
export const TEETH_COLOR = 0x8cffffff;
export const TEETH_SHADE_COLOR = 0x77d9d9ff;
// TEMP: remove after adding palettes for effects
const holdPoofColors = [
0xffff47ff, 0xeaed58ff, 0xd8dc00ff, 0xff6741ff, 0xff0000ff, 0xff7af9ff, 0xff00ccff, 0xb67affff,
0x9876ffff, 0x76a6ffff, 0x0097ffff, 0x00ff9bff, 0x76ed5cff, 0x28dc00ff, 0x76ed5cff,
];
export const defaultPalette = [
TRANSPARENT, WHITE, BLACK, MOUTH_COLOR, TONGUE_COLOR, LIGHT_SHADE_COLOR, TEAR_COLOR, DARK_GRAY,
...holdPoofColors,
];
export function cartesian<A, B>(a: A[], b: B[]): [A, B][];
export function cartesian<A, B, C>(a: A[], b: B[], c: C[]): [A, B, C][];
export function cartesian(...args: any[]) {
return reduce<any[], any[][]>(args, (a, b) => flatten(map(a, x => map(b as any, y => x.concat([y])))), [[]]);
}
export function mkdir(dirpath: string) {
try {
fs.mkdirSync(dirpath);
} catch { }
}
const isDirectory = (dir: string) => fs.lstatSync(dir).isDirectory();
export const getDirectories = (dir: string) => fs.readdirSync(dir).map(name => path.join(dir, name)).filter(isDirectory);
function findLayerByPath([name, ...child]: string[], layer: Layer | Psd | undefined): Layer | undefined {
return name ? findLayerByPath(child, layer && findByName(layer.children, name)) : layer;
}
export function findLayer(path: string, layer: Layer | Psd | undefined): Layer | undefined {
return findLayerByPath(path.split('/'), layer);
}
export function findLayerSafe(name: string, parent: Layer | Psd): Layer {
const layer = findLayer(name, parent);
if (!layer) {
throw new Error(`Missing layer "${name}" in "${parent.info}"`);
}
return layer;
}
export function findByName<T extends { name?: string }>(items: T[], name: string): T | undefined {
return items.find(i => i.name === name);
}
export function findByIndex<T extends { index?: number }>(items: T[], index: number) {
return items.find(i => i.index === index);
}
export const nameMatches = (regex: RegExp) => (l: { name: string; }) => regex.test(l.name);
export function compareNames(a: { name: string }, b: { name: string }) {
return a.name.localeCompare(b.name);
}
export const time = (function () {
const start = Date.now();
let last = start;
return function (text: string) {
console.log(text, (Date.now() - last), 'ms');
last = Date.now();
return true;
};
})();
export function spawnAsync(command: string, args?: string[]) {
return new Promise<void>((resolve, reject) => {
spawn(command, args)
.on('error', (err: Error) => reject(err))
.on('exit', (code: number) => code === 0 ? resolve() : reject(new Error(`Non-zero return code for ${command} (${code})`)));
});
}
// canvas
export function getCanvas(layer: Layer | undefined): ExtCanvas | undefined {
if (!layer)
return undefined;
const canvas = layer.canvas;
if (canvas) {
canvas.info = layer.info;
}
return canvas;
}
export function getCanvasSafe(layer: Layer): ExtCanvas {
const canvas = getCanvas(layer);
if (!canvas) {
throw new Error(`Cannot find canvas in layer "${layer.info}"`);
}
return canvas;
}
export function getLayerCanvas(name: string, parent: Layer | Psd | undefined) {
return getCanvas(findLayer(name, parent));
}
export function getLayerCanvasSafe(name: string, parent: Layer | Psd): ExtCanvas {
return getCanvasSafe(findLayerSafe(name, parent));
}
export function parseWithNumber(name: string) {
const match = /(\d+)/.exec(name);
return parseInt(match ? match[1] : '0', 10);
}
export const matcher = (regex: RegExp) => (text: string) => regex.test(text);
const isArrayEmpty = <T>(a: T[] | null) => !a || a.length === 0;
const nullForEmpty = <T>(a: T[] | null) => isArrayEmpty(a) ? null : a;
export function trimRight<T>(items: ((T | null)[] | null)[]): ((T | null)[] | null)[] {
return dropRightWhile(items.map(nullForEmpty) as any, isArrayEmpty as any) as any;
}
// sprites
const redCanvas = createColorCanvas(1000, 1000, 'red');
export function addImage(images: HTMLCanvasElement[], canvas: HTMLCanvasElement) {
if (canvas) {
// TODO: remove duplicated
images.push(canvas);
return images.length - 1;
} else {
return 0;
}
}
export function createSprite(index: number, image: HTMLCanvasElement, { w, h, x, y }: Rect) {
return { index, image, w, h, x: 0, y: 0, ox: x, oy: y };
}
const maxSpriteWidth = 500;
const maxSpriteHeight = 500;
export function addSprite(
sprites: Sprite[], canvas?: ExtCanvas, pattern?: ExtCanvas, palette?: number[], out: ColorsOutput = {}
): number {
if (canvas) {
const rect = getSpriteRect(canvas, 0, 0, canvas.width, canvas.height);
if (rect.w && rect.h) {
if (rect.w > maxSpriteWidth || rect.h > maxSpriteHeight) {
throw new Error(`Sprite too large (${rect.w}, ${rect.h}) from [${canvas.info}]`);
}
const image = imageToPalette(rect, canvas, pattern || redCanvas, palette, out);
sprites.push(createSprite(sprites.length, image, rect));
return sprites.length - 1;
}
}
return 0;
}
export function addSpriteWithColors(
sprites: Sprite[], colorImage?: ExtCanvas, patternImage?: ExtCanvas, forceWhite?: boolean
): ColorExtra {
const out: ColorsOutput = { forceWhite };
const color = addSprite(sprites, colorImage, patternImage, undefined, out);
return { color, colors: out.colors! };
}
export function getColorsCount(colorImage?: ExtCanvas, patternImage?: ExtCanvas, forceWhite?: boolean): number {
const out: ColorsOutput = { forceWhite };
addSprite([], colorImage, patternImage, undefined, out);
return out.colors!;
}
export function createPixelSprites({ objects, objects2, images, sprites }: Result) {
const pixel = createColorCanvas(3, 3, 'white');
objects['pixelRect'] = addImage(images, pixel);
objects2['pixelRect2'] = addSprite(sprites, pixel, undefined, defaultPalette);
}
// layers
export const compareLayers = (a: Layer, b: Layer) => parseWithNumber(a.name) - parseWithNumber(b.name);
export function getPatternLayers(layer: Layer) {
return layer.children.filter(nameMatches(/^pattern/)).sort(compareLayers);
}
export function getPatternCanvases(layer: Layer): ExtCanvas[] {
const canvases = getPatternLayers(layer).map(getCanvas);
return dropRightWhile(canvases, isCanvasEmpty) as ExtCanvas[];
}
export function clipPattern(color: ExtCanvas, pattern: ExtCanvas | undefined): ExtCanvas | undefined {
if (pattern) {
const ctx = pattern.getContext('2d')!;
ctx.globalCompositeOperation = 'destination-in';
ctx.drawImage(color, 0, 0);
}
return pattern;
}
+41
View File
@@ -0,0 +1,41 @@
import { ExtCanvas } from './types';
import { cropCanvas } from './canvas-utils';
const tileWidth = 32;
const tileHeight = 24;
const cols = 10;
const tiles = [
47, 47, 0, 0, 13, 19, 21, 20, 15, 16,
47, 47, 0, 0, 13, 13, 45, 22, 18, 17,
9, 2, 2, 2, 10, 14, 14, 14, 35, 36,
8, 5, null, 7, 4, 27, 26, 29, 37, 38,
8, null, 46, null, 4, 28, 24, 30, 39, 40,
8, 3, null, 1, 4, 23, 31, 32, 41, 42,
12, 6, 6, 6, 11, 25, 33, 34, 43, 44,
];
interface Rev {
number: number;
index: number;
dstIndex: number;
}
const revtiles: Rev[] = tiles
.map((number, index) => ({ number, index, dstIndex: 0 }))
.filter(x => x.number != null) as Rev[];
revtiles.sort((a, b) => a.number - b.number);
revtiles.forEach((t, i) => t.dstIndex = i);
export function tilesToSprites(canvas: ExtCanvas, spaceH: number, spaceV: number) {
return revtiles
.sort((a, b) => a.dstIndex - b.dstIndex)
.map(t => {
const srcIndex = t.index;
const srcCol = srcIndex % cols;
const srcRow = Math.floor(srcIndex / cols);
const srcX = spaceH + srcCol * (tileWidth + spaceH);
const srcY = spaceV + srcRow * (tileHeight + spaceV);
return cropCanvas(canvas, srcX, srcY, tileWidth, tileHeight);
});
}
+385
View File
@@ -0,0 +1,385 @@
import { cropCanvas } from './canvas-utils';
import { compact } from 'lodash';
import { ExtCanvas } from './types';
export const CHINESE = [
'一丁七万丈三上下不与丐丑专且丕世丘丙业丛东丝丞両丢两严並丧个丫中',
'丰串临丸丹为主丼丽举乃久么义之乌乍乎乏乐乒乓乔乖乗乘乙九乞也习乡',
'书买乱乳乾亀了予争事二于亏云互五井亘亙亚些亜亞亟亡亢交亥亦产亨亩',
'享京亭亮亲亵人亿什仁仅仆仇今介仍从仏仑仓仔仕他仗付仙代令以仪们仮',
'仰仲件价任份仿企伊伍伎伏伐休众优伙会伝伞伟传伢伤伦伪伯估伴伶伸伺',
'似伽佃但位低住佐佑体何余佚佛作佟你佣佩佬佯佳併佼使侃侄來侈例侍侏',
'侑供依侠価侣侥侦侧侨侬侮侯侵侶便係促俄俊俏俐俗俘保俞俟俠信俣俨俩',
'俭修俯俱俳俵俸俺俾倉個倍倏倒倔倖倘候倚借倡倣値倦倩倪倫倭倶倹债值',
'倾假偉偌偎偏偕做停健偲側偵偶偷偽偿傀傅傍傑傘備傣储傩催傭傲傳債傷',
'傻傾僅働像僑僕僚僞僧僭僵價僻儀億儉儒償儡優儲儿兀允元兄充兆先光克',
'免兎児兑兒兔党兜兢入全八公六兮兰共关兴兵其具典兹养兼兽冀内円冈冉',
'冊册再冒冕冗写军农冠冢冤冥冨冬冯冰冲决冴况冶冷冻净凄准凉凋凌凍减',
'凑凛凜凝几凡凤処凧凪凭凯凰凱凳凶凸凹出击函凿刀刁刃分切刈刊刑划列',
'刘则刚创初删判別刨利别刮到制刷券刹刺刻刽剂剃則削剌前剑剔剖剛剣剤',
'剥剧剩剪副剰割創剿劇劈劉劍力劝办功加务劣动助努劫励劲劳労効劾势勁',
'勃勅勇勉勋勒動勘務勝募勢勤勧勲勳勺勾勿匀匁匂包匆匈匕化北匙匠匡匣',
'匪匮匹区医匾匿十千升午卉半华协卑卒卓協单卖南単博卜占卡卢卤卦卧卫',
'卯印危即却卵卷卸卿厂厄厅历厉压厌厕厘厚原厢厥厦厨厩厮厳去县参又叉',
'及友双反収发叔取受变叙叛叟叠叡叢口古句另叨叩只叫召叭叮可台叱史右',
'叶号司叹叼叽吁吃各吆合吉吊同名后吏吐向吓吕吗君吝吞吟吠否吧吨吩含',
'听吭吮启吱吴吵吸吹吻吼吾呀呂呃呆呈呉告呐呕呗员呛呜呟呢周呪呱味呵',
'呸呻呼命咀咄咆咋和咎咏咐咒咔咕咖咙咚咦咧咨咪咬咯咱咲咳咸咽哀品哄',
'哆哇哈哉响哎哑哒哗哝哟員哥哦哧哨哩哪哭哮哲哺哼哽唄唆唇唉唐唔唠唤',
'唧唬售唯唱唷唾啃啄商啊問啓啡啤啥啦啧啪啬啸啼喀喂喃善喇喉喊喋喔喘',
'喙喚喜喝喧喩喪喫喬單喰喳営喷喻喽嗅嗒嗓嗔嗖嗜嗟嗡嗣嗤嗦嗨嗯嗳嗽嘀',
'嘆嘈嘉嘎嘗嘘嘛嘟嘩嘱嘲嘴嘶嘻嘿噂噌噎噗噛噜噢器噩噪噬噴噶嚇嚎嚏嚓',
'嚣嚴嚷嚼囊囚四回因团団园困囱囲図围固国图圃圆圈國圏園圓團土圣圧在',
'圭地圳场圾址坂均坊坍坎坏坐坑块坚坛坝坞坟坠坡坤坦坪坯坷垂垃垄型垒',
'垢垣垦垫垮埃埋城埔埜域埠埴執培基埼堀堂堅堆堑堕堡堤堪堯堰報場堵堺',
'塀塁塊塌塑塔塗塘塙塚塞塩填塾境墅墓増墙墜增墟墨墩墳墾壁壇壊壌壑壕',
'壘壞壤士壬壮壯声壱売壳壶壷壽处备変复夏夕外多夜够夢大天太夫夭央失',
'头夷夸夹夺奂奄奇奈奉奋奎奏契奔奕奖套奚奠奢奥奧奨奪奬奮女奴奶奸她',
'好如妃妄妆妇妈妊妒妓妖妙妞妥妨妬妮妹妻妾姆姉姊始姐姑姓委姗姚姜姥',
'姦姨姪姫姬姻姿威娃娅娇娓娘娜娟娠娥娩娯娱娴娶娼婆婉婚婢婦婪婴婵婶',
'婷婿媒媚媛媲媳嫁嫂嫉嫌嫔嫖嫡嫣嫦嫩嬉嬢孃子孔孕字存孙孚孜孝孟季孤',
'学孩孫孰孵孽宁它宅宇守安宋完宏宕宗官宙定宛宜宝实実宠审客宣室宥宦',
'宪宫宮宰害宴宵家容宽宾宿寂寄寅密寇富寐寒寓寛寝寞察寡寢寥實寧寨審',
'寮寰寵寸对寺寻导対寿封専射将將專尉尊尋導小少尔尖尘尚尝尤尧尬尭就',
'尴尸尹尺尻尼尽尾尿局屁层居屈屉届屋屎屏屑展属屠屡層履屯山屹屿岁岂',
'岐岑岔岗岚岛岡岩岬岭岳岸峙峠峡峦峨峭峯峰島峻峽崇崎崔崖崚崛崩崭崽',
'嵋嵌嵐嵩嵯嶋嶺巅巌巍巖川州巡巢巣工左巧巨巩巫差己已巳巴巷巻巽巾币',
'市布帅帆师希帐帕帖帘帚帛帜帝帥带帧師席帮帯帰帳帶帷常帽幅幌幔幕幡',
'幢幣干平年并幸幹幻幼幽幾广庁広庄庆庇床序庐库应底店庙庚府庞废度座',
'庫庭庵庶康庸廃廉廊廓廖廟廣廳延廷建廻廿开弁异弃弄弊式弐弓弔引弗弘',
'弛弟张弥弦弧弩弯弱張強弹强弼弾彈彌归当录彗彙彝形彦彩彪彫彬彭彰影',
'彷役彻彼往征径待很徊律後徐徒従得徘徙從徠御復循微徳徴德徹徽心必忆',
'忌忍忏忒忖志忘忙応忠忡忧快忱念忽忿怀态怂怅怎怒怔怕怖怜思怠怡急怦',
'性怨怪怯总恃恆恋恍恐恒恕恙恚恢恣恤恥恨恩恪恬恭息恰恳恵恶恺恻恼恿',
'悄悉悌悍悔悖悚悟悠患悦您悩悪悬悯悲悴悸悻悼情惇惊惋惑惕惘惚惜惟惠',
'惡惣惦惧惨惩惫惬惭惮惯惰想惴惶惹惺愁愈愉意愕愚愛感愣愤愧愫愼愿慄',
'慈態慌慎慑慕慢慣慧慨慮慰慶慷憂憋憎憐憔憚憤憧憨憩憬憲憶憾懂懇懈應',
'懊懐懒懦懲懵懷懸懿戈戊戌戍戎戏成我戒或战戚戟戦截戮戯戰戲戳戴户戸',
'戻房所扁扇扈扉手才扎扑扒打扔払托扛扣执扩扫扬扭扮扯扰扱扳扶批扼找',
'承技抄抉把抑抒抓投抖抗折抚抛抜択抡抢护报抨披抬抱抵抹押抽抿拂担拆',
'拇拈拉拌拍拎拐拒拓拔拖拗拘拙拚招拜拝拟拠拡拢拣拥拦拧拨择括拭拯拱',
'拳拴拶拷拼拽拾拿持挂指按挑挖挙挚挛挝挟挠挡挣挤挥挨挪挫振挺挽挿捂',
'捅捆捉捍捎捏捐捕捗捜捞损捡换捣捧捨据捲捶捷捺捻掀掃授掉掌掏掐排掖',
'掘掛掠採探掣接控推掩措掬掲掳掴掷掺掻揃揉揍描提插揖揚換握揣揩揪揭',
'揮援揺揽搀搁搂搅損搏搐搓搔搖搜搞搬搭携搾摂摄摆摇摊摑摒摔摘摧摩摯',
'摸摹摺撂撃撇撑撒撕撚撞撤撩撫撬播撮撰撲撵撸撼擁擂擅擊操擎擒擞擢擦',
'擬攀攒攘攝攥攫支收攸改攻放政故效敌敍敏救敕敖敗教敛敝敞敢散敦敬数',
'敲整敵敷文斉斋斌斎斐斑斓斗料斜斟斡斤斥斧斩斬断斯新方於施旁旅旋族',
'旗无既日旦旧旨早旬旭旱时旷旺昂昆昇昊昌明昏易昔星映春昧昨昭是昴昼',
'显時晃晄晋晌晏晒晓晕晖晚晝晟晤晦晨晩普景晰晴晶智晾暁暂暄暇暉暑暖',
'暗暢暦暧暨暫暮暴曇曉曖曙曜曝曦曰曲曳更書曹曼曽曾替最月有朋服朔朕',
'朗望朝期朦木未末本札术朱朴朵机朽杀杂权杆杉李杏材村杖杜杞束杠条来',
'杨杭杯杰東杵杷松板极构枇枉析枕林枚果枝枠枢枣枪枫枭枯架枷柄柊柏某',
'柑染柔柘柚柜查柬柯柱柳柴柵査柾柿栃栄栅标栈栋栏树栓栖栗栞校栩株样',
'核根格栽桁桂桃桅框案桌桐桑桓桔桜桟桢档桥桦桧桨桩桶梁梅梓梗梛條梢',
'梦梧梨梭梯械梳梵梶检棄棉棋棍棒棕棘棚棟棠森棱棲棵棺椀椅椋植椎椒椛',
'検椭椰椿楊楓楔楕楚楞楠楢業楯極楷楼楽概榄榆榈榊榎榛榜榨榮榴榻槇構',
'槌槍槐様槙槛槻槽樂樊樋標樟模樣権横樫樱樵樹樺樽橄橋橘橙機橡橱檀檎',
'檐檜檢檬櫂櫓櫛櫻欄欠次欢欣欤欧欲欺欽款歇歉歌歎歓止正此步武歧歩歪',
'歯歳歴歹死歼殃殆殉殊残殖殡殴段殷殺殻殿毁毅毋母毎每毒毓比毕毗毘毙',
'毛毡毫毬毯氏民氓气氖気氛氟氢氣氦氧氨氮氯水氷永氾汀汁求汇汉汎汐汕',
'汗汚汛汝汞江池污汤汪汰汲汶汹決汽汾沁沂沃沈沉沌沐沓沖沙沛沟没沢沥',
'沦沧沪沫沮河沸油治沼沽沾沿況泄泉泊泌法泛泞泡波泣泥注泪泰泳泵泻泼',
'泽洁洋洒洗洛洞津洪洲洵洸活洼洽派流浄浅浆浇浊测济浏浑浒浓浙浚浜浦',
'浩浪浬浮浴海浸涂涅消涉涌涎涕涙涛涟涡涣涤润涧涨涩涯液涵涸涼淀淄淆',
'淋淌淑淖淘淡淤淨淫淮深淳淵混淹添清渇済渉渊渋渍渎渐渓渔渗渚減渝渠',
'渡渣渤渥渦温測渭港渲渴游渺湃湄湊湍湖湘湛湧湯湾湿満溃溅溉源準溜溝',
'溢溥溪溯溶溺滅滇滉滋滑滔滕滚滝滞满滤滥滨滩滯滴漁漂漆漉漏漓演漕漠',
'漢漣漩漪漫漬漱漳漸漾潇潔潘潜潟潤潦潭潮潰澁澄澈澎澜澡澪澱澳激濁濃',
'濒濕濠濡濫濯瀑瀕瀚瀛瀧瀬灌灘火灭灯灰灵灶灸灼災灾灿炉炊炎炒炕炖炙',
'炫炬炭炮炯炳炸点為炼炽烁烂烈烏烘烙烛烟烤烦烧烫烬热烹烽焉焕焘焙焚',
'無焦焰然焼煉煌煎煙煜煞煤照煩煮煽熄熊熏熔熙熟熬熱熹燃燈燎燒燕燥燦',
'燭燿爆爪爬爭爱爲爵父爷爸爹爽爾片版牌牒牙牛牟牡牢牧物牲牵特牺牽犀',
'犁犊犠犬犯状犹狂狄狈狐狒狗狙狞狠狡狩独狭狮狱狸狹狼猎猓猖猛猜猝猟',
'猩猪猫献猴猶猾猿獄獅獗獣獲獸玄率玉王玑玖玛玩玫环现玲玺玻珀珂珈珊',
'珍珑珠珥班現球琅理琉琏琐琛琢琥琦琪琳琴琵琶琼瑕瑙瑚瑛瑜瑞瑟瑠瑰瑳',
'瑶瑾璃璇璋璐璞璧環璽瓜瓢瓣瓦瓮瓶瓷甄甘甚甜生産甥用甩甫甭田由甲申',
'电男甸町画畅界畏畑畔留畜畝畠畢略番異畳畴畸畿疆疊疋疎疏疑疗疙疚疡',
'疣疤疫疮疯疲疵疹疼疾病症痉痊痒痔痕痘痛痢痩痪痰痴痹瘍瘟瘢瘤瘦瘩瘪',
'瘫瘸瘾療癌癒癖癫発登白百皂的皆皇皈皋皐皑皓皖皮皱皿盃盅盆盈益盎盏',
'盐监盒盔盖盗盘盛盜盟盡監盤目盯盲直相盼盾省眈眉看県眞真眠眨眩眯眶',
'眷眸眺眼着睁睐睑睛睡督睦睨睫睬睹睽睾睿瞄瞅瞎瞒瞟瞥瞧瞩瞪瞬瞭瞰瞳',
'瞻瞿矗矛矜矢矣知矩矫短矮矯石矶矿码砂砌砍研砕砖砥砦砧砰砲破砸砾础',
'硅硕硝硫硬确硯碁碉碌碍碎碑碓碗碘碟碧碩碰碱碳確碾磁磅磊磋磐磕磨磯',
'磷磺礁礎示礼社祀祁祇祈祉祐祕祖祝神祟祠祢祥票祭祯祷祸祺祿禀禁禄禅',
'禍禎福禧禪禮禰禱禹禺离禽禾秀私秃秉秋种科秒秘租秤秦秧秩积称秸移秽',
'稀程稍税稔稚稜稟稠稣種稲稳稷稻稼稽稿穀穂穆積穏穗穣穫穰穴究穷穹空',
'穿突窃窄窍窑窒窓窖窗窘窜窝窟窥窦窪窮窯窺窿立竖站竜竞竟章竣童竪竭',
'端競竹竺竿笃笆笈笋笑笔笙笛笠符笨第笹笺笼筆筈等筋筏筐筑筒答策筛筝',
'筵筷筹签简箇箋箍箔箕算管箫箭箱箸節範篇築篠篡篤篮篱篷簇簌簡簧簸簾',
'簿籍籠米类籽籾粉粋粒粗粘粛粟粤粥粧粪粮粱粹精糊糕糖糙糜糟糧糸系糾',
'紀約紅紊紋納紐純紗紘紙級紛素紡索紧紫紬累細紳紹紺終絃組絆経結絞絡',
'絢給絮統絵絶絹継続綜維綱網綴綸綺綻綾綿緊緋総緑緒線締編緩緯練緻縁',
'縄縛縞縣縦縫縮縱績繁繊繍織繕繡繫繭繰纂纏纖纠红纤约级纪纬纯纱纲纳',
'纵纶纷纸纹纺纽线练组绅细织终绊绍绎经绑绒结绕绘给绚绛络绝绞统绢绣',
'绥继绩绪绫续绮绯绰绳维绵绷绸综绽绿缀缄缅缆缉缎缓缔缕编缘缚缝缠缤',
'缩缪缭缮缰缴缶缸缺罐网罕罗罚罡罢罩罪置罰署罵罷羁羅羊羌美羔羚羞羡',
'群羨義羲羹羽翁翅翌翎習翔翘翟翠翩翰翻翼耀老考者耆而耍耐耕耗耳耶耸',
'耻耽耿聂聆聊聋职联聖聘聚聞聡聪聴職聽聿肃肆肇肉肋肌肖肘肚肛肝肠股',
'肢肤肥肩肪肮肯育肴肺肾肿胀胁胃胆背胎胖胚胜胞胡胤胥胧胰胱胳胴胶胸',
'胺能脂脅脆脇脈脉脊脏脐脑脓脖脚脩脯脱脳脸脹脾腊腋腎腐腑腓腔腕腥腫',
'腮腰腸腹腺腻腾腿膀膊膏膚膛膜膝膨膳膺臀臂臆臓臟臣臥臨自臭至致臻臼',
'舅舆與興舌舍舎舒舔舗舜舞舟航般舰舱舵舶舷船艇艘艦良艰色艳艶艺艾节',
'芋芒芙芜芝芥芦芬芭芯花芳芸芹芽苇苍苏苑苔苗苛苞苟若苦苯英苹苺茂范',
'茄茅茉茎茜茧茨茫茬茵茶茸茹荀荃荆草荐荒荔荘荡荣荧荫药荷荻荼莉莊莎',
'莞莫莱莲获莹莺莽菁菅菇菊菌菓菖菜菩菫華菱菲萃萄萊萌萍萎萝萠萤营萦',
'萧萨萩萬萱落葆葉著葛葡董葦葫葬葱葵葺蒂蒋蒐蒔蒙蒜蒲蒸蒼蓄蓉蓋蓑蓓',
'蓝蓦蓬蓮蔑蔓蔗蔚蔡蔣蔦蔬蔭蔵蔷蔼蔽蕃蕉蕊蕎蕗蕨蕪蕴蕾薄薇薗薙薛薦',
'薩薪薫薬薯藁藉藍藏藐藕藝藤藥藩藻蘇蘑蘭蘸虎虏虐虑虔虚虜虞虫虹虻虽',
'虾蚀蚁蚂蚊蚌蚕蛀蛇蛊蛋蛍蛙蛛蛟蛤蛮蛹蛾蜀蜂蜈蜒蜕蜗蜘蜜蜡蜷蜿蝇蝉',
'蝎蝗蝙蝠蝦蝴蝶螂螃螅融螺蟀蟆蟋蟒蟹蟾蠕蠟蠢血衅衆行衍術衔街衙衛衝',
'衞衡衣补表衫衬衰衷衿袁袄袅袈袋袍袒袖袜被袭袱袴裁裂装裏裔裕裘裙補',
'裝裟裡裤裳裴裸裹製裾褂複褐褒褚褥褪褶襄襖襟襲西要覆覇見規視覗覚覧',
'親観覽见观规觅视览觉觑角解触言訂訃計訊討訓託記訟訣訪設許訳訴診註',
'証詐詔評詞詠詢詣試詩詫詮詰話該詳詹誇誉誌認誓誕誘語誠誤説読誰課誼',
'調諄談請諏諒論諜諦諧諭諮諸諺諾謀謁謂謄謎謙講謝謠謡謹識譜警譬議譲',
'護讃讐讓计订认讥讨让讪训议讯记讲讳讶讷许讹论讼讽设访诀证诃评诅识',
'诈诉诊词诏译试诗诘诙诚诛话诞诠诡询诣该详诧诩诫诬语误诰诱诲说诵请',
'诸诺读诽课谀谁调谅谈谊谋谍谎谏谐谑谒谓谕谙谚谛谜谟谢谣谤谦谨谩谬',
'谭谱谴谷豁豆豊豚象豢豪豫豹貌貝貞負財貢貧貨販貪貫責貯貰貴買貸費貼',
'貿賀賃賄資賊賑賓賛賜賞賠賢賣賦質賭購贈贝贞负贡财责贤败账货质贩贪',
'贫贬购贮贯贰贱贴贵贷贸费贺贻贼贾贿赁赂赃资赋赌赎赏赐赓赔赖赘赚赛',
'赞赠赡赢赣赤赦赫走赳赴赵赶起趁超越趋趟趣足趴趾跃跄跋跌跑跚跛距跟',
'跡跤跨跪路跳践跷跺踉踊踌踏踝踞踢踩踪踱踵蹂蹄蹈蹊蹋蹑蹙蹟蹦蹬蹭蹲',
'蹴蹿躁躇躍躏身躬躯躲躺車軌軍軒軟転軸軽較載輔輝輩輪輯輸輿轄轉轍轟',
'车轧轨轩转轮软轰轲轴轻轼载轿较辄辅辆辈辉辐辑输辕辖辗辘辙辛辜辞辟',
'辣辨辩辫辰辱農边辺辻込辽达辿迁迂迄迅过迈迎运近返还这进远违连迟迢',
'迥迦迪迫迭述迷迸迹追退送适逃逆选逊逍透逐递逓途逗這通逛逝逞速造逢',
'連逮週進逵逸逻逼逾遁遂遅遇遊運遍過遏遐道達違遗遙遜遠遡遣遥適遭遮',
'遵遷選遺遼遽避邀邃還邑邓邢那邦邪邮邯邱邵邸邹邻郁郊郎郑郝郡部郭郵',
'郷都鄂鄙鄭酉酋酌配酎酒酔酝酢酣酥酪酬酮酱酵酶酷酸酿醇醉醋醍醐醒醜',
'醤醬醸釀采釈釉释里重野量金釘釜針釣釧鈍鈴鉄鉛鉢鉱鉴銀銃銅銑銘銚銭',
'鋒鋭鋳鋸鋼錆錐錘錠錦錫錬錯録鍋鍛鍬鍵鎌鎖鎧鎭鎮鏡鐘鑄鑑鑫针钉钊钓',
'钗钙钝钞钟钠钡钢钥钦钧钩钮钱钳钵钻钾铀铁铃铅铎铐铜铝铢铭铮铲银铸',
'铺链销锁锄锅锈锋锌锐错锚锡锢锣锤锥锦键锯锵锻镀镇镑镖镜镯镰镶長长',
'門閃閉開閏閑間関閣閤閥閲闇闘门闪闭问闯闲间闵闷闸闹闺闻闽阀阁阅阉',
'阎阐阑阔阙阜队阪阮阱防阳阴阵阶阻阿陀附际陆陇陈陋陌降限陕陛陡院陣',
'除陥陨险陪陰陳陵陶陷陸険陽隅隆隈隊隋階随隐隔隘隙際障隠隣隧險隶隷',
'隻隼难雀雁雄雅集雇雌雍雏雑雕雛雜離難雨雪雫雯雰雲雳零雷電雾需霄霆',
'震霉霊霍霎霓霖霜霞霧露霸霹青靖静靜非靠靡面革靳靴靶鞄鞅鞋鞍鞘鞠鞭',
'韓韦韧韩韬音韵韶韻響頁頂頃項順須頌預頑頒頓頗領頬頭頰頻頼題額顎顔',
'顕願顛類顧顯页顶顷项顺须顼顽顾顿颁颂预颅领颇颈颉颊颌颍颐频颓颖颗',
'题颚颜额颠颤風颯风飘飛飜飞食飢飯飲飼飽飾餅養餌餐餓館饗饥饬饭饮饰',
'饱饲饵饶饷饺饼饿馅馆馈馋馒首香馨馬馳馴駄駅駆駈駐駒駕駿騎騒験騰騷',
'驍驗驚马驭驮驯驰驱驳驴驶驹驻驼驾驿骂骄骆骇验骏骑骗骚骡骤骥骨骷骸',
'骼髄髅髓高髦髪髭髮鬓鬱鬼魁魂魄魅魏魔魚魯鮎鮮鯉鯛鯨鰯鱈鱒鱗鱼鲁鲍',
'鲜鲤鲨鲸鳃鳄鳌鳍鳖鳞鳥鳩鳳鳴鳶鴨鴻鵜鵬鶏鶴鷄鷗鷲鷹鷺鸟鸠鸡鸣鸥鸦',
'鸭鸯鸳鸽鸾鸿鹃鹅鹉鹊鹏鹤鹦鹰鹳鹿麒麓麗麟麦麹麺麻麽麿黄黎黏黑黒黔',
'默黙黛黝黯鼎鼓鼠鼻鼾齊齐齢齿龄龈龋龍龙龚龟 ',
].map(lengthChecker(32)).join('');
export const BASE_CHARS = [
' ☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼',
` !"#$%&'()*+,-./0123456789:;<=>?`,
'@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_',
'`abcdefghijklmnopqrstuvwxyz{|}~⌂',
].map(lengthChecker(32)).join('');
export const CHARS = [
' ☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼',
` !"#$%&'()*+,-./0123456789:;<=>?`,
'@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_',
'`abcdefghijklmnopqrstuvwxyz{|}~⌂',
'ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜø£Ø×ƒ',
'áíóúñѪº¿®¬½¼¡«»АаБбВвГгДдЕеЁёЖж',
'ЗзИиЙйКкЛлМмНнОоПпРрСсТтУуФфХхЦц',
'ЧчШшЩщЪъЫыЬьЭэЮюЯяĄąĆćĘęŃńŚśŹźŻż',
'ŁłÓÔŪū€·´°⚲⚥♡ß™ÁÃãÕõČčĎďĚěÍÚŇňŘř',
'ŠšŤťŮůÝýŽžĹ弾ŔŕĞğİıŞşÀÈÌÒÙЀЁЂЃЄ',
'ЅІЇЈЉЊЋЌЍЎЏѐёђѓєѕіїјљњћќѝўџѠѡѢѣѤ',
'ѥѰѱѲѳҊҋҌҍҎҏҐґ ĂăÂÎȘșȚțŊŋŐőŰűĀāĈĉ',
'■□▢▣▤▥▦▧▨▩▪▫▭▮▯▰▱△▴▵▶▷▸▹▻▽▾▿◀◁◂◃',
'◅◆◇◈◉◊◌◍◎●◐◑◒◓◔◕◖◗◚◛◜◝◞◟◠◡◢◣◤◥◦◧',
'◨◩◪◫◬◭◮◯◰◱◲◳◴◵◶◷◸◹◺◻◼◽◾◿¸¾ÊËÏÐÛÞ',
'ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩĊċĐđĒēĚě',
'αβγδεζηθικλμνξοπρστυφχψωĖėĜĝĠġĢģ',
'ĤĥĦħĨĩĪīĬĭĮįIJijĴĵĶķĸĻļĿŀŅņʼnŌōŎŏŒœ',
'ŖŗŜŝŢţŦŧŨũŬŭŲųŴŵŶŷŸſ¢¤¥¦¨©¯±¹²³µ',
'ð÷þĔĕ★☆✰✦✧卐卍❥ღஐϟ【】《》✿❀№♢♤♧✓✔✕✖✗✘',
'ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただ',
'ちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむ',
'めもゃやゅゆょよらりるれろゎわゐゑをんゔゕゖ⦅⦆。「」、・ ̄ˊς',
'ァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダ',
'チヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミム',
'メモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶヷヸヹヺ・ーヽヾヿ ',
'、。〃々〈〉「」『』〒〓〜〝〞〟〡〢〣〤〦〧〨〩〰〱〲〳〴〵 ',
'‐‑‒–—―‖‗‘’‚‛“”„‟‧⁇⁈⁉ ',
'⚧☿♁⚨⚩⚦⚢⚣⚤♔♕♚♛❣☀… ',
'👃🙂😵😠😐😑😆😟🙃 ',
' ',
' ',
].map(lengthChecker(32)).join('');
export const ROMAJI = [
'\uff00!"#$%&'()*+,-./0123456789:;<=>?',
'@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_',
'`abcdefghijklmnopqrstuvwxyz{|}~ ',
].map(lengthChecker(32)).join('');
export const EMOJI = [
'😍😈👿🤔 ',
'❤💙💚💛💜🖤💔💖💗💕 ',
'🦋🕷🦇👻🐾🐈🐱😺😸😹😻😼😽🙀😿😾',
'🌸💮🌲🎄💐🌿🍎🍏🍊🏀🎃🍕🍬🍭🍡🌈',
'💎🥌🎁⛄❄🕯🎅🌠⭐🌟🎲✨⚡🔥🎵🎶',
'♈♉♊♋♌♍♎♏♐♑♒♓⛎👑☘🍀',
'🍪🥚🍐🥭🥕🍇⛏🍌 ',
].map(lengthChecker(16)).join('');
function isSurrogate(code: number) {
return code >= 0xd800 && code <= 0xdbff;
}
function fromSurrogate(high: number, low: number) {
return ((high & 0x3ff) << 10) + (low & 0x3ff) + 0x10000;
}
export function charsToCodes(text: string) {
const chars: number[] = [];
for (let i = 0; i < text.length; i++) {
let code = text.charCodeAt(i);
if (isSurrogate(code) && (i + 1) < text.length) {
code = fromSurrogate(code, text.charCodeAt(i + 1));
i++;
}
chars.push(code);
}
return chars;
}
export interface FontSprite {
code: number;
sprite: number;
}
export function createFont(
canvas: ExtCanvas, w: number, h: number, addImage: (canvas: ExtCanvas) => number,
options: { noChinese?: boolean; mono?: number; onlyBase?: boolean; } = {},
): FontSprite[] {
const data = canvas.getContext('2d')!.getImageData(0, 0, canvas.width, canvas.height);
const cols = canvas.width / w;
const baseCodes = charsToCodes(BASE_CHARS);
const charsCodes = charsToCodes(CHARS);
const chineseCodes = options.noChinese ? [] : charsToCodes(CHINESE);
const romajiCodes = charsToCodes(ROMAJI);
const codes = options.onlyBase ? [...baseCodes] : [...charsCodes, ...chineseCodes, ...romajiCodes];
const baseCodesLength = charsCodes.length + chineseCodes.length;
const added = new Set<number>();
const arrows = new Set('↑↓'.split('').map(x => x.charCodeAt(0)));
return compact(codes
.map((code, i) => {
if (i >= baseCodesLength) {
i = 32 + (i - baseCodesLength);
}
const { x, y } = getXY(cols, i);
let { left, width } = getCharWidth(data, x, y, w, h);
const actualCode = i ? code : 0;
if (arrows.has(code)) {
left -= 1;
width += 2;
}
if (width !== 0 && options.mono !== undefined) {
left = 0;
width = options.mono;
}
if (!width) {
return undefined;
} else if (added.has(actualCode)) {
// console.log('skipping character', actualCode);
return undefined;
} else {
const sprite = addImage(cropCanvas(canvas, x * w + left, y * h, width - left, h));
added.add(actualCode);
return { code: actualCode, sprite };
}
}))
.sort(compareFontSprite);
}
export function createEmojis(
canvas: ExtCanvas, w: number, h: number, addImage: (canvas: ExtCanvas) => number
): FontSprite[] {
const data = canvas.getContext('2d')!.getImageData(0, 0, canvas.width, canvas.height);
const cols = canvas.width / w;
return compact(charsToCodes(EMOJI)
.map((code, i) => {
const { x, y } = getXY(cols, i);
const { left, width } = getCharWidth(data, x, y, w, h);
if (!code || code === 32) {
return undefined;
}
if (!width) {
console.log('emoji not found in PSD', code, width);
return undefined;
}
const sprite = addImage(cropCanvas(canvas, x * w + left, y * h, width - left, h));
return { code, sprite };
}))
.sort(compareFontSprite);
}
export function fontSpritesToStringAndSprites(fontSprites: FontSprite[]): { chars: string; sprites: number[]; } {
const chars = fontSprites.map(s => String.fromCodePoint(s.code)).join('');
const sprites = fontSprites.map(s => s.sprite);
return { chars, sprites };
}
function compareFontSprite(a: FontSprite, b: FontSprite) {
return a.code - b.code;
}
function isColEmpty(data: ImageData, x: number, y: number, h: number) {
for (let yy = 0; yy < h; yy++) {
if (data.data[((y + yy) * data.width + x) * 4 + 3])
return false;
}
return true;
}
function getXY(cols: number, i: number) {
return { x: i % cols, y: Math.floor(i / cols) };
}
function getCharWidth(data: ImageData, x: number, y: number, w: number, h: number) {
let left = 0;
let width = w;
for (; left < w; left++) {
if (!isColEmpty(data, x * w + left, y * h, h)) {
break;
}
}
for (; width > 0; width--) {
if (!isColEmpty(data, x * w + width - 1, y * h, h)) {
break;
}
}
return { left, width };
}
function lengthChecker(expected: number) {
return function (line: string) {
const length = charsToCodes(line).length;
if (length !== expected)
throw new Error(`Invalid line length (${length}/${expected}) in "${line}"`);
return line;
};
}
File diff suppressed because it is too large Load Diff
+54
View File
@@ -0,0 +1,54 @@
import * as fs from 'fs';
import * as path from 'path';
import { CHARS, ROMAJI, EMOJI, charsToCodes } from './create-font';
type Item = { name: string; }[];
const existing = new Set<number>();
const missing = new Map<number, number>();
const missingNames: string[] = [];
const rootPath = path.join(__dirname, '..', '..', '..', 'tools');
const items: Item = JSON.parse(fs.readFileSync(path.join(rootPath, 'names.json'), 'utf8'));
function isNonPrintableCharacter(code: number): boolean {
return code >= 0xfe00 && code <= 0xfe0f;
}
function ignore(code: number): boolean {
return (code >= 0x0180 && code <= 0x024F) // Latin Extended-B
|| (code >= 0x0600 && code <= 0x06FF) // Arabic
|| (code >= 0x2719 && code <= 0x2721) // crosses
|| (code >= 0x0300 && code <= 0x036F) // Combining Diacritical Marks
|| (code >= 0x2200 && code <= 0x22FF) // Mathematical Operators
|| (code >= 0x0E00 && code <= 0x0E7F) // Thai
|| (code >= 0x0250 && code <= 0x02AF) // IPA Extensions
|| (code >= 0x2460 && code <= 0x24FF) // Enclosed Alphanumerics
|| (code >= 0x2300 && code <= 0x23FF) // Miscellaneous Technical
;
}
charsToCodes(CHARS + ROMAJI + EMOJI).forEach(code => existing.add(code));
items.forEach(({ name }) => {
const codes = charsToCodes(name);
const missingChars = codes.reduce((count, code) => {
if (existing.has(code) || isNonPrintableCharacter(code))
return count;
const current = missing.get(code) || 0;
missing.set(code, current + 1);
return count + 1;
}, 0);
if (missingChars) {
missingNames.push(name);
}
});
const missingChars: [number, string, number][] = [];
missing.forEach((value, key) => missingChars.push([value, String.fromCodePoint(key), key]));
missingChars.sort(([a], [b]) => b - a);
fs.writeFileSync(path.join(rootPath, 'output', 'missing-names.txt'), missingNames.join('\n'), 'utf8');
fs.writeFileSync(path.join(rootPath, 'output', 'missing-chars.txt'), missingChars
.map(([a, b, c]) => `${a}: "${b}" (${c}) [U+${c.toString(16)}]${ignore(c) ? ' (ignore)' : ''}`).join('\n'), 'utf8');
+128
View File
@@ -0,0 +1,128 @@
import { Rect, ExtCanvas } from './types';
import { cropCanvas, createExtCanvas } from './canvas-utils';
const validPatternColors = [
255, // red
255 << 8, // green
255 | (255 << 8), // yellow
255 << 16, // blue
255 | (255 << 16), // purple
(255 << 8) | (255 << 16), // teal
255 | (255 << 8) | (255 << 16), // white
0, // black
];
const getAlpha = (alpha: number) => alpha < 15 ? false : (alpha > 250 ? true : null);
const isOutline = (shade: number) => shade <= 159;
const round255 = (n: number) => n > 200 ? 255 : (n < 50 ? 0 : n);
const rgbToColor = (r: number, g: number, b: number) => round255(r) | (round255(g) << 8) | (round255(b) << 16);
const findValidPatternColor = (r: number, g: number, b: number) => validPatternColors.indexOf(rgbToColor(r, g, b));
function getShade(shade: number) {
// outlines
// 135 - dark outline
// 159 - outline
// fills
// 174 - dark shades
// 204 - shade
// 217 - dark fill
// 255 - fill
if (shade > 130 && shade < 140) { // dark outline
return 135;
} else if (shade > 150 && shade < 165) { // outline
return 159;
} else if (shade > 165 && shade < 180) { // dark shade
return 174;
} else if (shade > 190 && shade < 210) { // shade
return 204;
} else if (shade > 210 && shade < 220) { // dark fill
return 217;
} else if (shade > 245) { // fill
return 255;
} else {
return -1;
}
}
function getShadeForShading(shade: number) {
if (shade === 135) { // dark outline
return 204;
} else if (shade === 159) { // outline
return 255;
} else {
return shade;
}
}
function pixel(data: Uint8ClampedArray, index: number) {
return data.slice(index, index + 4).join(', ');
}
export interface ColorsOutput {
colors?: number;
forceWhite?: boolean;
}
export function imageToPalette(
rect: Rect, image: ExtCanvas, pattern: ExtCanvas, palette: number[] | undefined, config: ColorsOutput
) {
const pat = cropCanvas(pattern, 0, 0, image.width, image.height);
const ctx = pat.getContext('2d')!;
ctx.globalCompositeOperation = 'destination-in';
ctx.drawImage(image, 0, 0);
const imageData = image.getContext('2d')!.getImageData(rect.x, rect.y, rect.w, rect.h);
const pattData = ctx.getImageData(rect.x, rect.y, rect.w, rect.h);
const length = imageData.width * imageData.height * 4;
const data = imageData.data;
const pdata = pattData.data;
let maxIndex = 0;
for (let i = 0; i < length; i += 4) {
const x = ((i / 4) % rect.w) + rect.x;
const y = Math.floor((i / 4) / rect.h) + rect.y;
if (palette) {
const color = ((data[i] << 24) | (data[i + 1] << 16) | (data[i + 2] << 8) | data[i + 3]) >>> 0;
const index = palette.indexOf(color);
if (index === -1) {
throw new Error(`Invalid color (palette) [${pixel(data, i)}] [0x${color.toString(16)}] (${x} ${y}) (${image.info})`);
}
data[i] = index;
data[i + 1] = 255;
} else {
const alpha = getAlpha(data[i + 3]);
const shade = getShade(data[i]);
const index = findValidPatternColor(pdata[i], pdata[i + 1], pdata[i + 2]);
if (index === -1)
throw new Error(`Invalid color (pattern) [${pixel(pdata, i)}] (${x} ${y}) (${pat.info})`);
if (alpha == null)
throw new Error(`Invalid color [${pixel(data, i)}] (${x} ${y}) (${image.info})`);
const actualIndex = (index * 2) + (isOutline(shade) ? 2 : 1);
const shadeForShading = getShadeForShading(shade);
data[i] = alpha ? actualIndex : 0;
data[i + 1] = (config.forceWhite && shade !== -1) ? 255 : (alpha ? shadeForShading : 255);
if (alpha) {
maxIndex = Math.max(maxIndex, index);
}
}
data[i + 2] = 0;
data[i + 3] = 255;
}
config.colors = palette ? palette.length : ((maxIndex + 1) * 2 + 1);
const result = createExtCanvas(image.width, image.height, `${image.info} (image to palette)`);
result.getContext('2d')!.putImageData(imageData, rect.x, rect.y);
return result;
}
+68
View File
@@ -0,0 +1,68 @@
import * as fs from 'fs';
import * as path from 'path';
import { Layer as PsdLayer, Psd as PsdFile, readPsd, initializeCanvas } from 'ag-psd';
import { Layer, Psd } from './types';
import { createExtCanvas } from './canvas-utils';
import { matcher, parseWithNumber } from './common';
initializeCanvas((width, height) => createExtCanvas(width, height, 'loaded from psd'));
export function openPsd(filePath: string) {
try {
const buffer = fs.readFileSync(filePath);
const name = path.basename(filePath, '.psd');
const dir = path.basename(path.dirname(filePath));
const psd = readPsd(buffer, {
skipCompositeImageData: true,
skipThumbnail: true,
throwForMissingFeatures: true,
logMissingFeatures: true,
});
return toPsd(psd, name, dir);
} catch (e) {
console.error(`Failed to load: ${filePath}: ${e.message}`);
throw e;
}
}
function toPsd({ width, height, children }: PsdFile, name: string, dir: string): Psd {
const info = `${dir}/${name}`;
return {
dir, name, width, height, info, children: (children || []).map(c => toLayer(c, width, height, info)),
};
}
function toLayer({ name, canvas, left, top, children }: PsdLayer, width: number, height: number, parentInfo: string): Layer {
const info = `${parentInfo}/${name}`;
return {
name: name || '<noname>',
info,
canvas: fixCanvas(canvas, width, height, left || 0, top || 0, info),
children: (children || []).map(c => toLayer(c, width, height, info)),
};
}
function fixCanvas(
canvas: HTMLCanvasElement | undefined, width: number, height: number, left: number, top: number, info: string
) {
if (!canvas)
return undefined;
const result = createExtCanvas(width, height, info);
result.getContext('2d')!.drawImage(canvas, left, top);
return result;
}
const isPsd = matcher(/\.psd$/);
export const getPsds = (directory: string) => fs.readdirSync(directory).filter(isPsd);
export function openPsdFiles(directory: string, match?: RegExp) {
return getPsds(directory)
.filter(f => match ? match.test(f) : true)
.sort((a, b) => parseWithNumber(a) - parseWithNumber(b))
.map(f => path.join(directory, f))
.map(openPsd);
}
+492
View File
@@ -0,0 +1,492 @@
import * as fs from 'fs';
import * as path from 'path';
import { times } from 'lodash';
import { Sprite, Rect } from './types';
import { findByIndex } from './common';
import { removeItem } from '../common/utils';
import { createExtCanvas, saveCanvas } from './canvas-utils';
interface ExtSprite extends Sprite {
index?: number;
layers?: number;
duplicateOf?: ExtSprite;
overlays?: ExtSprite[];
overlayedOn?: ExtSprite;
data?: ImageData;
}
function isIdenticalSprite(a: ExtSprite | undefined, b: ExtSprite | undefined): boolean {
return !!(a && b && a.data && b.data && isIdenticalData(a.data, b.data));
}
function isIdenticalData(a: ImageData, b: ImageData) {
if (a.width !== b.width || a.height !== b.height)
return false;
const length = (a.width * a.height * 4) | 0;
const adat = a.data;
const bdat = b.data;
for (let i = 0; i < length; i = (i + 1) | 0) {
if (adat[i] !== bdat[i]) {
return false;
}
}
return true;
}
function isIdenticalChannel(a: Sprite | undefined, b: Sprite | undefined, channel: number) {
if (!a || !b || a.w !== b.w || a.h !== b.h)
return false;
const adata = a.image.getContext('2d')!.getImageData(a.ox, a.oy, a.w, a.h);
const bdata = b.image.getContext('2d')!.getImageData(b.ox, b.oy, b.w, b.h);
const length = (adata.width * adata.height * 4) | 0;
const adat = adata.data;
const bdat = bdata.data;
for (let i = channel | 0; i < length; i = (i + 4) | 0) {
if (adat[i] !== bdat[i]) {
return false;
}
}
return true;
}
function trimImageData(data: ImageData): Rect {
const width = data.width | 0;
const imageData = data.data;
let top = 0;
let left = 0;
let right = data.width | 0;
let bottom = data.height | 0;
function isEmpty(x: number, y: number) {
return imageData[((getIndex(x, y, width) << 2) + 3) | 0] === 0;
}
function isRowEmpty(y: number) {
for (let x = left | 0; x < right; x = (x + 1) | 0) {
if (!isEmpty(x | 0, y | 0)) {
return false;
}
}
return true;
}
function isColEmpty(x: number) {
for (let y = top | 0; y < bottom; y = (y + 1) | 0) {
if (!isEmpty(x | 0, y | 0)) {
return false;
}
}
return true;
}
while (bottom > top && isRowEmpty(bottom - 1))
bottom--;
while (right > left && isColEmpty(right - 1))
right--;
while (top < bottom && isRowEmpty(top))
top++;
while (left < right && isColEmpty(left))
left++;
return { y: top, x: left, w: right - left, h: bottom - top };
}
export function getSpriteRect(canvas: HTMLCanvasElement, x: number, y: number, w: number, h: number): Rect {
const data = canvas.getContext('2d')!.getImageData(x, y, w, h);
const rect = trimImageData(data);
return { x: x + rect.x, y: y + rect.y, w: rect.w, h: rect.h };
}
export function imageToSprite(image: HTMLCanvasElement, index: number): ExtSprite {
const { w, h, x, y } = getSpriteRect(image, 0, 0, image.width, image.height);
return { image, index, w, h, x: 0, y: 0, ox: x, oy: y };
}
function getIndex(x: number, y: number, outputWidth: number) {
return ((x | 0) + (((y | 0) * outputWidth) | 0)) | 0;
}
function isEmpty(x: number, y: number, w: number, h: number, outputWidth: number, taken: Uint8Array) {
outputWidth = outputWidth | 0;
if (((x + w) | 0) > outputWidth || ((y + h) | 0) > outputWidth) {
return false;
}
for (let iy = 0; iy < h; iy++) {
for (let ix = 0; ix < w; ix++) {
if (taken[getIndex((ix + x) | 0, (iy + y) | 0, outputWidth)] !== 0) {
return false;
}
}
}
return true;
}
interface Line {
start: number;
length: number;
}
interface Taken {
lines: Line[][];
data: Uint8Array; // TODO: remove completely, just use lines
}
function getFirstFree(outputWidth: number, width: number, height: number, { data, lines }: Taken) {
const maxY = (outputWidth - height) | 0;
for (let y = 0; y < maxY; y = (y + 1) | 0) {
const spans = lines[y];
for (let i = 0; i < spans.length; i++) {
const span = spans[i];
const start = span.start | 0;
const end = (start + span.length - width) | 0;
for (let x = start; x < end; x = (x + 1) | 0) {
if (isEmpty(x, y, width, height, outputWidth, data)) {
return { x, y, layer: 0 };
}
}
}
}
throw new Error(`Cannot find free space for (${width}, ${height}) [getFirstFree]`);
}
function getFirstFreePacked(outputWidth: number, width: number, height: number, takens: Taken[]) {
const maxY = outputWidth - height;
for (let layer = 0; layer < takens.length; layer = (layer + 1) | 0) {
const { data, lines } = takens[layer];
for (let y = 0; y < maxY; y = (y + 1) | 0) {
const spans = lines[y];
for (let i = 0; i < spans.length; i++) {
const span = spans[i];
const start = span.start | 0;
const end = (start + span.length - width) | 0;
for (let x = start | 0; x < end; x = (x + 1) | 0) {
if (isEmpty(x, y, width, height, outputWidth, data)) {
return { x, y, layer };
}
}
}
}
}
throw new Error(`Cannot find free space for (${width}, ${height}) [getFirstFreePacked]`);
}
function positionSprite(sprite: ExtSprite, outputWidth: number, taken: Taken[], pack: boolean) {
const layers = sprite.layers || 1;
const { x, y, layer } = (pack && layers === 1) ?
getFirstFreePacked(outputWidth, sprite.w, sprite.h, taken) :
getFirstFree(outputWidth, sprite.w, sprite.h, taken[0]);
sprite.x = x;
sprite.y = y;
sprite.layer = layer;
const w = sprite.w;
const right = x + w;
for (let il = 0; il < layers; il++) {
const { data, lines } = taken[il + layer];
for (let iy = 0; iy < sprite.h; iy++) {
const yy = y + iy;
const spans = lines[yy];
for (let ix = 0; ix < sprite.w; ix++) {
const xx = x + ix;
data[getIndex(xx, yy, outputWidth)] = 1;
}
for (let i = 0; i < spans.length; i++) {
const span = spans[i];
const start = span.start;
const length = span.length;
const end = start + length;
if (start >= right) // right of span
break;
if (end <= right) // left of span
continue;
if (start === x) {
if (length === w) { // entire span
spans.splice(i, 1);
} else { // at the start of span
span.start += w;
span.length -= w;
}
} else {
if (end === right) { // at the end of span
span.length -= w;
} else { // in the middle of span
span.length = x - start;
spans.splice(i + 1, 0, { start: right, length: end - right });
}
}
// let start = span.start;
// let lineMoved = false;
// for (let ix = 0; ix < sprite.w; ix++) {
// const xx = x + ix;
// data[getIndex(xx, yy, outputWidth)] = 1;
// if (xx === start) {
// start++;
// lineMoved = true;
// }
// }
// if (lineMoved) {
// for (let x = start; x < outputWidth && data[getIndex(x, yy, outputWidth)] !== 0; x++) {
// start++;
// }
// span.start = start;
// span.length = outputWidth - start;
// }
}
}
}
}
function hasShading(s: Sprite) {
const data = s.image.getContext('2d')!.getImageData(s.ox, s.oy, s.w, s.h);
for (let y = 0; y < data.height; y++) {
for (let x = 0; x < data.width; x++) {
if (data.data[(x + y * data.width) * 4 + 1] !== 0xff) {
return true;
}
}
}
return false;
}
function getSpriteImageData(s: ExtSprite): ImageData | undefined {
if (!s.w || !s.h)
return undefined;
const context = s.image.getContext('2d')!;
const { width, height, data } = context.getImageData(s.ox, s.oy, s.w, s.h);
return { width, height, data };
}
export function createSpriteSheet(name: string, images: ExtSprite[], log: boolean, size: number, bg?: string, pack = false) {
const maxLayers = 4;
const sprites = images.slice();
let outputWidth = size;
let maxY = 0;
let areaTaken = 0;
let deduplicated = 0;
let layered = 0;
let tooBig = sprites.find(s => s.w >= outputWidth);
while (tooBig) {
throw new Error(`Sprite too large (${tooBig.w}x${tooBig.h}) in ${name} (${size}x${size})`);
}
sprites
.forEach(s => s.data = getSpriteImageData(s));
sprites
.forEach((s, i) => {
if (s.w && s.h) {
for (let j = 0; j < i; j++) {
if (!s.duplicateOf && isIdenticalSprite(sprites[j], s)) {
s.duplicateOf = sprites[j];
deduplicated++;
//console.log('duplicate', sprite.name, '==', sprites[j].name);
break;
}
}
}
});
sprites
.filter(s => !s.duplicateOf && s.w && s.h)
.forEach(s => {
s.shade = !pack || hasShading(s);
s.layers = s.shade ? 2 : 1;
});
if (pack) {
sprites
.filter(s => !s.duplicateOf && s.shade)
.reduce((pool, sprite) => {
const match = pool.find(d => isIdenticalChannel(sprite, d, 1));
if (match) {
match.overlays = match.overlays || [];
match.overlays.push(sprite);
sprite.layer = match.layers!;
sprite.overlayedOn = match;
match.layers = match.layers! + 1;
if (match.layers >= maxLayers) {
removeItem(pool, match);
}
layered++;
} else {
pool.push(sprite);
}
return pool;
}, [] as ExtSprite[]);
}
sprites.sort((a, b) => ((b.layers || 1) - (a.layers || 1)) || ((b.h * 1024 + b.w) - (a.h * 1024 + a.w)));
maxY = 0;
areaTaken = 0;
const taken: Taken[] = times(maxLayers, () => ({
lines: times(outputWidth, () => [{ start: 0, length: outputWidth }]),
data: new Uint8Array(outputWidth * outputWidth),
}));
sprites
.filter(s => !s.duplicateOf && !s.overlayedOn)
.forEach(s => {
try {
positionSprite(s, outputWidth, taken, pack);
maxY = Math.max(maxY, s.y + s.h);
areaTaken += s.w * s.h;
} catch (e) {
console.error(e);
}
});
if (maxY > outputWidth) {
throw new Error(`Exceeded sprite sheet size for ${name} (${size}x${size})`);
}
if (log) {
const efficiency = (areaTaken * 100 / outputWidth / maxY).toFixed();
console.log(
`[sprites] [${name}] Packed ${sprites.length} sprites into ${outputWidth} x ${maxY} sheet, `
+ `${efficiency}% efficiency, ${deduplicated} deduplicated, ${layered} layered`);
}
sprites
.filter(s => s.overlayedOn)
.forEach(s => {
s.x = s.overlayedOn!.x;
s.y = s.overlayedOn!.y;
});
sprites
.filter(s => s.duplicateOf)
.forEach(s => {
s.x = s.duplicateOf!.x;
s.y = s.duplicateOf!.y;
s.layer = s.duplicateOf!.layer;
s.shade = s.duplicateOf!.shade;
});
const image = createExtCanvas(outputWidth, outputWidth, 'sprite sheet image');
const alpha = createExtCanvas(outputWidth, outputWidth, 'sprite sheet alpha');
const context = image.getContext('2d')!;
const alphaContext = alpha.getContext('2d')!;
if (bg) {
context.fillStyle = bg;
context.fillRect(0, 0, outputWidth, outputWidth);
alphaContext.fillStyle = bg;
alphaContext.fillRect(0, 0, outputWidth, outputWidth);
}
if (pack) {
const data = context.getImageData(0, 0, image.width, image.height);
const alphaData = alphaContext.getImageData(0, 0, image.width, image.height);
sprites
.filter(s => !s.duplicateOf && s.w && s.h)
.forEach(s => {
if (s.layer === 3) {
drawChannel(s.image, alphaData, 0, 0, s.ox, s.oy, s.x, s.y, s.w, s.h);
} else {
drawChannel(s.image, data, 0, s.layer || 0, s.ox, s.oy, s.x, s.y, s.w, s.h);
}
if (s.shade) {
drawChannel(s.image, data, 1, 1, s.ox, s.oy, s.x, s.y, s.w, s.h);
}
});
context.putImageData(data, 0, 0);
alphaContext.putImageData(alphaData, 0, 0);
} else {
sprites
.filter(s => !s.duplicateOf && s.w && s.h)
.forEach(s => context.drawImage(s.image, s.ox, s.oy, s.w, s.h, s.x, s.y, s.w, s.h));
}
return {
sprites: images.map((_, index) => findByIndex(sprites, index) || null),
image,
alpha,
};
}
function drawChannel(
src: HTMLCanvasElement, dst: ImageData, srcChannel: number, dstChannel: number,
sx: number, sy: number, dx: number, dy: number, w: number, h: number
) {
const srcData = src.getContext('2d')!.getImageData(sx, sy, w, h);
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
dst.data[((x + dx) + (y + dy) * dst.width) * 4 + dstChannel] =
srcData.data[(x + y * srcData.width) * 4 + srcChannel];
}
}
}
export function saveSpriteSheet(filePath: string, canvas: HTMLCanvasElement) {
saveCanvas(filePath, canvas);
return path.basename(filePath);
}
export function saveSpriteSheetAsBinary(filePath: string, canvas: HTMLCanvasElement) {
const context = canvas.getContext('2d')!;
const data = context.getImageData(0, 0, canvas.width, canvas.height);
fs.writeFileSync(filePath, Buffer.from(data.data.buffer));
}
export function saveCanvasAsRaw(filePath: string, canvas: HTMLCanvasElement) {
const buffer = Buffer.alloc(4 + 4 + 4 + 4 * canvas.width * canvas.height);
buffer.writeUInt8('R'.charCodeAt(0), 0);
buffer.writeUInt8('A'.charCodeAt(0), 1);
buffer.writeUInt8('W'.charCodeAt(0), 2);
buffer.writeUInt8(' '.charCodeAt(0), 3);
buffer.writeUInt32LE(canvas.width, 4);
buffer.writeUInt32LE(canvas.height, 8);
const data = canvas.getContext('2d')!.getImageData(0, 0, canvas.width, canvas.height);
buffer.set(data.data, 12);
fs.writeFileSync(filePath, buffer);
}
+195
View File
@@ -0,0 +1,195 @@
// generated file
/* tslint:disable */
import {
Sprite, SpriteBorder, PonyNose, PonyEye, ColorExtra, ColorExtraSets, Shadow, ColorShadow, TileSprites, SpriteSheet
} from '../common/interfaces';
import { parseSpriteColor } from '../common/utils';
import { bitReaderCustom } from '../common/bitUtils';
// [frame][type][pattern]
export type StaticSprites = (ColorExtra[] | undefined)[];
export type AnimatedSprites = (StaticSprites | undefined)[];
export type StaticSpritesExtra = (ColorExtra[] | undefined)[];
export type AnimatedSpritesExtra = (StaticSprites | undefined)[];
export type PonyEyes = ((PonyEye | undefined)[] | undefined)[];
const sprites = createSprites('/*SPRITES*/');
const sprites2 = createSprites('/*SPRITES_PALETTE*/');
/*FONTS*/
const palettes: Uint32Array[] = createPalettes('/*COLORS*/', [
/*PALETTES*/
]);
/*NAMED_SPRITES*/
/*NAMED_PALETTES*/
export const spriteSheets: SpriteSheet[] = [
{
src: '/*SPRITE_SHEET*/',
data: undefined, texture: undefined, sprites: sprites, palette: false
},
{
src: '/*SPRITE_SHEET_PALETTE*/', srcA: '/*SPRITE_SHEET_PALETTE_ALPHA*/',
data: undefined, texture: undefined, sprites: sprites2, palette: true
},
];
export const normalSpriteSheet = spriteSheets[0];
export const paletteSpriteSheet = spriteSheets[1];
export function createSprites(data: string): Sprite[] {
const sprites: Sprite[] = [
{ x: 0, y: 0, w: 0, h: 0, ox: 0, oy: 0, type: 0 },
];
let offset = 0;
const read = bitReaderCustom(() => {
const value = parseInt(data.substr(offset, 2), 16);
offset += 2;
return value;
});
while (offset < data.length) {
sprites.push({
x: read(12),
y: read(12),
w: read(9),
h: read(9),
ox: read(8),
oy: read(8),
type: read(6),
});
}
return sprites;
}
export function createFont(sprites: Sprite[], groups: [number, number[]][]) {
const chars: { code: number; sprite: Sprite; }[] = [];
for (const [start, codes] of groups) {
for (let i = 0; i < codes.length; i++) {
if (codes[i]) {
chars.push({ code: start + i, sprite: sprites[codes[i]] });
}
}
}
return chars;
}
export function createButton(
border: number, topLeft: number, top: number, topRight: number, left: number, bg: number, right: number,
bottomLeft: number, bottom: number, bottomRight: number
): SpriteBorder {
return {
border,
topLeft: sprites[topLeft],
top: sprites[top],
topRight: sprites[topRight],
left: sprites[left],
bg: sprites[bg],
right: sprites[right],
bottomLeft: sprites[bottomLeft],
bottom: sprites[bottom],
bottomRight: sprites[bottomRight]
};
}
export function mapSprites(frames: number[]) {
return frames.map(i => sprites[i]);
}
export function mapSprites2(frames: number[]) {
return frames.map(i => sprites2[i]);
}
export function createPalettes(colorsString: string, palettes: number[][]): Uint32Array[] {
const colors = colorsString.split(/ /g).map(parseSpriteColor);
return palettes.map(palette => {
const result = new Uint32Array(palette.length);
for (let i = 0; i < palette.length; i++) {
result[i] = colors[palette[i]] >>> 0;
}
return result;
});
}
export function createColorPal(color: number, colors: number): ColorExtra {
return { color: sprites2[color], colors };
}
export function colorPal(colors: number) {
return (color: number) => createColorPal(color, colors);
}
const colorPal3 = colorPal(3);
const colorPal5 = colorPal(5);
const colorPal7 = colorPal(7);
const colorPal9 = colorPal(9);
const colorPal11 = colorPal(11);
const colorPal13 = colorPal(13);
const colorPal17 = colorPal(17);
export function getPalette(index: number) {
return palettes[index];
}
const emptyPalette = new Uint32Array(0);
export function emptyColorPalette(): ColorExtra {
return { color: sprites2[0], palettes: [emptyPalette] };
}
export function createSpritesPalette(sprites: number[], paletteIndexes: number[]): TileSprites {
return { sprites: sprites.map(i => sprites2[i]), palettes: paletteIndexes.map(getPalette) };
}
export function createColorPalette(color: number, paletteIndexes: number[]): ColorExtra {
return { color: sprites2[color], palettes: paletteIndexes.map(getPalette) };
}
export function createColorExtraPal(color: number, colors: number, extra: number, paletteIndexes: number[]): ColorExtra {
return { color: sprites2[color], colors, extra: sprites2[extra], palettes: paletteIndexes.map(getPalette) };
}
export function createShadow(shadow: number): Shadow {
return { shadow: sprites2[shadow] };
}
export function createColorShadowPalette(color: number, shadow: number, paletteIndexes: number[]): ColorShadow {
return { color: sprites2[color], shadow: sprites2[shadow], palettes: paletteIndexes.map(getPalette) };
}
export function createNose(color: number, colors: number, mouth: number, fangs: number) {
return { color: sprites2[color], colors, mouth: sprites2[mouth], fangs: sprites2[fangs] };
}
export function createEye(base: number, irises: number[], shadow?: number, shine?: number): PonyEye {
return { base: sprites2[base], irises: mapSprites2(irises), shadow: sprites2[shadow || 0], shine: sprites2[shine || 0] };
}
export function createAnimation(frames: number[]) {
return { frames: mapSprites(frames) };
}
export function createAnimationPalette(frames: number[], palette: number) {
return { frames: mapSprites2(frames), palette: getPalette(palette) };
}
export function createAnimationShadow(frames: number[], shadow: number, palette: number) {
return { frames: mapSprites2(frames), shadow: sprites2[shadow], palette: getPalette(palette) };
}
export {
Sprite, SpriteBorder, PonyNose, PonyEye, ColorExtra, ColorExtraSets,
colorPal3, colorPal5, colorPal7, colorPal9, colorPal11, colorPal13, colorPal17
};
+1
View File
@@ -0,0 +1 @@
save this file to trigger sprite regenerations in `gulp dev --sprites` mode
+112
View File
@@ -0,0 +1,112 @@
export interface Psd {
dir: string;
name: string;
info: string;
width: number;
height: number;
children: Layer[];
}
export interface Layer {
name: string;
info: string;
children: Layer[];
canvas?: ExtCanvas;
}
export interface ExtCanvas extends HTMLCanvasElement {
info: string;
}
export interface Sprite extends Rect {
ox: number;
oy: number;
image: HTMLCanvasElement;
shade?: boolean;
layer?: number;
}
export interface Result {
objects: any;
objects2: any;
images: HTMLCanvasElement[];
sprites: Sprite[];
// palettes: number[][];
}
export interface ColorShadow {
color: number;
shadow: number;
}
export interface Nose {
mouth: number;
muzzle: number;
fangs: number;
}
export interface Eye {
base: number;
irises: number[];
shadow: number;
shine: number;
}
export interface Point {
x: number;
y: number;
}
export interface Rect {
x: number;
y: number;
w: number;
h: number;
}
export interface Tree {
stump: number;
trunk: number;
crown: number;
stumpShadow: number;
shadow: number;
palette: number[];
}
export interface Animation {
frames: number[];
palette: number[];
shadow?: number;
}
export interface ColorExtra {
color: number;
colors: number;
extra?: number;
palette?: number[];
palettes?: number[][];
}
export interface TileSprites {
sprites: number[];
palettes: number[][];
}
export interface Button {
border: number;
topLeft: number;
top: number;
topRight: number;
left: number;
bg: number;
right: number;
bottomLeft: number;
bottom: number;
bottomRight: number;
palette?: number[];
}
export interface Emote {
name: string;
sprite: number;
}