mirror of
https://github.com/Terncode/pixel.horse.git
synced 2026-09-25 06:05:52 +02:00
Archive commit
This commit is contained in:
@@ -0,0 +1,358 @@
|
||||
import { max, compact } from 'lodash';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { Psd, writePsd, Layer } from 'ag-psd';
|
||||
import { SpriteSet, PonyInfoNumber, PaletteSpriteSet, NoDraw } from '../../common/interfaces';
|
||||
import { times, cloneDeep, setFlag, includes, toInt } from '../../common/utils';
|
||||
import { createDefaultPony, syncLockedPonyInfoNumber, toPaletteNumber, mockPaletteManager } from '../../common/ponyInfo';
|
||||
import { Sets } from '../../client/ponyUtils';
|
||||
import { defaultDrawPonyOptions, defaultPonyState } from '../../client/ponyHelpers';
|
||||
import { createCanvas, disableImageSmoothing } from '../../client/canvasUtils';
|
||||
import { ContextSpriteBatch } from '../../graphics/contextSpriteBatch';
|
||||
import { BLACK, BLUE, CYAN, WHITE, RED, GREEN, YELLOW, MAGENTA, TRANSPARENT } from '../../common/colors';
|
||||
import { colorToCSS } from '../../common/color';
|
||||
import { decompressPony, compressPonyString } from '../../common/compressPony';
|
||||
import { drawPony } from '../../client/ponyDraw';
|
||||
import * as sprites from '../../generated/sprites';
|
||||
import { drawPixelTextOnCanvas, fillRect } from '../../graphics/graphicsUtils';
|
||||
import { Sheet, SheetLayer, ignoreSet, DEFAULT_COLOR } from '../../common/sheets';
|
||||
import { createHeadAnimation } from '../../client/ponyAnimations';
|
||||
|
||||
const PONY_X = 30;
|
||||
const PONY_Y = 50;
|
||||
const patternColors = [RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, BLACK];
|
||||
const whiteColors = [WHITE, WHITE, WHITE, WHITE, WHITE, WHITE, WHITE];
|
||||
|
||||
function maxPatterns(sprites: Sets): number {
|
||||
return max(sprites.map(s => s && s.length ? max(s.map(x => x ? x.length : 0)) : 0))!;
|
||||
}
|
||||
|
||||
const backupSprites: any = {
|
||||
head: [
|
||||
undefined,
|
||||
[
|
||||
[
|
||||
{}
|
||||
],
|
||||
],
|
||||
],
|
||||
};
|
||||
|
||||
function getSets(sheet: Sheet, key: string, override?: string): Sets | undefined {
|
||||
const setsKey = override || key || '';
|
||||
const sets = backupSprites[setsKey] || (sprites as any)[setsKey];
|
||||
|
||||
if (sheet.duplicateFirstFrame !== undefined) {
|
||||
return times(sheet.duplicateFirstFrame, () => sets[0]);
|
||||
} else {
|
||||
return sheet.single ? [sets] : sets;
|
||||
}
|
||||
}
|
||||
|
||||
function getSetsForFirstKey(sheet: Sheet) {
|
||||
const layer = sheet.layers.find(l => !!l.set);
|
||||
return layer && getSets(sheet, layer.set!);
|
||||
}
|
||||
|
||||
export function getCols(sheet: Sheet) {
|
||||
return sheet.state!.animation.frames.length;
|
||||
}
|
||||
|
||||
export function getRows(sheet: Sheet) {
|
||||
if (sheet.rows !== undefined) {
|
||||
return sheet.rows;
|
||||
} else {
|
||||
const sets = getSetsForFirstKey(sheet);
|
||||
const maxFrames = sets && max(sets.map(f => f ? f.length : 0));
|
||||
return (maxFrames || 0) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
export function savePsd(psd: Psd, name: string) {
|
||||
saveAs(new Blob([writePsd(psd, { generateThumbnail: true })], { type: 'application/octet-stream' }), name);
|
||||
}
|
||||
|
||||
export function createPsd(sheet: Sheet, rows: number, cols: number): Psd {
|
||||
const width = canvasWidth(sheet, rows, cols);
|
||||
const height = canvasHeight(sheet, rows, cols);
|
||||
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
children: compact([
|
||||
{ name: '<bg>', canvas: createBackground(rows, cols, width, height, sheet), transparencyProtected: true },
|
||||
...sheet.layers!.map(layer => createPsdLayer(sheet, rows, cols, layer)),
|
||||
{ name: '<refs>', canvas: createRefsCanvas(width, height, sheet.paletteOffsetY) },
|
||||
]),
|
||||
};
|
||||
}
|
||||
|
||||
function canvasWidth(sheet: Sheet, _rows: number, cols: number) {
|
||||
if (sheet.wrap) {
|
||||
cols = sheet.wrap;
|
||||
}
|
||||
|
||||
return (sheet.offset * (cols - 1)) + sheet.width;
|
||||
}
|
||||
|
||||
function canvasHeight(sheet: Sheet, rows: number, _cols: number) {
|
||||
if (sheet.wrap) {
|
||||
rows = Math.ceil(rows / sheet.wrap);
|
||||
}
|
||||
|
||||
return sheet.height * rows;
|
||||
}
|
||||
|
||||
function drawPsdLayer(
|
||||
sheet: Sheet, rows: number, cols: number, layer: SheetLayer, pattern = -1, extra = false
|
||||
): HTMLCanvasElement {
|
||||
const { width, height, offset, offsetY = 0, wrap } = sheet;
|
||||
const pony = createPony();
|
||||
const baseState = { ...defaultPonyState(), ...sheet.state, blushColor: BLACK };
|
||||
const options = { ...defaultDrawPonyOptions(), ...layer.options };
|
||||
const ignoreColor = (layer.drawBlack === undefined ? !!layer.head : layer.drawBlack) ? TRANSPARENT : BLACK;
|
||||
const fieldName = layer.fieldName || sheet.fieldName;
|
||||
|
||||
if (!layer.head) {
|
||||
baseState.headAnimation = createHeadAnimation('', 1, false, [[]]);
|
||||
pony.head = ignoreSet();
|
||||
pony.nose = ignoreSet();
|
||||
pony.ears = ignoreSet();
|
||||
} else if (layer.noFace) {
|
||||
baseState.headAnimation = createHeadAnimation('', 1, false, [[]]);
|
||||
pony.head = ignoreSet();
|
||||
pony.nose = ignoreSet();
|
||||
}
|
||||
|
||||
if (!layer.body) {
|
||||
options.no = setFlag(options.no, NoDraw.BodyOnly, true);
|
||||
}
|
||||
|
||||
if (!layer.frontLeg) {
|
||||
options.no = setFlag(options.no, NoDraw.FrontLeg, true);
|
||||
}
|
||||
|
||||
if (!layer.backLeg) {
|
||||
options.no = setFlag(options.no, NoDraw.BackLeg, true);
|
||||
}
|
||||
|
||||
if (!layer.frontFarLeg) {
|
||||
options.no = setFlag(options.no, NoDraw.FrontFarLeg, true);
|
||||
}
|
||||
|
||||
if (!layer.backFarLeg) {
|
||||
options.no = setFlag(options.no, NoDraw.BackFarLeg, true);
|
||||
}
|
||||
|
||||
layer.setup && layer.setup(pony, baseState);
|
||||
|
||||
syncLockedPonyInfoNumber(pony);
|
||||
|
||||
const actualRows = wrap ? Math.ceil(rows / wrap) : rows;
|
||||
const actualCols = wrap ? wrap : cols;
|
||||
const empties = sheet.empties && includes(sheet.setsWithEmpties, layer.set) ? sheet.empties : [];
|
||||
|
||||
return drawFrames(actualRows, actualCols, width, height, offset, (batch, x, y) => {
|
||||
const xIndexBase = (wrap ? actualCols * y + x : x);
|
||||
const xIndexOffset = empties.filter(i => i <= xIndexBase).length;
|
||||
const xIndex = includes(empties, xIndexBase) ? 0 : (xIndexBase - xIndexOffset);
|
||||
const yIndex = wrap ? 0 : y;
|
||||
|
||||
const state = cloneDeep(baseState);
|
||||
|
||||
sheet.frame && sheet.frame(pony, state, options, xIndex, yIndex, pattern);
|
||||
layer.frame && layer.frame(pony, state, options, xIndex, yIndex, pattern);
|
||||
|
||||
state.animationFrame = xIndex;
|
||||
|
||||
if (layer.set && fieldName) {
|
||||
const sets = getSets(sheet, layer.set, layer.setOverride);
|
||||
|
||||
if (!sets) {
|
||||
throw new Error(`Missing sets for (${layer.set})`);
|
||||
}
|
||||
|
||||
const frameIndex = sheet.single ? 0 : xIndex;
|
||||
const typeIndex = sheet.single ? xIndex : yIndex;
|
||||
const aframe = sets[frameIndex];
|
||||
const type = (aframe && typeIndex < aframe.length) ? typeIndex : -1;
|
||||
const set: SpriteSet<number> = { type };
|
||||
|
||||
if (pattern !== -1) {
|
||||
set.fills = patternColors;
|
||||
set.outlines = patternColors;
|
||||
|
||||
if (aframe && typeIndex < aframe.length && aframe[typeIndex] && pattern < aframe[typeIndex]!.length) {
|
||||
set.pattern = pattern;
|
||||
} else {
|
||||
set.type = -1;
|
||||
}
|
||||
} else {
|
||||
set.fills = whiteColors;
|
||||
set.outlines = whiteColors;
|
||||
|
||||
if (!(aframe && typeIndex < aframe.length && aframe[typeIndex])) {
|
||||
set.type = -1;
|
||||
}
|
||||
}
|
||||
|
||||
layer.frameSet && layer.frameSet(set, xIndex, yIndex, pattern);
|
||||
|
||||
(pony as any)[fieldName] = set.type === -1 ? ignoreSet() : set;
|
||||
}
|
||||
|
||||
batch.disableShading = pattern !== -1;
|
||||
batch.ignoreColor = ignoreColor;
|
||||
|
||||
const pal = toPaletteNumber(pony);
|
||||
|
||||
if (layer.extra !== undefined) {
|
||||
const set = pal[layer.extra] as PaletteSpriteSet;
|
||||
|
||||
if (extra) {
|
||||
set.palette = mockPaletteManager.add([0, BLACK, BLACK, BLACK, BLACK, BLACK, BLACK, BLACK, BLACK]);
|
||||
} else {
|
||||
set.extraPalette = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
drawPony(batch, pal, state, PONY_X, PONY_Y + offsetY + toInt(layer.shiftY), options);
|
||||
});
|
||||
}
|
||||
|
||||
function createPsdPatternLayers(sheet: Sheet, rows: number, cols: number, layer: SheetLayer): Layer[] {
|
||||
const sets = getSets(sheet, layer.set!, layer.setOverride);
|
||||
const patterns = layer.patterns || (sets ? maxPatterns(sets) : 0) || 6;
|
||||
|
||||
return compact([
|
||||
layer.extra && {
|
||||
name: 'extra',
|
||||
canvas: drawPsdLayer(sheet, rows, cols, layer, -1, true),
|
||||
},
|
||||
{
|
||||
name: 'color',
|
||||
canvas: drawPsdLayer(sheet, rows, cols, layer),
|
||||
},
|
||||
...times(patterns, i => ({
|
||||
name: `pattern ${i}`,
|
||||
canvas: drawPsdLayer(sheet, rows, cols, layer, i),
|
||||
hidden: true,
|
||||
clipping: true,
|
||||
blendMode: 'multiply',
|
||||
})),
|
||||
]);
|
||||
}
|
||||
|
||||
function createPsdLayer(sheet: Sheet, rows: number, cols: number, layer: SheetLayer): Layer {
|
||||
const name = layer.name;
|
||||
|
||||
if (layer.set) {
|
||||
return { name, children: createPsdPatternLayers(sheet, rows, cols, layer) };
|
||||
} else {
|
||||
return { name, canvas: drawPsdLayer(sheet, rows, cols, layer) };
|
||||
}
|
||||
}
|
||||
|
||||
function drawFrames(
|
||||
rows: number, cols: number, w: number, h: number, offset: number,
|
||||
draw: (batch: ContextSpriteBatch, x: number, y: number) => void
|
||||
): HTMLCanvasElement {
|
||||
const canvas = createCanvas((offset * (cols - 1)) + w, h * rows);
|
||||
const buffer = createCanvas(w, h);
|
||||
const batch = new ContextSpriteBatch(buffer);
|
||||
const viewContext = canvas.getContext('2d')!;
|
||||
viewContext.save();
|
||||
disableImageSmoothing(viewContext);
|
||||
|
||||
for (let y = 0; y < rows; y++) {
|
||||
for (let x = 0; x < cols; x++) {
|
||||
batch.start(sprites.paletteSpriteSheet, 0);
|
||||
draw(batch, x, y);
|
||||
batch.end();
|
||||
viewContext.drawImage(buffer, x * offset, y * h);
|
||||
}
|
||||
}
|
||||
|
||||
viewContext.restore();
|
||||
return canvas;
|
||||
}
|
||||
|
||||
function createBackground(rows: number, cols: number, width: number, height: number, sheet: Sheet) {
|
||||
const canvas = createCanvas(width, height);
|
||||
const context = canvas.getContext('2d')!;
|
||||
|
||||
if (sheet.wrap) {
|
||||
cols = sheet.wrap;
|
||||
rows = Math.ceil(rows / sheet.wrap);
|
||||
}
|
||||
|
||||
fillRect(context, 'lightgreen', 0, 0, canvas.width, canvas.height);
|
||||
|
||||
context.globalAlpha = 0.1;
|
||||
|
||||
for (let y = 0; y < rows; y++) {
|
||||
for (let x = 0; x < cols; x++) {
|
||||
const color = (x + (y % 2)) % 2 ? 'green' : 'blue';
|
||||
fillRect(context, color, x * sheet.offset, y * sheet.height, sheet.width, sheet.height);
|
||||
}
|
||||
}
|
||||
|
||||
context.globalAlpha = 1;
|
||||
|
||||
for (let y = 0; y < rows; y++) {
|
||||
for (let x = 0; x < cols; x++) {
|
||||
const gap = sheet.width - sheet.offset;
|
||||
const index = sheet.wrap ? (cols * y + x) : x;
|
||||
drawPixelTextOnCanvas(context, x * sheet.offset + gap + 2, y * sheet.height + 2, 0x76c189ff, index.toString());
|
||||
}
|
||||
}
|
||||
|
||||
return canvas;
|
||||
}
|
||||
|
||||
function createRefsCanvas(width: number, height: number, offsetY = 0) {
|
||||
const canvas = createCanvas(width, height);
|
||||
const context = canvas.getContext('2d')!;
|
||||
const h = 2;
|
||||
|
||||
patternColors.forEach((c, i) => fillRect(context, colorToCSS(c), 5, 5 + h * i + offsetY, 10, h));
|
||||
|
||||
fillRect(context, '#888888', 25, 10 + offsetY, 8, 10);
|
||||
fillRect(context, '#d9d9d9', 27, 12 + offsetY, 4, 4);
|
||||
fillRect(context, '#afafaf', 27, 16 + offsetY, 4, 2);
|
||||
fillRect(context, '#9f9f9f', 20, 5 + offsetY, 8, 10);
|
||||
fillRect(context, '#ffffff', 22, 7 + offsetY, 4, 4);
|
||||
fillRect(context, '#cdcdcd', 22, 11 + offsetY, 4, 2);
|
||||
|
||||
return canvas;
|
||||
}
|
||||
|
||||
function createPony(): PonyInfoNumber {
|
||||
const pony = decompressPony(compressPonyString(createDefaultPony()));
|
||||
pony.mane!.type = 0;
|
||||
pony.backMane!.type = 0;
|
||||
pony.tail!.type = 0;
|
||||
pony.coatFill = DEFAULT_COLOR;
|
||||
pony.lockCoatOutline = true;
|
||||
pony.lockBackLegAccessory = false;
|
||||
return syncLockedPonyInfoNumber(pony);
|
||||
}
|
||||
|
||||
export function drawPsd(psd: Psd, scale: number, canvas?: HTMLCanvasElement): HTMLCanvasElement {
|
||||
const buffer = canvas || createCanvas(100, 100);
|
||||
buffer.width = psd.width * scale;
|
||||
buffer.height = psd.height * scale;
|
||||
const context = buffer.getContext('2d')!;
|
||||
context.save();
|
||||
context.scale(scale, scale);
|
||||
disableImageSmoothing(context);
|
||||
drawLayer(psd, context);
|
||||
context.restore();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
function drawLayer(layer: Layer, context: CanvasRenderingContext2D) {
|
||||
if (!layer.hidden) {
|
||||
layer.canvas && context.drawImage(layer.canvas, 0, 0);
|
||||
layer.children && layer.children.forEach(c => drawLayer(c, context));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user