Decode boxes, rearrange some code
This commit is contained in:
parent
352fb0ddd0
commit
7e3d096d47
231
index.js
231
index.js
|
@ -1,3 +1,5 @@
|
||||||
|
const LE = true // little-endian
|
||||||
|
|
||||||
const readBinary = async (url) => {
|
const readBinary = async (url) => {
|
||||||
const response = await fetch(url)
|
const response = await fetch(url)
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
@ -7,92 +9,6 @@ const readBinary = async (url) => {
|
||||||
return new DataView(await response.arrayBuffer())
|
return new DataView(await response.arrayBuffer())
|
||||||
}
|
}
|
||||||
|
|
||||||
const decodeHowHeld = (byte) => {
|
|
||||||
const heldVal = byte & 0xc0
|
|
||||||
if (heldVal == 0) {
|
|
||||||
return "swing"
|
|
||||||
} else if (heldVal == 0x40) {
|
|
||||||
return "out"
|
|
||||||
} else if (heldVal == 0x80) {
|
|
||||||
return "both"
|
|
||||||
} else {
|
|
||||||
return "at_side"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const LE = true // little-endian
|
|
||||||
|
|
||||||
const decodeCelType = (byte) => {
|
|
||||||
const typeVal = byte & 0xc0
|
|
||||||
if (typeVal == 0x00) {
|
|
||||||
if ((byte & 0x20) == 0) {
|
|
||||||
return "bitmap"
|
|
||||||
} else {
|
|
||||||
return "text"
|
|
||||||
}
|
|
||||||
} else if (typeVal == 0x40) {
|
|
||||||
return "trap"
|
|
||||||
} else if (typeVal == 0x80) {
|
|
||||||
return "box"
|
|
||||||
} else {
|
|
||||||
return "circle"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const emptyBitmap = (w, h) => {
|
|
||||||
const bitmap = []
|
|
||||||
for (let y = 0; y < h; y ++) {
|
|
||||||
const scanline = []
|
|
||||||
for (let x = 0; x < w; x ++) {
|
|
||||||
scanline.push(0)
|
|
||||||
scanline.push(0)
|
|
||||||
scanline.push(0)
|
|
||||||
scanline.push(0)
|
|
||||||
}
|
|
||||||
bitmap.push(scanline)
|
|
||||||
}
|
|
||||||
return bitmap
|
|
||||||
}
|
|
||||||
|
|
||||||
const celDecoder = {}
|
|
||||||
celDecoder.bitmap = (data, cel) => {
|
|
||||||
const bitmap = emptyBitmap(cel.width, cel.height)
|
|
||||||
let ibmp = 0
|
|
||||||
const end = cel.width * cel.height
|
|
||||||
const putByte = (byte) => {
|
|
||||||
const x = Math.floor(ibmp / cel.height) * 4
|
|
||||||
const y = (cel.height - (ibmp % cel.height)) - 1
|
|
||||||
bitmap[y][x] = (byte & 0xc0) >> 6
|
|
||||||
bitmap[y][x + 1] = (byte & 0x3c) >> 4
|
|
||||||
bitmap[y][x + 2] = (byte & 0x0c) >> 2
|
|
||||||
bitmap[y][x + 3] = (byte & 0x03)
|
|
||||||
ibmp ++
|
|
||||||
}
|
|
||||||
let i = 6
|
|
||||||
while (ibmp < end) {
|
|
||||||
const byte = data.getUint8(i)
|
|
||||||
i ++
|
|
||||||
if (byte == 0) {
|
|
||||||
const count = data.getUint8(i)
|
|
||||||
i ++
|
|
||||||
if ((count & 0x80) == 0) {
|
|
||||||
const val = data.getUint8(i)
|
|
||||||
i ++
|
|
||||||
for (let repeat = 0; repeat < count; repeat ++) {
|
|
||||||
putByte(val)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// transparent run
|
|
||||||
for (let repeat = 0; repeat < (count & 0x7f); repeat ++) {
|
|
||||||
putByte(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
putByte(byte)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cel.bitmap = bitmap
|
|
||||||
}
|
|
||||||
|
|
||||||
const makeCanvas = (w, h) => {
|
const makeCanvas = (w, h) => {
|
||||||
const canvas = document.createElement("canvas")
|
const canvas = document.createElement("canvas")
|
||||||
canvas.width = w
|
canvas.width = w
|
||||||
|
@ -100,7 +16,7 @@ const makeCanvas = (w, h) => {
|
||||||
return canvas
|
return canvas
|
||||||
}
|
}
|
||||||
|
|
||||||
const drawBitmap = (bitmap) => {
|
const canvasFromBitmap = (bitmap) => {
|
||||||
const h = bitmap.length
|
const h = bitmap.length
|
||||||
const w = bitmap[0].length * 2
|
const w = bitmap[0].length * 2
|
||||||
const canvas = makeCanvas(w, h)
|
const canvas = makeCanvas(w, h)
|
||||||
|
@ -141,6 +57,128 @@ const drawBitmap = (bitmap) => {
|
||||||
return canvas
|
return canvas
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JS bitmap format: array of scanlines, each scanline being an array of numbers from 0-3
|
||||||
|
const emptyBitmap = (w, h, color = 0) => {
|
||||||
|
const bitmap = []
|
||||||
|
for (let y = 0; y < h; y ++) {
|
||||||
|
const scanline = []
|
||||||
|
for (let x = 0; x < w; x ++) {
|
||||||
|
scanline.push(color)
|
||||||
|
scanline.push(color)
|
||||||
|
scanline.push(color)
|
||||||
|
scanline.push(color)
|
||||||
|
}
|
||||||
|
bitmap.push(scanline)
|
||||||
|
}
|
||||||
|
return bitmap
|
||||||
|
}
|
||||||
|
|
||||||
|
const drawByte = (bitmap, x, y, byte) => {
|
||||||
|
bitmap[y][x] = (byte & 0xc0) >> 6
|
||||||
|
bitmap[y][x + 1] = (byte & 0x3c) >> 4
|
||||||
|
bitmap[y][x + 2] = (byte & 0x0c) >> 2
|
||||||
|
bitmap[y][x + 3] = (byte & 0x03)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prop decoding functions
|
||||||
|
const decodeHowHeld = (byte) => {
|
||||||
|
const heldVal = byte & 0xc0
|
||||||
|
if (heldVal == 0) {
|
||||||
|
return "swing"
|
||||||
|
} else if (heldVal == 0x40) {
|
||||||
|
return "out"
|
||||||
|
} else if (heldVal == 0x80) {
|
||||||
|
return "both"
|
||||||
|
} else {
|
||||||
|
return "at_side"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const decodeCelType = (byte) => {
|
||||||
|
const typeVal = byte & 0xc0
|
||||||
|
if (typeVal == 0x00) {
|
||||||
|
if ((byte & 0x20) == 0) {
|
||||||
|
return "bitmap"
|
||||||
|
} else {
|
||||||
|
return "text"
|
||||||
|
}
|
||||||
|
} else if (typeVal == 0x40) {
|
||||||
|
return "trap"
|
||||||
|
} else if (typeVal == 0x80) {
|
||||||
|
return "box"
|
||||||
|
} else {
|
||||||
|
return "circle"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const celDecoder = {}
|
||||||
|
celDecoder.bitmap = (data, cel) => {
|
||||||
|
// bitmap cells are RLE-encoded vertical strips of bytes. Decoding starts from the bottom-left
|
||||||
|
// and proceeds upwards until the top of the bitmap is hit; then then next vertical strip is decoded.
|
||||||
|
// Each byte describes four 2-bit pixels.
|
||||||
|
const bitmap = emptyBitmap(cel.width, cel.height)
|
||||||
|
let ibmp = 0
|
||||||
|
const end = cel.width * cel.height
|
||||||
|
const putByte = (byte) => {
|
||||||
|
const x = Math.floor(ibmp / cel.height) * 4
|
||||||
|
const y = (cel.height - (ibmp % cel.height)) - 1
|
||||||
|
drawByte(bitmap, x, y, byte)
|
||||||
|
ibmp ++
|
||||||
|
}
|
||||||
|
let i = 6
|
||||||
|
while (ibmp < end) {
|
||||||
|
const byte = data.getUint8(i)
|
||||||
|
i ++
|
||||||
|
if (byte == 0) {
|
||||||
|
// A zero byte denotes the start of a run of identical bytes. The second
|
||||||
|
// byte denotes the number of repetitions.
|
||||||
|
const count = data.getUint8(i)
|
||||||
|
i ++
|
||||||
|
if ((count & 0x80) == 0) {
|
||||||
|
// if the high bit of the count is not set, we read a third byte to
|
||||||
|
// determine the byte to repeat.
|
||||||
|
const val = data.getUint8(i)
|
||||||
|
i ++
|
||||||
|
for (let repeat = 0; repeat < count; repeat ++) {
|
||||||
|
putByte(val)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if the high bit of the count is set, the lower 7 bits are used as
|
||||||
|
// the count, and a fully transparent byte is repeated.
|
||||||
|
for (let repeat = 0; repeat < (count & 0x7f); repeat ++) {
|
||||||
|
putByte(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// non-zero bytes are raw bitmap data
|
||||||
|
putByte(byte)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cel.bitmap = bitmap
|
||||||
|
}
|
||||||
|
|
||||||
|
celDecoder.box = (data, cel) => {
|
||||||
|
const bitmap = emptyBitmap(cel.width, cel.height)
|
||||||
|
cel.borderLR = (data.getUint8(0) & 0x20) != 0
|
||||||
|
cel.borderTB = (data.getUint8(0) & 0x10) != 0
|
||||||
|
cel.pattern = data.getUint8(6)
|
||||||
|
for (let y = 0; y < cel.height; y ++) {
|
||||||
|
for (let x = 0; x < cel.width; x ++) {
|
||||||
|
if (cel.borderTB && (y == 0 || y == (cel.height - 1))) {
|
||||||
|
drawByte(bitmap, x * 4, y, 0xaa)
|
||||||
|
} else {
|
||||||
|
drawByte(bitmap, x * 4, y, cel.pattern)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cel.borderLR) {
|
||||||
|
const line = bitmap[y]
|
||||||
|
line[0] = 2
|
||||||
|
line[line.length - 1] = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cel.bitmap = bitmap
|
||||||
|
}
|
||||||
|
|
||||||
const decodeCel = (data, changesColorRam) => {
|
const decodeCel = (data, changesColorRam) => {
|
||||||
const cel = {
|
const cel = {
|
||||||
data: data,
|
data: data,
|
||||||
|
@ -158,7 +196,7 @@ const decodeCel = (data, changesColorRam) => {
|
||||||
celDecoder[cel.type](data, cel)
|
celDecoder[cel.type](data, cel)
|
||||||
}
|
}
|
||||||
if (cel.bitmap) {
|
if (cel.bitmap) {
|
||||||
cel.image = drawBitmap(cel.bitmap).toDataURL()
|
cel.canvas = canvasFromBitmap(cel.bitmap)
|
||||||
}
|
}
|
||||||
return cel
|
return cel
|
||||||
}
|
}
|
||||||
|
@ -232,12 +270,15 @@ const decodeProp = (data) => {
|
||||||
// We could also potentially assume that this structure always follows the header (or the
|
// We could also potentially assume that this structure always follows the header (or the
|
||||||
// "container" XY array, if one exists), as that seems to be consistently be the case with all
|
// "container" XY array, if one exists), as that seems to be consistently be the case with all
|
||||||
// the props in the Habitat source tree.
|
// the props in the Habitat source tree.
|
||||||
for (let frameOff = graphicStateOff; ; frameOff ++) {
|
// It's possible for there to be no frames, which is represented by an offset of 0 (no_animation)
|
||||||
const frame = decodeFrame(data.getUint8(frameOff), stateCount)
|
if (graphicStateOff != 0) {
|
||||||
if (!frame) {
|
for (let frameOff = graphicStateOff; ; frameOff ++) {
|
||||||
break
|
const frame = decodeFrame(data.getUint8(frameOff), stateCount)
|
||||||
|
if (!frame) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
prop.frames.push(frame)
|
||||||
}
|
}
|
||||||
prop.frames.push(frame)
|
|
||||||
}
|
}
|
||||||
for (let celOffsetOff = celOffsetsOff; allCelsMask != 0; celOffsetOff += 2) {
|
for (let celOffsetOff = celOffsetsOff; allCelsMask != 0; celOffsetOff += 2) {
|
||||||
const icel = prop.cels.length
|
const icel = prop.cels.length
|
||||||
|
@ -251,9 +292,9 @@ const decodeProp = (data) => {
|
||||||
const showCels = (prop) => {
|
const showCels = (prop) => {
|
||||||
const container = document.getElementById("cels")
|
const container = document.getElementById("cels")
|
||||||
for (const cel of prop.cels) {
|
for (const cel of prop.cels) {
|
||||||
if (cel.image) {
|
if (cel.canvas) {
|
||||||
const img = document.createElement("img")
|
const img = document.createElement("img")
|
||||||
img.src = cel.image
|
img.src = cel.canvas.toDataURL()
|
||||||
img.width = cel.width * 4 * 2 * 3
|
img.width = cel.width * 4 * 2 * 3
|
||||||
img.height = cel.height * 3
|
img.height = cel.height * 3
|
||||||
img.style.imageRendering = "pixelated"
|
img.style.imageRendering = "pixelated"
|
||||||
|
|
Loading…
Reference in a new issue