iPF progressive mode decoder

This commit is contained in:
minjaesong
2025-12-19 21:41:51 +09:00
parent 1680137b7d
commit 96d697e158
4 changed files with 206 additions and 6 deletions

View File

@@ -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)

View File

@@ -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}"`),

View File

@@ -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

View File

@@ -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()