From d97004a6ddbd7613a99736d45c53ed5547aabd1d Mon Sep 17 00:00:00 2001 From: modeco80 Date: Sun, 25 Aug 2024 09:22:06 -0400 Subject: [PATCH] move image drawing to core also adjust for some api changes I forgot to fix --- msagent.js/core/src/image.ts | 49 ++++++++++++++++++++++++++++++++++++ msagent.js/core/src/index.ts | 1 + msagent.js/web/src/agent.ts | 41 +++--------------------------- msagent.js/web/src/index.ts | 1 + webapp/src/ts/testbed.ts | 5 +++- 5 files changed, 58 insertions(+), 39 deletions(-) create mode 100644 msagent.js/core/src/image.ts diff --git a/msagent.js/core/src/image.ts b/msagent.js/core/src/image.ts new file mode 100644 index 0000000..7e1e02d --- /dev/null +++ b/msagent.js/core/src/image.ts @@ -0,0 +1,49 @@ +import { AcsImageEntry } from './structs/image'; + +// probably should be in a utility module +function dwAlign(off: number): number { + let ul = off >>> 0; + + ul += 3; + ul >>= 2; + ul <<= 2; + return ul; +} + +import { BufferStream, SeekDir } from './buffer'; +import { RGBAColor } from './structs/core'; + +/// Draws an ACS image to a newly allocated buffer. +/// This function normalizes the agent 8bpp DIB format to a saner RGBA format, +/// that can be directly converted to an ImageData for drawing to a web canvas. +/// +/// However, that should be done (and is done) by a higher level web layer. +export function imageDrawToBuffer(imageEntry: AcsImageEntry, palette: RGBAColor[]) { + let rgbaBuffer = new Uint32Array(imageEntry.image.width * imageEntry.image.height); + + let buffer = imageEntry.image.data; + let bufStream = new BufferStream(buffer); + + let rows = new Array(imageEntry.image.height - 1); + + // Read all the rows bottom-up first. This idiosyncracy is due to the fact + // that the bitmap data is actually formatted to be used as a GDI DIB + // (device-independent bitmap), so it inherits all the strange baggage from that. + for (let y = imageEntry.image.height - 1; y >= 0; --y) { + let row = bufStream.subBuffer(imageEntry.image.width).raw(); + rows[y] = row.slice(0, imageEntry.image.width); + + // Seek to the next DWORD aligned spot to get to the next row. + // For most images this may mean not seeking at all. + bufStream.seek(dwAlign(bufStream.tell()), SeekDir.BEG); + } + + // Next, draw the rows converted to RGBA, top down (so it's drawn as you'd expect) + for (let y = 0; y < imageEntry.image.height - 1; ++y) { + let row = rows[y]; + for (let x = 0; x < imageEntry.image.width; ++x) { + rgbaBuffer[y * imageEntry.image.width + x] = palette[row[x]].to_rgba(); + } + } + return rgbaBuffer; +} diff --git a/msagent.js/core/src/index.ts b/msagent.js/core/src/index.ts index 823640f..86ec132 100644 --- a/msagent.js/core/src/index.ts +++ b/msagent.js/core/src/index.ts @@ -9,4 +9,5 @@ export * from './structs/image.js'; export * from './types.js'; export * from './decompress.js'; +export * from './image.js'; export * from './parse.js'; diff --git a/msagent.js/web/src/agent.ts b/msagent.js/web/src/agent.ts index 1c8d353..29c727f 100644 --- a/msagent.js/web/src/agent.ts +++ b/msagent.js/web/src/agent.ts @@ -1,20 +1,12 @@ -import { BufferStream, SeekDir } from '@msagent.js/core'; -import { AcsData } from './character.js'; +import { BufferStream, SeekDir, imageDrawToBuffer } from '@msagent.js/core'; +import { AcsData } from '@msagent.js/core'; import { ContextMenu, ContextMenuItem } from './contextmenu.js'; import { AcsAnimation, AcsAnimationFrameInfo } from '@msagent.js/core'; import { AcsImageEntry } from '@msagent.js/core'; import { Point, Size } from '@msagent.js/core'; import { wordballoonDrawImage, wordballoonDrawText } from './wordballoon.js'; -// probably should be in a utility module -function dwAlign(off: number): number { - let ul = off >>> 0; - ul += 3; - ul >>= 2; - ul <<= 2; - return ul; -} function randint(min: number, max: number) { return Math.floor(Math.random() * (max - min) + min); @@ -316,34 +308,7 @@ export class Agent { // Draw a single image from the agent's image table. drawImage(imageEntry: AcsImageEntry, xOffset: number, yOffset: number) { - let rgbaBuffer = new Uint32Array(imageEntry.image.width * imageEntry.image.height); - - let buffer = imageEntry.image.data; - let bufStream = new BufferStream(buffer); - - let rows = new Array(imageEntry.image.height - 1); - - // Read all the rows bottom-up first. This idiosyncracy is due to the fact - // that the bitmap data is actually formatted to be used as a GDI DIB - // (device-independent bitmap), so it inherits all the strange baggage from that. - for (let y = imageEntry.image.height - 1; y >= 0; --y) { - let row = bufStream.subBuffer(imageEntry.image.width).raw(); - let rowResized = row.slice(0, imageEntry.image.width); - rows[y] = rowResized; - - // Seek to the next DWORD aligned spot to get to the next row. - // For most images this may mean not seeking at all. - bufStream.seek(dwAlign(bufStream.tell()), SeekDir.BEG); - } - - // Next, draw the rows converted to RGBA, top down (so it's drawn as you'd expect) - for (let y = 0; y < imageEntry.image.height - 1; ++y) { - let row = rows[y]; - for (let x = 0; x < imageEntry.image.width; ++x) { - rgbaBuffer[y * imageEntry.image.width + x] = this.data.characterInfo.palette[row[x]].to_rgba(); - } - } - + let rgbaBuffer = imageDrawToBuffer(imageEntry, this.data.characterInfo.palette); let data = new ImageData(new Uint8ClampedArray(rgbaBuffer.buffer), imageEntry.image.width, imageEntry.image.height); this.ctx.putImageData(data, xOffset, yOffset); } diff --git a/msagent.js/web/src/index.ts b/msagent.js/web/src/index.ts index 8667ff9..e321a12 100644 --- a/msagent.js/web/src/index.ts +++ b/msagent.js/web/src/index.ts @@ -1,6 +1,7 @@ import { compressInit } from '@msagent.js/core'; import { wordballoonInit } from './wordballoon.js'; +export { Agent } from './agent.js'; export * from './character.js'; export * from './sprite.js'; export * from './wordballoon.js'; diff --git a/webapp/src/ts/testbed.ts b/webapp/src/ts/testbed.ts index 1c387e9..915d5d1 100644 --- a/webapp/src/ts/testbed.ts +++ b/webapp/src/ts/testbed.ts @@ -2,6 +2,8 @@ // This will go away when it isn't needed import * as msagent from '@msagent.js/web'; +import * as msagent_core from '@msagent.js/core'; + let w = window as any; w.agents = []; let input = document.getElementById('testbed-input') as HTMLInputElement; @@ -12,7 +14,8 @@ input.addEventListener('change', async () => { let buffer = await input.files![0].arrayBuffer(); console.log('Creating agent'); - let agent = msagent.agentCreateCharacter(new Uint8Array(buffer)); + let agentParsedData = msagent_core.agentCharacterParseACS(new msagent_core.BufferStream(new Uint8Array(buffer))); + let agent = new msagent.Agent(agentParsedData); w.agents.push(agent);