mirror of
https://github.com/Terncode/pixel.horse.git
synced 2026-09-24 13:55:04 +02:00
Speed up sprite compilation
This commit is contained in:
+123
-47
@@ -52,6 +52,58 @@ const getPngs = (directory: string) => fs.readdirSync(directory).filter(isPng);
|
|||||||
const getFrames = (layers: Layer[]) => layers.filter(nameMatches(/^frame/)).sort(compareLayers);
|
const getFrames = (layers: Layer[]) => layers.filter(nameMatches(/^frame/)).sort(compareLayers);
|
||||||
const ponyPsd = (name: string) => openPsd(path.join(ponyPath, name));
|
const ponyPsd = (name: string) => openPsd(path.join(ponyPath, name));
|
||||||
|
|
||||||
|
// profiling / progress logging
|
||||||
|
|
||||||
|
interface ProfileStep {
|
||||||
|
name: string;
|
||||||
|
elapsed: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
let spritesLog = false;
|
||||||
|
let profileStart = 0;
|
||||||
|
const profileSteps: ProfileStep[] = [];
|
||||||
|
|
||||||
|
function formatTime(ms: number) {
|
||||||
|
return ms >= 1000 ? `${(ms / 1000).toFixed(1)}s` : `${ms}ms`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function logStep<T>(name: string, fn: () => T, log = spritesLog): T {
|
||||||
|
const start = Date.now();
|
||||||
|
|
||||||
|
if (log) {
|
||||||
|
const total = formatTime(start - profileStart);
|
||||||
|
console.log(`[sprites] ${name}... (${total} elapsed)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = fn();
|
||||||
|
const elapsed = Date.now() - start;
|
||||||
|
profileSteps.push({ name, elapsed });
|
||||||
|
|
||||||
|
if (log) {
|
||||||
|
console.log(`[sprites] ${name}: ${formatTime(elapsed)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function printProfile() {
|
||||||
|
if (!spritesLog) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const total = profileSteps.reduce((sum, step) => sum + step.elapsed, 0);
|
||||||
|
const maxNameLength = profileSteps.length ? Math.max(...profileSteps.map(step => step.name.length)) : 0;
|
||||||
|
|
||||||
|
console.log(`\n[sprites] profile:`);
|
||||||
|
|
||||||
|
for (const { name, elapsed } of profileSteps) {
|
||||||
|
const percent = total ? ((elapsed / total) * 100).toFixed(1) : '0.0';
|
||||||
|
console.log(`[sprites] ${formatTime(elapsed).padStart(7)} (${percent}%) ${name.padEnd(maxNameLength)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[sprites] ${formatTime(total).padStart(7)} (100.0%) ${'TOTAL'.padEnd(maxNameLength)}`);
|
||||||
|
}
|
||||||
|
|
||||||
function openPng(fileName: string) {
|
function openPng(fileName: string) {
|
||||||
return imageToCanvas(loadImage(fileName));
|
return imageToCanvas(loadImage(fileName));
|
||||||
}
|
}
|
||||||
@@ -1055,36 +1107,42 @@ function createResult(): Result {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function createPonySprites(result: Result) {
|
function createPonySprites(result: Result, log: boolean) {
|
||||||
getEyesFromPsd(result, ponyPsd('eyes.psd'), ponyPsd('irises.psd'));
|
logStep('eyes', () => {
|
||||||
getMuzzlesFromPsd(result, ponyPsd('muzzles.psd'));
|
getEyesFromPsd(result, ponyPsd('eyes.psd'), ponyPsd('irises.psd'));
|
||||||
getBlushFromPsd(result, ponyPsd('blush.psd'));
|
}, log);
|
||||||
getPonyShadowsAndSelection(result, ponyPsd('shadows.psd'));
|
logStep('muzzles', () => getMuzzlesFromPsd(result, ponyPsd('muzzles.psd')), log);
|
||||||
|
logStep('blush', () => getBlushFromPsd(result, ponyPsd('blush.psd')), log);
|
||||||
result.objects2.cms = addCMSprite(result.sprites, false);
|
logStep('shadows', () => getPonyShadowsAndSelection(result, ponyPsd('shadows.psd')), log);
|
||||||
result.objects2.cmsFlip = addCMSprite(result.sprites, true);
|
logStep('cms', () => {
|
||||||
|
result.objects2.cms = addCMSprite(result.sprites, false);
|
||||||
|
result.objects2.cmsFlip = addCMSprite(result.sprites, true);
|
||||||
|
}, log);
|
||||||
|
|
||||||
sheets
|
sheets
|
||||||
.filter(s => 'name' in s && !!s.file && !s.skipImport)
|
.filter(s => 'name' in s && !!s.file && !s.skipImport)
|
||||||
.forEach(s => importSprites(result, s as Sheet));
|
.forEach(s => {
|
||||||
|
const sheet = s as Sheet;
|
||||||
|
logStep(`import ${sheet.name}`, () => importSprites(result, sheet), log);
|
||||||
|
});
|
||||||
|
|
||||||
// const bug = getFramesFromPSD(result, ponyPsd('fly-bug.psd'));
|
// const bug = getFramesFromPSD(result, ponyPsd('fly-bug.psd'));
|
||||||
// result.objects2['wings: AnimatedSprites'].slice(3)
|
// result.objects2['wings: AnimatedSprites'].slice(3)
|
||||||
// .forEach((frame: any, i: number) => frame[1] = [pegasus[i]]);
|
// .forEach((frame: any, i: number) => frame[1] = [pegasus[i]]);
|
||||||
|
|
||||||
getTreesFromPsds(result, path.join(sourcePath, 'trees'));
|
logStep('trees', () => getTreesFromPsds(result, path.join(sourcePath, 'trees')), log);
|
||||||
getObjectsFromPsds(result, path.join(sourcePath, 'objects'));
|
logStep('objects', () => getObjectsFromPsds(result, path.join(sourcePath, 'objects')), log);
|
||||||
|
|
||||||
createButtons(result, path.join(sourcePath, 'buttons'));
|
logStep('buttons', () => createButtons(result, path.join(sourcePath, 'buttons')), log);
|
||||||
createAnimations(result, path.join(sourcePath, 'animations'));
|
logStep('animations', () => createAnimations(result, path.join(sourcePath, 'animations')), log);
|
||||||
createOtherSprites(result, path.join(sourcePath, 'sprites'));
|
logStep('sprites', () => createOtherSprites(result, path.join(sourcePath, 'sprites')), log);
|
||||||
createOtherSpritesPalette(result, path.join(sourcePath, 'sprites-palette'));
|
logStep('sprites-palette', () => createOtherSpritesPalette(result, path.join(sourcePath, 'sprites-palette')), log);
|
||||||
createIcons(result, path.join(sourcePath, 'icons'));
|
logStep('icons', () => createIcons(result, path.join(sourcePath, 'icons')), log);
|
||||||
createOtherSpritesAnimations(result, path.join(sourcePath, 'sprites-animations'));
|
logStep('sprites-animations', () => createOtherSpritesAnimations(result, path.join(sourcePath, 'sprites-animations')), log);
|
||||||
createEmoteAnimations(result, path.join(sourcePath, 'emotes'));
|
logStep('emotes', () => createEmoteAnimations(result, path.join(sourcePath, 'emotes')), log);
|
||||||
createWalls(result, path.join(sourcePath, 'walls'));
|
logStep('walls', () => createWalls(result, path.join(sourcePath, 'walls')), log);
|
||||||
|
|
||||||
createPixelSprites(result);
|
logStep('pixel sprites', () => createPixelSprites(result), log);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WallConfig {
|
interface WallConfig {
|
||||||
@@ -1329,15 +1387,7 @@ const SUPPORTER1 = 0xf86754ff;
|
|||||||
const SUPPORTER2_BANDS = [0xffdfc1ff, 0xffcd99ff, 0xff9f3bff, 0xd97e09ff];
|
const SUPPORTER2_BANDS = [0xffdfc1ff, 0xffcd99ff, 0xff9f3bff, 0xd97e09ff];
|
||||||
const SUPPORTER3_BANDS = [0xffffffff, 0xfffda4ff, 0xffea3bff, 0xfdbb0bff];
|
const SUPPORTER3_BANDS = [0xffffffff, 0xfffda4ff, 0xffea3bff, 0xfdbb0bff];
|
||||||
|
|
||||||
export function createSprites(log: boolean) {
|
function createFonts(result: Result) {
|
||||||
mkdir(destPath);
|
|
||||||
mkdir(generatedPath);
|
|
||||||
|
|
||||||
const result = createResult();
|
|
||||||
|
|
||||||
createPonySprites(result);
|
|
||||||
createTileSprites(result);
|
|
||||||
|
|
||||||
const mainFont = fontCanvas('main.psd');
|
const mainFont = fontCanvas('main.psd');
|
||||||
const mainEmoji = fontCanvas('emoji.psd');
|
const mainEmoji = fontCanvas('emoji.psd');
|
||||||
const tinyFont = fontCanvas('tiny.psd');
|
const tinyFont = fontCanvas('tiny.psd');
|
||||||
@@ -1368,10 +1418,44 @@ export function createSprites(log: boolean) {
|
|||||||
const monoFontSpritesPal = createFont(stripedMonoFont, 8, 9,
|
const monoFontSpritesPal = createFont(stripedMonoFont, 8, 9,
|
||||||
canvas => addSprite(result.sprites, canvas, undefined, smallFontPalette), { noChinese: true, mono: 4, onlyBase: true });
|
canvas => addSprite(result.sprites, canvas, undefined, smallFontPalette), { noChinese: true, mono: 4, onlyBase: true });
|
||||||
|
|
||||||
const lights = createLights(result, path.join(sourcePath, 'lights'));
|
return {
|
||||||
|
emojiPalette,
|
||||||
|
fonts: {
|
||||||
|
font: fontSprites,
|
||||||
|
emoji: emojiSprites,
|
||||||
|
fontSmall: smallFontSprites,
|
||||||
|
fontMono: monoFontSprites,
|
||||||
|
},
|
||||||
|
fontsPal: {
|
||||||
|
fontPal: fontSpritesPal,
|
||||||
|
emojiPal: emojiSpritesPal,
|
||||||
|
fontSmallPal: smallFontSpritesPal,
|
||||||
|
fontMonoPal: monoFontSpritesPal,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const ponySheet = createSpriteSheet('ponySheet', result.images.map(imageToSprite), log, 1024);
|
export function createSprites(log: boolean) {
|
||||||
const ponySheet2 = createSpriteSheet('ponySheet2', result.sprites, log, 1024, 'black', true);
|
spritesLog = log;
|
||||||
|
profileStart = Date.now();
|
||||||
|
profileSteps.length = 0;
|
||||||
|
|
||||||
|
mkdir(destPath);
|
||||||
|
mkdir(generatedPath);
|
||||||
|
|
||||||
|
const result = createResult();
|
||||||
|
|
||||||
|
createPonySprites(result, log);
|
||||||
|
logStep('tiles', () => createTileSprites(result), log);
|
||||||
|
|
||||||
|
const { fonts, fontsPal, emojiPalette } = logStep('fonts', () => createFonts(result), log);
|
||||||
|
|
||||||
|
const lights = logStep('lights', () => createLights(result, path.join(sourcePath, 'lights')), log);
|
||||||
|
|
||||||
|
const ponySheet = logStep(
|
||||||
|
'pack ponySheet', () => createSpriteSheet('ponySheet', result.images.map(imageToSprite), log, 1024), log);
|
||||||
|
const ponySheet2 = logStep(
|
||||||
|
'pack ponySheet2', () => createSpriteSheet('ponySheet2', result.sprites, log, 1024, 'black', true), log);
|
||||||
|
|
||||||
fixPixelRect(ponySheet.sprites, result.objects, 'pixelRect', 'pixel');
|
fixPixelRect(ponySheet.sprites, result.objects, 'pixelRect', 'pixel');
|
||||||
fixPixelRect(ponySheet2.sprites, result.objects2, 'pixelRect2', 'pixel2');
|
fixPixelRect(ponySheet2.sprites, result.objects2, 'pixelRect2', 'pixel2');
|
||||||
@@ -1395,18 +1479,8 @@ export function createSprites(log: boolean) {
|
|||||||
paletteSprites: ponySheet2.sprites,
|
paletteSprites: ponySheet2.sprites,
|
||||||
result,
|
result,
|
||||||
palettes,
|
palettes,
|
||||||
fonts: {
|
fonts,
|
||||||
font: fontSprites,
|
fontsPal,
|
||||||
emoji: emojiSprites,
|
|
||||||
fontSmall: smallFontSprites,
|
|
||||||
fontMono: monoFontSprites,
|
|
||||||
},
|
|
||||||
fontsPal: {
|
|
||||||
fontPal: fontSpritesPal,
|
|
||||||
emojiPal: emojiSpritesPal,
|
|
||||||
fontSmallPal: smallFontSpritesPal,
|
|
||||||
fontMonoPal: monoFontSpritesPal,
|
|
||||||
},
|
|
||||||
namedPalettes: {
|
namedPalettes: {
|
||||||
defaultPalette: 0,
|
defaultPalette: 0,
|
||||||
emojiPalette: addPalette(emojiPalette),
|
emojiPalette: addPalette(emojiPalette),
|
||||||
@@ -1423,15 +1497,17 @@ export function createSprites(log: boolean) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
createSpritesTS(path.join(generatedPath, 'sprites.ts'), spritesConfig);
|
logStep('generate sprites.ts', () => createSpritesTS(path.join(generatedPath, 'sprites.ts'), spritesConfig), log);
|
||||||
|
|
||||||
// Other exports
|
// Other exports
|
||||||
saveCanvasAsRaw(path.join(outputPath, 'pony2.raw'), ponySheet2.image);
|
logStep('save raw', () => saveCanvasAsRaw(path.join(outputPath, 'pony2.raw'), ponySheet2.image), log);
|
||||||
|
|
||||||
|
printProfile();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (require.main === module) {
|
if (require.main === module) {
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
createSprites(true);
|
createSprites(false);
|
||||||
const time = ((Date.now() - start) / 1000).toFixed(2);
|
const time = ((Date.now() - start) / 1000).toFixed(2);
|
||||||
console.log(`[sprites] done: ${time}s, stats: { w: ${0}-${maxW} h: ${0}-${maxH} ox: ${minOX}-${maxOX} oy: ${minOY}-${maxOY} }`);
|
console.log(`[sprites] done: ${time}s, stats: { w: ${0}-${maxW} h: ${0}-${maxH} ox: ${minOX}-${maxOX} oy: ${minOY}-${maxOY} }`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import * as fs from 'fs';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { times } from 'lodash';
|
import { times } from 'lodash';
|
||||||
import { Sprite, Rect } from './types';
|
import { Sprite, Rect } from './types';
|
||||||
import { findByIndex } from './common';
|
|
||||||
import { removeItem } from '../common/utils';
|
import { removeItem } from '../common/utils';
|
||||||
import { createExtCanvas, saveCanvas } from './canvas-utils';
|
import { createExtCanvas, saveCanvas } from './canvas-utils';
|
||||||
|
|
||||||
@@ -37,13 +36,18 @@ function isIdenticalData(a: ImageData, b: ImageData) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isIdenticalChannel(a: Sprite | undefined, b: Sprite | undefined, channel: number) {
|
function isIdenticalChannel(a: ExtSprite | undefined, b: ExtSprite | undefined, channel: number) {
|
||||||
if (!a || !b || a.w !== b.w || a.h !== b.h) {
|
if (!a || !b || a.w !== b.w || a.h !== b.h) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const adata = a.image.getContext('2d')!.getImageData(a.ox, a.oy, a.w, a.h);
|
const adata = a.data;
|
||||||
const bdata = b.image.getContext('2d')!.getImageData(b.ox, b.oy, b.w, b.h);
|
const bdata = b.data;
|
||||||
|
|
||||||
|
if (!adata || !bdata) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const length = (adata.width * adata.height * 4) | 0;
|
const length = (adata.width * adata.height * 4) | 0;
|
||||||
const adat = adata.data;
|
const adat = adata.data;
|
||||||
const bdat = bdata.data;
|
const bdat = bdata.data;
|
||||||
@@ -121,7 +125,7 @@ function getIndex(x: number, y: number, outputWidth: number) {
|
|||||||
return ((x | 0) + (((y | 0) * outputWidth) | 0)) | 0;
|
return ((x | 0) + (((y | 0) * outputWidth) | 0)) | 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isEmpty(x: number, y: number, w: number, h: number, outputWidth: number, taken: Uint8Array) {
|
function isEmpty(x: number, y: number, w: number, h: number, outputWidth: number, lines: Line[][]) {
|
||||||
outputWidth = outputWidth | 0;
|
outputWidth = outputWidth | 0;
|
||||||
|
|
||||||
if (((x + w) | 0) > outputWidth || ((y + h) | 0) > outputWidth) {
|
if (((x + w) | 0) > outputWidth || ((y + h) | 0) > outputWidth) {
|
||||||
@@ -129,10 +133,24 @@ function isEmpty(x: number, y: number, w: number, h: number, outputWidth: number
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (let iy = 0; iy < h; iy++) {
|
for (let iy = 0; iy < h; iy++) {
|
||||||
for (let ix = 0; ix < w; ix++) {
|
const spans = lines[y + iy];
|
||||||
if (taken[getIndex((ix + x) | 0, (iy + y) | 0, outputWidth)] !== 0) {
|
let found = false;
|
||||||
return false;
|
|
||||||
|
for (let i = 0; i < spans.length; i++) {
|
||||||
|
const span = spans[i];
|
||||||
|
|
||||||
|
if (span.start > x) { // spans are sorted and non-overlapping
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (x + w <= span.start + span.length) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,10 +164,9 @@ interface Line {
|
|||||||
|
|
||||||
interface Taken {
|
interface Taken {
|
||||||
lines: Line[][];
|
lines: Line[][];
|
||||||
data: Uint8Array; // TODO: remove completely, just use lines
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFirstFree(outputWidth: number, width: number, height: number, { data, lines }: Taken) {
|
function getFirstFree(outputWidth: number, width: number, height: number, { lines }: Taken) {
|
||||||
const maxY = (outputWidth - height) | 0;
|
const maxY = (outputWidth - height) | 0;
|
||||||
|
|
||||||
for (let y = 0; y < maxY; y = (y + 1) | 0) {
|
for (let y = 0; y < maxY; y = (y + 1) | 0) {
|
||||||
@@ -161,7 +178,7 @@ function getFirstFree(outputWidth: number, width: number, height: number, { data
|
|||||||
const end = (start + span.length - width) | 0;
|
const end = (start + span.length - width) | 0;
|
||||||
|
|
||||||
for (let x = start; x < end; x = (x + 1) | 0) {
|
for (let x = start; x < end; x = (x + 1) | 0) {
|
||||||
if (isEmpty(x, y, width, height, outputWidth, data)) {
|
if (isEmpty(x, y, width, height, outputWidth, lines)) {
|
||||||
return { x, y, layer: 0 };
|
return { x, y, layer: 0 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,7 +192,7 @@ function getFirstFreePacked(outputWidth: number, width: number, height: number,
|
|||||||
const maxY = outputWidth - height;
|
const maxY = outputWidth - height;
|
||||||
|
|
||||||
for (let layer = 0; layer < takens.length; layer = (layer + 1) | 0) {
|
for (let layer = 0; layer < takens.length; layer = (layer + 1) | 0) {
|
||||||
const { data, lines } = takens[layer];
|
const { lines } = takens[layer];
|
||||||
|
|
||||||
for (let y = 0; y < maxY; y = (y + 1) | 0) {
|
for (let y = 0; y < maxY; y = (y + 1) | 0) {
|
||||||
const spans = lines[y];
|
const spans = lines[y];
|
||||||
@@ -186,7 +203,7 @@ function getFirstFreePacked(outputWidth: number, width: number, height: number,
|
|||||||
const end = (start + span.length - width) | 0;
|
const end = (start + span.length - width) | 0;
|
||||||
|
|
||||||
for (let x = start | 0; x < end; x = (x + 1) | 0) {
|
for (let x = start | 0; x < end; x = (x + 1) | 0) {
|
||||||
if (isEmpty(x, y, width, height, outputWidth, data)) {
|
if (isEmpty(x, y, width, height, outputWidth, lines)) {
|
||||||
return { x, y, layer };
|
return { x, y, layer };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -211,17 +228,12 @@ function positionSprite(sprite: ExtSprite, outputWidth: number, taken: Taken[],
|
|||||||
const right = x + w;
|
const right = x + w;
|
||||||
|
|
||||||
for (let il = 0; il < layers; il++) {
|
for (let il = 0; il < layers; il++) {
|
||||||
const { data, lines } = taken[il + layer];
|
const { lines } = taken[il + layer];
|
||||||
|
|
||||||
for (let iy = 0; iy < sprite.h; iy++) {
|
for (let iy = 0; iy < sprite.h; iy++) {
|
||||||
const yy = y + iy;
|
const yy = y + iy;
|
||||||
const spans = lines[yy];
|
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++) {
|
for (let i = 0; i < spans.length; i++) {
|
||||||
const span = spans[i];
|
const span = spans[i];
|
||||||
const start = span.start;
|
const start = span.start;
|
||||||
@@ -254,35 +266,17 @@ function positionSprite(sprite: ExtSprite, outputWidth: number, taken: Taken[],
|
|||||||
spans.splice(i + 1, 0, { start: right, length: end - right });
|
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) {
|
function hasShading(s: ExtSprite) {
|
||||||
const data = s.image.getContext('2d')!.getImageData(s.ox, s.oy, s.w, s.h);
|
const data = s.data;
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
for (let y = 0; y < data.height; y++) {
|
for (let y = 0; y < data.height; y++) {
|
||||||
for (let x = 0; x < data.width; x++) {
|
for (let x = 0; x < data.width; x++) {
|
||||||
@@ -305,6 +299,18 @@ function getSpriteImageData(s: ExtSprite): ImageData | undefined {
|
|||||||
return { width, height, data } as ImageData;
|
return { width, height, data } as ImageData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDataHash(data: ImageData): number {
|
||||||
|
const bytes = data.data;
|
||||||
|
const length = bytes.length | 0;
|
||||||
|
let hash = 0x811c9dc5;
|
||||||
|
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
hash = Math.imul(hash ^ bytes[i], 0x01000193);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash >>> 0;
|
||||||
|
}
|
||||||
|
|
||||||
export function createSpriteSheet(name: string, images: ExtSprite[], log: boolean, size: number, bg?: string, pack = false) {
|
export function createSpriteSheet(name: string, images: ExtSprite[], log: boolean, size: number, bg?: string, pack = false) {
|
||||||
const maxLayers = 4;
|
const maxLayers = 4;
|
||||||
const sprites = images.slice();
|
const sprites = images.slice();
|
||||||
@@ -323,16 +329,27 @@ export function createSpriteSheet(name: string, images: ExtSprite[], log: boolea
|
|||||||
sprites
|
sprites
|
||||||
.forEach(s => s.data = getSpriteImageData(s));
|
.forEach(s => s.data = getSpriteImageData(s));
|
||||||
|
|
||||||
|
const seen: Map<string, ExtSprite[]> = new Map();
|
||||||
|
|
||||||
sprites
|
sprites
|
||||||
.forEach((s, i) => {
|
.forEach(s => {
|
||||||
if (s.w && s.h) {
|
if (s.w && s.h && s.data) {
|
||||||
for (let j = 0; j < i; j++) {
|
const key = `${s.w}x${s.h}|${getDataHash(s.data)}`;
|
||||||
if (!s.duplicateOf && isIdenticalSprite(sprites[j], s)) {
|
const candidates = seen.get(key);
|
||||||
s.duplicateOf = sprites[j];
|
|
||||||
|
if (candidates) {
|
||||||
|
const duplicate = candidates.find(c => isIdenticalSprite(c, s));
|
||||||
|
|
||||||
|
if (duplicate) {
|
||||||
|
s.duplicateOf = duplicate;
|
||||||
deduplicated++;
|
deduplicated++;
|
||||||
//console.log('duplicate', sprite.name, '==', sprites[j].name);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
candidates.push(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
seen.set(key, [s]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -378,7 +395,6 @@ export function createSpriteSheet(name: string, images: ExtSprite[], log: boolea
|
|||||||
|
|
||||||
const taken: Taken[] = times(maxLayers, () => ({
|
const taken: Taken[] = times(maxLayers, () => ({
|
||||||
lines: times(outputWidth, () => [{ start: 0, length: outputWidth }]),
|
lines: times(outputWidth, () => [{ start: 0, length: outputWidth }]),
|
||||||
data: new Uint8Array(outputWidth * outputWidth),
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
sprites
|
sprites
|
||||||
@@ -439,17 +455,17 @@ export function createSpriteSheet(name: string, images: ExtSprite[], log: boolea
|
|||||||
const alphaData = alphaContext.getImageData(0, 0, image.width, image.height);
|
const alphaData = alphaContext.getImageData(0, 0, image.width, image.height);
|
||||||
|
|
||||||
sprites
|
sprites
|
||||||
.filter(s => !s.duplicateOf && s.w && s.h)
|
.filter(s => !s.duplicateOf && s.w && s.h && s.data)
|
||||||
.forEach(s => {
|
.forEach(s => {
|
||||||
if (s.layer === 3) {
|
if (s.layer === 3) {
|
||||||
drawChannel(s.image, alphaData, 0, 0, s.ox, s.oy, s.x, s.y, s.w, s.h);
|
drawChannel(s.data!, alphaData, 0, 0, s.x, s.y, s.w, s.h);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
drawChannel(s.image, data, 0, s.layer || 0, s.ox, s.oy, s.x, s.y, s.w, s.h);
|
drawChannel(s.data!, data, 0, s.layer || 0, s.x, s.y, s.w, s.h);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s.shade) {
|
if (s.shade) {
|
||||||
drawChannel(s.image, data, 1, 1, s.ox, s.oy, s.x, s.y, s.w, s.h);
|
drawChannel(s.data!, data, 1, 1, s.x, s.y, s.w, s.h);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -462,23 +478,35 @@ export function createSpriteSheet(name: string, images: ExtSprite[], log: boolea
|
|||||||
.forEach(s => context.drawImage(s.image, s.ox, s.oy, s.w, s.h, s.x, s.y, 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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const byIndex = new Map<number, ExtSprite>();
|
||||||
|
|
||||||
|
sprites.forEach(s => {
|
||||||
|
if (s.index != null) {
|
||||||
|
byIndex.set(s.index, s);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sprites: images.map((_, index) => findByIndex(sprites, index) || null),
|
sprites: images.map((_, index) => byIndex.get(index) || null),
|
||||||
image,
|
image,
|
||||||
alpha,
|
alpha,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawChannel(
|
function drawChannel(
|
||||||
src: HTMLCanvasElement, dst: ImageData, srcChannel: number, dstChannel: number,
|
src: ImageData, dst: ImageData, srcChannel: number, dstChannel: number,
|
||||||
sx: number, sy: number, dx: number, dy: number, w: number, h: number
|
dx: number, dy: number, w: number, h: number
|
||||||
) {
|
) {
|
||||||
const srcData = src.getContext('2d')!.getImageData(sx, sy, w, h);
|
const srcData = src.data;
|
||||||
|
const srcWidth = src.width | 0;
|
||||||
|
const dstWidth = dst.width | 0;
|
||||||
|
|
||||||
for (let y = 0; y < h; y++) {
|
for (let y = 0; y < h; y++) {
|
||||||
|
const sOffset = ((y * srcWidth) * 4 + srcChannel) | 0;
|
||||||
|
const dOffset = ((((y + dy) * dstWidth) + dx) * 4 + dstChannel) | 0;
|
||||||
|
|
||||||
for (let x = 0; x < w; x++) {
|
for (let x = 0; x < w; x++) {
|
||||||
dst.data[((x + dx) + (y + dy) * dst.width) * 4 + dstChannel] =
|
dst.data[dOffset + (x * 4) | 0] = srcData[sOffset + (x * 4) | 0];
|
||||||
srcData.data[(x + y * srcData.width) * 4 + srcChannel];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user