msagent.js: fixed bitmap drawing
Now everything is top down, correctly colored, as you'd expect! Woohoo!
This commit is contained in:
parent
9f211ec36a
commit
76541dd31d
3 changed files with 94 additions and 47 deletions
|
@ -1,45 +1,86 @@
|
|||
import { AcsData } from "./character.js";
|
||||
import { AcsAnimationFrameInfo } from "./structs/animation.js";
|
||||
import { BufferStream, SeekDir } from './buffer.js';
|
||||
import { AcsData } from './character.js';
|
||||
import { AcsAnimationFrameInfo } from './structs/animation.js';
|
||||
import { AcsImageEntry } from './structs/image.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;
|
||||
}
|
||||
|
||||
export class Agent {
|
||||
private data: AcsData;
|
||||
private cnv: HTMLCanvasElement;
|
||||
private ctx: CanvasRenderingContext2D;
|
||||
constructor(data: AcsData) {
|
||||
this.data = data;
|
||||
this.cnv = document.createElement("canvas");
|
||||
this.ctx = this.cnv.getContext("2d")!;
|
||||
this.cnv.width = data.characterInfo.charWidth;
|
||||
this.cnv.height = data.characterInfo.charHeight;
|
||||
this.cnv.style.position = "fixed";
|
||||
this.hide();
|
||||
this.renderFrame(this.data.animInfo[0].animationData.frameInfo[0]);
|
||||
}
|
||||
private data: AcsData;
|
||||
private cnv: HTMLCanvasElement;
|
||||
private ctx: CanvasRenderingContext2D;
|
||||
constructor(data: AcsData) {
|
||||
this.data = data;
|
||||
this.cnv = document.createElement('canvas');
|
||||
this.ctx = this.cnv.getContext('2d')!;
|
||||
this.cnv.width = data.characterInfo.charWidth;
|
||||
this.cnv.height = data.characterInfo.charHeight;
|
||||
this.cnv.style.position = 'fixed';
|
||||
this.hide();
|
||||
this.renderFrame(this.data.animInfo[0].animationData.frameInfo[0]);
|
||||
}
|
||||
|
||||
private renderFrame(frame: AcsAnimationFrameInfo) {
|
||||
for (const mimg of frame.images) {
|
||||
const img = this.data.images[mimg.imageIndex];
|
||||
let data = this.ctx.createImageData(img.image.width, img.image.height);
|
||||
for (let i = 0; i < img.image.data.length; i++) {
|
||||
let px = this.data.characterInfo.palette[img.image.data[i]];
|
||||
data.data[(i * 4)] = px.r;
|
||||
data.data[(i * 4) + 1] = px.g;
|
||||
data.data[(i * 4) + 2] = px.b;
|
||||
data.data[(i * 4) + 3] = px.a;
|
||||
}
|
||||
this.ctx.putImageData(data, mimg.xOffset, mimg.yOffset);
|
||||
}
|
||||
}
|
||||
private renderFrame(frame: AcsAnimationFrameInfo) {
|
||||
this.ctx.clearRect(0, 0, this.cnv.width, this.cnv.height);
|
||||
for (const mimg of frame.images) {
|
||||
this.drawImage(this.data.images[mimg.imageIndex], mimg.xOffset, mimg.yOffset);
|
||||
}
|
||||
}
|
||||
|
||||
addToDom(parent: HTMLElement = document.body) {
|
||||
parent.appendChild(this.cnv);
|
||||
}
|
||||
// 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);
|
||||
|
||||
show() {
|
||||
this.cnv.style.display = "block";
|
||||
}
|
||||
let buffer = imageEntry.image.data;
|
||||
let bufStream = new BufferStream(buffer);
|
||||
|
||||
hide() {
|
||||
this.cnv.style.display = "none";
|
||||
}
|
||||
}
|
||||
let rows = new Array<Uint8Array>(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 data = new ImageData(new Uint8ClampedArray(rgbaBuffer.buffer), imageEntry.image.width, imageEntry.image.height);
|
||||
this.ctx.putImageData(data, xOffset, yOffset);
|
||||
}
|
||||
|
||||
addToDom(parent: HTMLElement = document.body) {
|
||||
parent.appendChild(this.cnv);
|
||||
}
|
||||
|
||||
show() {
|
||||
this.cnv.style.display = 'block';
|
||||
// TODO: play the show animation
|
||||
}
|
||||
|
||||
hide() {
|
||||
// TODO: play the hide animation (then clear the canvas)
|
||||
// (if not constructing. We can probably just duplicate this one line and put it in the constructor tbh)
|
||||
this.cnv.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -91,9 +91,9 @@ export class RGBAColor {
|
|||
b = 0;
|
||||
a = 0;
|
||||
|
||||
// Does what it says on the tin, converts to RGBA
|
||||
// Does what it says on the tin, converts to a packed 32-bit RGBA value
|
||||
to_rgba(): number {
|
||||
return (this.r << 24) | (this.g << 16) | (this.b << 8) | this.a;
|
||||
return (this.a << 24) | (this.r << 16) | (this.g << 8) | this.b;
|
||||
}
|
||||
|
||||
static from_gdi_rgbquad(val: number) {
|
||||
|
@ -102,11 +102,18 @@ export class RGBAColor {
|
|||
// Extract individual RGB values from the RGBQUAD
|
||||
// We ignore the last 8 bits because it is always left
|
||||
// as 0x00 or if uncleared, just random garbage.
|
||||
quad.r = (val & 0xff000000) >> 24;
|
||||
quad.g = (val & 0x00ff0000) >> 16;
|
||||
quad.b = (val & 0x0000ff00) >> 8;
|
||||
quad.a = 255;
|
||||
|
||||
// this is wrong
|
||||
//quad.r = (val & 0xff000000) >> 24;
|
||||
//quad.g = (val & 0x00ff0000) >> 16;
|
||||
//quad.b = (val & 0x0000ff00) >> 8;
|
||||
|
||||
quad.r = (val & 0x000000ff);
|
||||
quad.g = (val & 0x0000ff00) >> 8;
|
||||
quad.b = (val & 0x00ff0000) >> 16;
|
||||
|
||||
quad.a = 255;
|
||||
console.log(val, quad);
|
||||
return quad;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,13 +6,12 @@ import * as msagent from "@msagent-chat/msagent.js";
|
|||
let input = document.getElementById("testbed-input") as HTMLInputElement;
|
||||
|
||||
input.addEventListener("change", async () => {
|
||||
|
||||
|
||||
let buffer = await input.files![0].arrayBuffer();
|
||||
|
||||
console.log("About to parse character");
|
||||
let agent = msagent.agentParseCharacterTestbed(new Uint8Array(buffer));
|
||||
agent.addToDom(document.body);
|
||||
|
||||
agent.show();
|
||||
console.log("parsed character");
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue