mirror of
https://github.com/Terncode/pixel.horse.git
synced 2026-09-24 21:55:52 +02:00
Revert codestyle changes (#40)
* Revert "7f7efbb94bab8574e42d942ad82d414e007b2970" Code style changes should probably be part of a PR
This commit is contained in:
+351
-351
@@ -7,486 +7,486 @@ 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;
|
||||
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));
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < length; i = (i + 1) | 0) {
|
||||
if (adat[i] !== bdat[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
for (let i = channel | 0; i < length; i = (i + 4) | 0) {
|
||||
if (adat[i] !== bdat[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
function trimImageData(data: ImageData): Rect {
|
||||
const width = data.width | 0;
|
||||
const imageData = data.data;
|
||||
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;
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
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++;
|
||||
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 };
|
||||
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 };
|
||||
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 };
|
||||
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;
|
||||
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;
|
||||
outputWidth = outputWidth | 0;
|
||||
|
||||
if (((x + w) | 0) > outputWidth || ((y + h) | 0) > outputWidth) {
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
return true;
|
||||
}
|
||||
|
||||
interface Line {
|
||||
start: number;
|
||||
length: number;
|
||||
start: number;
|
||||
length: number;
|
||||
}
|
||||
|
||||
interface Taken {
|
||||
lines: Line[][];
|
||||
data: Uint8Array; // TODO: remove completely, just use lines
|
||||
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;
|
||||
const maxY = (outputWidth - height) | 0;
|
||||
|
||||
for (let y = 0; y < maxY; y = (y + 1) | 0) {
|
||||
const spans = lines[y];
|
||||
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 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 };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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]`);
|
||||
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;
|
||||
const maxY = outputWidth - height;
|
||||
|
||||
for (let layer = 0; layer < takens.length; layer = (layer + 1) | 0) {
|
||||
const { data, lines } = takens[layer];
|
||||
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 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 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 };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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]`);
|
||||
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]);
|
||||
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;
|
||||
sprite.x = x;
|
||||
sprite.y = y;
|
||||
sprite.layer = layer;
|
||||
|
||||
const w = sprite.w;
|
||||
const right = x + w;
|
||||
const w = sprite.w;
|
||||
const right = x + w;
|
||||
|
||||
for (let il = 0; il < layers; il++) {
|
||||
const { data, lines } = taken[il + layer];
|
||||
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 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 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;
|
||||
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 (start >= right) // right of span
|
||||
break;
|
||||
|
||||
if (end <= right) // left of span
|
||||
continue;
|
||||
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 });
|
||||
}
|
||||
}
|
||||
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;
|
||||
// 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;
|
||||
// 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 (xx === start) {
|
||||
// start++;
|
||||
// lineMoved = true;
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (lineMoved) {
|
||||
// for (let x = start; x < outputWidth && data[getIndex(x, yy, outputWidth)] !== 0; x++) {
|
||||
// start++;
|
||||
// }
|
||||
// if (lineMoved) {
|
||||
// for (let x = start; x < outputWidth && data[getIndex(x, yy, outputWidth)] !== 0; x++) {
|
||||
// start++;
|
||||
// }
|
||||
|
||||
// span.start = start;
|
||||
// span.length = outputWidth - 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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
return false;
|
||||
}
|
||||
|
||||
function getSpriteImageData(s: ExtSprite): ImageData | undefined {
|
||||
if (!s.w || !s.h)
|
||||
return 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 };
|
||||
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();
|
||||
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);
|
||||
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})`);
|
||||
}
|
||||
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 => 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
|
||||
.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;
|
||||
});
|
||||
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 (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) {
|
||||
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);
|
||||
}
|
||||
if (match.layers >= maxLayers) {
|
||||
removeItem(pool, match);
|
||||
}
|
||||
|
||||
layered++;
|
||||
} else {
|
||||
pool.push(sprite);
|
||||
}
|
||||
layered++;
|
||||
} else {
|
||||
pool.push(sprite);
|
||||
}
|
||||
|
||||
return pool;
|
||||
}, [] as ExtSprite[]);
|
||||
}
|
||||
return pool;
|
||||
}, [] as ExtSprite[]);
|
||||
}
|
||||
|
||||
sprites.sort((a, b) => ((b.layers || 1) - (a.layers || 1)) || ((b.h * 1024 + b.w) - (a.h * 1024 + a.w)));
|
||||
sprites.sort((a, b) => ((b.layers || 1) - (a.layers || 1)) || ((b.h * 1024 + b.w) - (a.h * 1024 + a.w)));
|
||||
|
||||
maxY = 0;
|
||||
areaTaken = 0;
|
||||
maxY = 0;
|
||||
areaTaken = 0;
|
||||
|
||||
const taken: Taken[] = times(maxLayers, () => ({
|
||||
lines: times(outputWidth, () => [{ start: 0, length: outputWidth }]),
|
||||
data: new Uint8Array(outputWidth * outputWidth),
|
||||
}));
|
||||
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);
|
||||
}
|
||||
});
|
||||
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 (maxY > outputWidth) {
|
||||
throw new Error(`Exceeded sprite sheet size for ${name} (${size}x${size})`);
|
||||
}
|
||||
|
||||
if (log) {
|
||||
const efficiency = (areaTaken * 100 / outputWidth / maxY).toFixed();
|
||||
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`);
|
||||
}
|
||||
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.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;
|
||||
});
|
||||
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')!;
|
||||
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 (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);
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
});
|
||||
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));
|
||||
}
|
||||
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,
|
||||
};
|
||||
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
|
||||
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);
|
||||
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];
|
||||
}
|
||||
}
|
||||
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);
|
||||
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));
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user