mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-03-07 11:51:49 +09:00
iPF progressive mode decoder
This commit is contained in:
@@ -870,11 +870,21 @@ Object.freeze(_TVDOS.DRV.FS.DEVPT)
|
||||
_TVDOS.DRV.FS.DEVFBIPF = {}
|
||||
|
||||
_TVDOS.DRV.FS.DEVFBIPF.pwrite = (fd, infilePtr, count, _2) => {
|
||||
let decodefun = ([graphics.decodeIpf1, graphics.decodeIpf2])[sys.peek(infilePtr + 13)]
|
||||
let flags = sys.peek(infilePtr+12)
|
||||
let ipfType = sys.peek(infilePtr+13)
|
||||
let isProgressive = (flags & 0x80) != 0
|
||||
let hasAlpha = (flags & 0x01) != 0
|
||||
|
||||
// Select decode function based on type and progressive flag
|
||||
let decodefun
|
||||
if (isProgressive) {
|
||||
decodefun = ([graphics.decodeIpf1Progressive, graphics.decodeIpf2Progressive])[ipfType]
|
||||
} else {
|
||||
decodefun = ([graphics.decodeIpf1, graphics.decodeIpf2])[ipfType]
|
||||
}
|
||||
|
||||
let width = sys.peek(infilePtr+8) | (sys.peek(infilePtr+9) << 8)
|
||||
let height = sys.peek(infilePtr+10) | (sys.peek(infilePtr+11) << 8)
|
||||
let hasAlpha = (sys.peek(infilePtr+12) != 0)
|
||||
let ipfType = sys.peek(infilePtr+13)
|
||||
let imgLen = sys.peek(infilePtr+24) | (sys.peek(infilePtr+25) << 8) | (sys.peek(infilePtr+26) << 16) | (sys.peek(infilePtr+27) << 24)
|
||||
|
||||
let ipfbuf = sys.malloc(imgLen)
|
||||
|
||||
@@ -33,6 +33,7 @@ const COL_HL_EXT = {
|
||||
"mv2": 213,
|
||||
"mv3": 213,
|
||||
"tav": 213,
|
||||
"ipf": 190,
|
||||
"ipf1": 190,
|
||||
"ipf2": 190,
|
||||
"txt": 223,
|
||||
@@ -51,6 +52,7 @@ const EXEC_FUNS = {
|
||||
"tav": (f) => _G.shell.execute(`playtav "${f}" -i`),
|
||||
"tad": (f) => _G.shell.execute(`playtad "${f}" -i`),
|
||||
"pcm": (f) => _G.shell.execute(`playpcm "${f}" -i`),
|
||||
"ipf": (f) => _G.shell.execute(`decodeipf "${f}" -i`),
|
||||
"ipf1": (f) => _G.shell.execute(`decodeipf "${f}" -i`),
|
||||
"ipf2": (f) => _G.shell.execute(`decodeipf "${f}" -i`),
|
||||
"bas": (f) => _G.shell.execute(`basic "${f}"`),
|
||||
|
||||
@@ -562,7 +562,7 @@ NOTE FROM DEVELOPER
|
||||
|
||||
TSVM Interchangeable Picture Format (aka iPF Type 1/2)
|
||||
|
||||
Image is divided into 4x4 blocks and each block is serialised, then the entire iPF blocks are gzipped
|
||||
Image is divided into 4x4 blocks and each block is serialised, then the entire iPF blocks are Zstd-compressed
|
||||
|
||||
|
||||
# File Structure
|
||||
@@ -576,7 +576,7 @@ Image is divided into 4x4 blocks and each block is serialised, then the entire i
|
||||
uint8 Flags
|
||||
0b p00z 000a
|
||||
- a: has alpha
|
||||
- z: gzipped (p flag always sets this flag)
|
||||
- z: Zstd-compressed (p flag always sets this flag)
|
||||
- p: progressive ordering (Adam7)
|
||||
uint8 iPF Type/Colour Mode
|
||||
0: Type 1 (4:2:0 chroma subsampling; 2048 colours?)
|
||||
@@ -585,7 +585,7 @@ Image is divided into 4x4 blocks and each block is serialised, then the entire i
|
||||
uint32 UNCOMPRESSED SIZE (somewhat redundant but included for convenience)
|
||||
|
||||
- Chroma Subsampled Blocks
|
||||
Gzipped unless the z-flag is not set.
|
||||
Zstd-compressed unless the z-flag is not set.
|
||||
4x4 pixels are sampled, then divided into YCoCg planes.
|
||||
CoCg planes are "chroma subsampled" by 4:2:0, then quantised to 4 bits (8 bits for CoCg combined)
|
||||
Y plane is quantised to 4 bits
|
||||
|
||||
@@ -1339,6 +1339,194 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
||||
}
|
||||
|
||||
|
||||
// Adam7 interlace pattern - pass number (1-7) for each pixel in 8x8 block
|
||||
private val ADAM7_PASS = arrayOf(
|
||||
intArrayOf(1, 6, 4, 6, 2, 6, 4, 6),
|
||||
intArrayOf(7, 7, 7, 7, 7, 7, 7, 7),
|
||||
intArrayOf(5, 6, 5, 6, 5, 6, 5, 6),
|
||||
intArrayOf(7, 7, 7, 7, 7, 7, 7, 7),
|
||||
intArrayOf(3, 6, 4, 6, 3, 6, 4, 6),
|
||||
intArrayOf(7, 7, 7, 7, 7, 7, 7, 7),
|
||||
intArrayOf(5, 6, 5, 6, 5, 6, 5, 6),
|
||||
intArrayOf(7, 7, 7, 7, 7, 7, 7, 7)
|
||||
)
|
||||
|
||||
/**
|
||||
* Get Adam7 pass number for a block at (blockX, blockY).
|
||||
* Uses the block's top-left pixel position to determine pass.
|
||||
*/
|
||||
private fun getAdam7Pass(blockX: Int, blockY: Int): Int {
|
||||
val px = (blockX * 4) % 8
|
||||
val py = (blockY * 4) % 8
|
||||
return ADAM7_PASS[py][px]
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a mapping from progressive input order to (blockX, blockY) positions.
|
||||
* Returns a list of Pair(blockX, blockY) in the order they appear in progressive data.
|
||||
*/
|
||||
private fun buildAdam7BlockOrder(blocksX: Int, blocksY: Int): List<Pair<Int, Int>> {
|
||||
val result = mutableListOf<Pair<Int, Int>>()
|
||||
for (pass in 1..7) {
|
||||
for (by in 0 until blocksY) {
|
||||
for (bx in 0 until blocksX) {
|
||||
if (getAdam7Pass(bx, by) == pass) {
|
||||
result.add(Pair(bx, by))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode iPF1 with Adam7 progressive ordering.
|
||||
* Blocks are stored in pass order (1-7), not raster order.
|
||||
*/
|
||||
fun decodeIpf1Progressive(srcPtr: Int, destRG: Int, destBA: Int, width: Int, height: Int, hasAlpha: Boolean) {
|
||||
val sign = if (destRG >= 0) 1 else -1
|
||||
if (destRG * destBA < 0) throw IllegalArgumentException("Both destination memories must be on the same domain (both being Usermem or HWmem)")
|
||||
val sptr = srcPtr.toLong()
|
||||
val dptr1 = destRG.toLong()
|
||||
val dptr2 = destBA.toLong()
|
||||
var readCount = 0
|
||||
fun readShort() =
|
||||
vm.peek(sptr + readCount++)!!.toUint() or vm.peek(sptr + readCount++)!!.toUint().shl(8)
|
||||
|
||||
val blocksX = ceil(width / 4f).toInt()
|
||||
val blocksY = ceil(height / 4f).toInt()
|
||||
val blockOrder = buildAdam7BlockOrder(blocksX, blocksY)
|
||||
|
||||
for ((blockX, blockY) in blockOrder) {
|
||||
val rg = IntArray(16)
|
||||
val ba = IntArray(16)
|
||||
|
||||
val co = readShort()
|
||||
val cg = readShort()
|
||||
val y1 = readShort()
|
||||
val y2 = readShort()
|
||||
val y3 = readShort()
|
||||
val y4 = readShort()
|
||||
|
||||
var a1 = 65535; var a2 = 65535; var a3 = 65535; var a4 = 65535
|
||||
|
||||
if (hasAlpha) {
|
||||
a1 = readShort()
|
||||
a2 = readShort()
|
||||
a3 = readShort()
|
||||
a4 = readShort()
|
||||
}
|
||||
|
||||
var corner = ipf1YcocgToRGB(co and 15, cg and 15, y1, a1)
|
||||
rg[0] = corner[0];ba[0] = corner[1]
|
||||
rg[1] = corner[2];ba[1] = corner[3]
|
||||
rg[4] = corner[4];ba[4] = corner[5]
|
||||
rg[5] = corner[6];ba[5] = corner[7]
|
||||
|
||||
corner = ipf1YcocgToRGB((co shr 4) and 15, (cg shr 4) and 15, y2, a2)
|
||||
rg[2] = corner[0];ba[2] = corner[1]
|
||||
rg[3] = corner[2];ba[3] = corner[3]
|
||||
rg[6] = corner[4];ba[6] = corner[5]
|
||||
rg[7] = corner[6];ba[7] = corner[7]
|
||||
|
||||
corner = ipf1YcocgToRGB((co shr 8) and 15, (cg shr 8) and 15, y3, a3)
|
||||
rg[8] = corner[0];ba[8] = corner[1]
|
||||
rg[9] = corner[2];ba[9] = corner[3]
|
||||
rg[12] = corner[4];ba[12] = corner[5]
|
||||
rg[13] = corner[6];ba[13] = corner[7]
|
||||
|
||||
corner = ipf1YcocgToRGB((co shr 12) and 15, (cg shr 12) and 15, y4, a4)
|
||||
rg[10] = corner[0];ba[10] = corner[1]
|
||||
rg[11] = corner[2];ba[11] = corner[3]
|
||||
rg[14] = corner[4];ba[14] = corner[5]
|
||||
rg[15] = corner[6];ba[15] = corner[7]
|
||||
|
||||
// move decoded pixels into memory
|
||||
for (py in 0..3) { for (px in 0..3) {
|
||||
val ox = blockX * 4 + px
|
||||
val oy = blockY * 4 + py
|
||||
val offset = oy * 560 + ox
|
||||
vm.poke(dptr1 + offset*sign, rg[py * 4 + px].toByte())
|
||||
vm.poke(dptr2 + offset*sign, ba[py * 4 + px].toByte())
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode iPF2 with Adam7 progressive ordering.
|
||||
* Blocks are stored in pass order (1-7), not raster order.
|
||||
*/
|
||||
fun decodeIpf2Progressive(srcPtr: Int, destRG: Int, destBA: Int, width: Int, height: Int, hasAlpha: Boolean) {
|
||||
val sign = if (destRG >= 0) 1 else -1
|
||||
if (destRG * destBA < 0) throw IllegalArgumentException("Both destination memories must be on the same domain (both being Usermem or HWmem)")
|
||||
val sptr = srcPtr.toLong()
|
||||
val dptr1 = destRG.toLong()
|
||||
val dptr2 = destBA.toLong()
|
||||
var readCount = 0
|
||||
fun readShort() =
|
||||
vm.peek(sptr + readCount++)!!.toUint() or vm.peek(sptr + readCount++)!!.toUint().shl(8)
|
||||
fun readInt() =
|
||||
vm.peek(sptr + readCount++)!!.toUint() or vm.peek(sptr + readCount++)!!.toUint().shl(8) or vm.peek(sptr + readCount++)!!.toUint().shl(16) or vm.peek(sptr + readCount++)!!.toUint().shl(24)
|
||||
|
||||
val blocksX = ceil(width / 4f).toInt()
|
||||
val blocksY = ceil(height / 4f).toInt()
|
||||
val blockOrder = buildAdam7BlockOrder(blocksX, blocksY)
|
||||
|
||||
for ((blockX, blockY) in blockOrder) {
|
||||
val rg = IntArray(16)
|
||||
val ba = IntArray(16)
|
||||
|
||||
val co = readInt()
|
||||
val cg = readInt()
|
||||
val y1 = readShort()
|
||||
val y2 = readShort()
|
||||
val y3 = readShort()
|
||||
val y4 = readShort()
|
||||
|
||||
var a1 = 65535; var a2 = 65535; var a3 = 65535; var a4 = 65535
|
||||
|
||||
if (hasAlpha) {
|
||||
a1 = readShort()
|
||||
a2 = readShort()
|
||||
a3 = readShort()
|
||||
a4 = readShort()
|
||||
}
|
||||
|
||||
var corner = ipf2YcocgToRGB(co and 15, (co shr 8) and 15, cg and 15, (cg shr 8) and 15, y1, a1)
|
||||
rg[0] = corner[0];ba[0] = corner[1]
|
||||
rg[1] = corner[2];ba[1] = corner[3]
|
||||
rg[4] = corner[4];ba[4] = corner[5]
|
||||
rg[5] = corner[6];ba[5] = corner[7]
|
||||
|
||||
corner = ipf2YcocgToRGB((co shr 4) and 15, (co shr 12) and 15, (cg shr 4) and 15, (cg shr 12) and 15, y2, a2)
|
||||
rg[2] = corner[0];ba[2] = corner[1]
|
||||
rg[3] = corner[2];ba[3] = corner[3]
|
||||
rg[6] = corner[4];ba[6] = corner[5]
|
||||
rg[7] = corner[6];ba[7] = corner[7]
|
||||
|
||||
corner = ipf2YcocgToRGB((co shr 16) and 15, (co shr 24) and 15, (cg shr 16) and 15, (cg shr 24) and 15, y3, a3)
|
||||
rg[8] = corner[0];ba[8] = corner[1]
|
||||
rg[9] = corner[2];ba[9] = corner[3]
|
||||
rg[12] = corner[4];ba[12] = corner[5]
|
||||
rg[13] = corner[6];ba[13] = corner[7]
|
||||
|
||||
corner = ipf2YcocgToRGB((co shr 20) and 15, (co shr 28) and 15, (cg shr 20) and 15, (cg shr 28) and 15, y4, a4)
|
||||
rg[10] = corner[0];ba[10] = corner[1]
|
||||
rg[11] = corner[2];ba[11] = corner[3]
|
||||
rg[14] = corner[4];ba[14] = corner[5]
|
||||
rg[15] = corner[6];ba[15] = corner[7]
|
||||
|
||||
// move decoded pixels into memory
|
||||
for (py in 0..3) { for (px in 0..3) {
|
||||
val ox = blockX * 4 + px
|
||||
val oy = blockY * 4 + py
|
||||
val offset = oy * 560 + ox
|
||||
vm.poke(dptr1 + offset*sign, rg[py * 4 + px].toByte())
|
||||
vm.poke(dptr2 + offset*sign, ba[py * 4 + px].toByte())
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
private val SKIP = 0x00.toByte()
|
||||
private val PATCH = 0x01.toByte()
|
||||
private val REPEAT = 0x02.toByte()
|
||||
|
||||
Reference in New Issue
Block a user