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:
+120
-44
@@ -52,6 +52,58 @@ const getPngs = (directory: string) => fs.readdirSync(directory).filter(isPng);
|
||||
const getFrames = (layers: Layer[]) => layers.filter(nameMatches(/^frame/)).sort(compareLayers);
|
||||
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) {
|
||||
return imageToCanvas(loadImage(fileName));
|
||||
}
|
||||
@@ -1055,36 +1107,42 @@ function createResult(): Result {
|
||||
};
|
||||
}
|
||||
|
||||
function createPonySprites(result: Result) {
|
||||
function createPonySprites(result: Result, log: boolean) {
|
||||
logStep('eyes', () => {
|
||||
getEyesFromPsd(result, ponyPsd('eyes.psd'), ponyPsd('irises.psd'));
|
||||
getMuzzlesFromPsd(result, ponyPsd('muzzles.psd'));
|
||||
getBlushFromPsd(result, ponyPsd('blush.psd'));
|
||||
getPonyShadowsAndSelection(result, ponyPsd('shadows.psd'));
|
||||
|
||||
}, log);
|
||||
logStep('muzzles', () => getMuzzlesFromPsd(result, ponyPsd('muzzles.psd')), log);
|
||||
logStep('blush', () => getBlushFromPsd(result, ponyPsd('blush.psd')), log);
|
||||
logStep('shadows', () => getPonyShadowsAndSelection(result, ponyPsd('shadows.psd')), log);
|
||||
logStep('cms', () => {
|
||||
result.objects2.cms = addCMSprite(result.sprites, false);
|
||||
result.objects2.cmsFlip = addCMSprite(result.sprites, true);
|
||||
}, log);
|
||||
|
||||
sheets
|
||||
.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'));
|
||||
// result.objects2['wings: AnimatedSprites'].slice(3)
|
||||
// .forEach((frame: any, i: number) => frame[1] = [pegasus[i]]);
|
||||
|
||||
getTreesFromPsds(result, path.join(sourcePath, 'trees'));
|
||||
getObjectsFromPsds(result, path.join(sourcePath, 'objects'));
|
||||
logStep('trees', () => getTreesFromPsds(result, path.join(sourcePath, 'trees')), log);
|
||||
logStep('objects', () => getObjectsFromPsds(result, path.join(sourcePath, 'objects')), log);
|
||||
|
||||
createButtons(result, path.join(sourcePath, 'buttons'));
|
||||
createAnimations(result, path.join(sourcePath, 'animations'));
|
||||
createOtherSprites(result, path.join(sourcePath, 'sprites'));
|
||||
createOtherSpritesPalette(result, path.join(sourcePath, 'sprites-palette'));
|
||||
createIcons(result, path.join(sourcePath, 'icons'));
|
||||
createOtherSpritesAnimations(result, path.join(sourcePath, 'sprites-animations'));
|
||||
createEmoteAnimations(result, path.join(sourcePath, 'emotes'));
|
||||
createWalls(result, path.join(sourcePath, 'walls'));
|
||||
logStep('buttons', () => createButtons(result, path.join(sourcePath, 'buttons')), log);
|
||||
logStep('animations', () => createAnimations(result, path.join(sourcePath, 'animations')), log);
|
||||
logStep('sprites', () => createOtherSprites(result, path.join(sourcePath, 'sprites')), log);
|
||||
logStep('sprites-palette', () => createOtherSpritesPalette(result, path.join(sourcePath, 'sprites-palette')), log);
|
||||
logStep('icons', () => createIcons(result, path.join(sourcePath, 'icons')), log);
|
||||
logStep('sprites-animations', () => createOtherSpritesAnimations(result, path.join(sourcePath, 'sprites-animations')), log);
|
||||
logStep('emotes', () => createEmoteAnimations(result, path.join(sourcePath, 'emotes')), log);
|
||||
logStep('walls', () => createWalls(result, path.join(sourcePath, 'walls')), log);
|
||||
|
||||
createPixelSprites(result);
|
||||
logStep('pixel sprites', () => createPixelSprites(result), log);
|
||||
}
|
||||
|
||||
interface WallConfig {
|
||||
@@ -1329,15 +1387,7 @@ const SUPPORTER1 = 0xf86754ff;
|
||||
const SUPPORTER2_BANDS = [0xffdfc1ff, 0xffcd99ff, 0xff9f3bff, 0xd97e09ff];
|
||||
const SUPPORTER3_BANDS = [0xffffffff, 0xfffda4ff, 0xffea3bff, 0xfdbb0bff];
|
||||
|
||||
export function createSprites(log: boolean) {
|
||||
mkdir(destPath);
|
||||
mkdir(generatedPath);
|
||||
|
||||
const result = createResult();
|
||||
|
||||
createPonySprites(result);
|
||||
createTileSprites(result);
|
||||
|
||||
function createFonts(result: Result) {
|
||||
const mainFont = fontCanvas('main.psd');
|
||||
const mainEmoji = fontCanvas('emoji.psd');
|
||||
const tinyFont = fontCanvas('tiny.psd');
|
||||
@@ -1368,10 +1418,44 @@ export function createSprites(log: boolean) {
|
||||
const monoFontSpritesPal = createFont(stripedMonoFont, 8, 9,
|
||||
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);
|
||||
const ponySheet2 = createSpriteSheet('ponySheet2', result.sprites, log, 1024, 'black', true);
|
||||
export function createSprites(log: boolean) {
|
||||
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(ponySheet2.sprites, result.objects2, 'pixelRect2', 'pixel2');
|
||||
@@ -1395,18 +1479,8 @@ export function createSprites(log: boolean) {
|
||||
paletteSprites: ponySheet2.sprites,
|
||||
result,
|
||||
palettes,
|
||||
fonts: {
|
||||
font: fontSprites,
|
||||
emoji: emojiSprites,
|
||||
fontSmall: smallFontSprites,
|
||||
fontMono: monoFontSprites,
|
||||
},
|
||||
fontsPal: {
|
||||
fontPal: fontSpritesPal,
|
||||
emojiPal: emojiSpritesPal,
|
||||
fontSmallPal: smallFontSpritesPal,
|
||||
fontMonoPal: monoFontSpritesPal,
|
||||
},
|
||||
fonts,
|
||||
fontsPal,
|
||||
namedPalettes: {
|
||||
defaultPalette: 0,
|
||||
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
|
||||
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) {
|
||||
const start = Date.now();
|
||||
createSprites(true);
|
||||
createSprites(false);
|
||||
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} }`);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ 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';
|
||||
|
||||
@@ -37,13 +36,18 @@ function isIdenticalData(a: ImageData, b: ImageData) {
|
||||
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) {
|
||||
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 adata = a.data;
|
||||
const bdata = b.data;
|
||||
|
||||
if (!adata || !bdata) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const length = (adata.width * adata.height * 4) | 0;
|
||||
const adat = adata.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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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 ix = 0; ix < w; ix++) {
|
||||
if (taken[getIndex((ix + x) | 0, (iy + y) | 0, outputWidth)] !== 0) {
|
||||
return false;
|
||||
const spans = lines[y + iy];
|
||||
let found = 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 {
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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 };
|
||||
}
|
||||
}
|
||||
@@ -175,7 +192,7 @@ function getFirstFreePacked(outputWidth: number, width: number, height: number,
|
||||
const maxY = outputWidth - height;
|
||||
|
||||
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) {
|
||||
const spans = lines[y];
|
||||
@@ -186,7 +203,7 @@ function getFirstFreePacked(outputWidth: number, width: number, height: number,
|
||||
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)) {
|
||||
if (isEmpty(x, y, width, height, outputWidth, lines)) {
|
||||
return { x, y, layer };
|
||||
}
|
||||
}
|
||||
@@ -211,17 +228,12 @@ function positionSprite(sprite: ExtSprite, outputWidth: number, taken: Taken[],
|
||||
const right = x + w;
|
||||
|
||||
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++) {
|
||||
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;
|
||||
@@ -254,35 +266,17 @@ function positionSprite(sprite: ExtSprite, outputWidth: number, taken: Taken[],
|
||||
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);
|
||||
function hasShading(s: ExtSprite) {
|
||||
const data = s.data;
|
||||
|
||||
if (!data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let y = 0; y < data.height; y++) {
|
||||
for (let x = 0; x < data.width; x++) {
|
||||
@@ -305,6 +299,18 @@ function getSpriteImageData(s: ExtSprite): ImageData | undefined {
|
||||
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) {
|
||||
const maxLayers = 4;
|
||||
const sprites = images.slice();
|
||||
@@ -323,16 +329,27 @@ export function createSpriteSheet(name: string, images: ExtSprite[], log: boolea
|
||||
sprites
|
||||
.forEach(s => s.data = getSpriteImageData(s));
|
||||
|
||||
const seen: Map<string, ExtSprite[]> = new Map();
|
||||
|
||||
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];
|
||||
.forEach(s => {
|
||||
if (s.w && s.h && s.data) {
|
||||
const key = `${s.w}x${s.h}|${getDataHash(s.data)}`;
|
||||
const candidates = seen.get(key);
|
||||
|
||||
if (candidates) {
|
||||
const duplicate = candidates.find(c => isIdenticalSprite(c, s));
|
||||
|
||||
if (duplicate) {
|
||||
s.duplicateOf = duplicate;
|
||||
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, () => ({
|
||||
lines: times(outputWidth, () => [{ start: 0, length: outputWidth }]),
|
||||
data: new Uint8Array(outputWidth * outputWidth),
|
||||
}));
|
||||
|
||||
sprites
|
||||
@@ -439,17 +455,17 @@ export function createSpriteSheet(name: string, images: ExtSprite[], log: boolea
|
||||
const alphaData = alphaContext.getImageData(0, 0, image.width, image.height);
|
||||
|
||||
sprites
|
||||
.filter(s => !s.duplicateOf && s.w && s.h)
|
||||
.filter(s => !s.duplicateOf && s.w && s.h && s.data)
|
||||
.forEach(s => {
|
||||
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 {
|
||||
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) {
|
||||
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));
|
||||
}
|
||||
|
||||
const byIndex = new Map<number, ExtSprite>();
|
||||
|
||||
sprites.forEach(s => {
|
||||
if (s.index != null) {
|
||||
byIndex.set(s.index, s);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
sprites: images.map((_, index) => findByIndex(sprites, index) || null),
|
||||
sprites: images.map((_, index) => byIndex.get(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: ImageData, dst: ImageData, srcChannel: number, dstChannel: 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++) {
|
||||
const sOffset = ((y * srcWidth) * 4 + srcChannel) | 0;
|
||||
const dOffset = ((((y + dy) * dstWidth) + dx) * 4 + dstChannel) | 0;
|
||||
|
||||
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];
|
||||
dst.data[dOffset + (x * 4) | 0] = srcData[sOffset + (x * 4) | 0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user