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 = {}
|
||||||
|
|
||||||
_TVDOS.DRV.FS.DEVFBIPF.pwrite = (fd, infilePtr, count, _2) => {
|
_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 width = sys.peek(infilePtr+8) | (sys.peek(infilePtr+9) << 8)
|
||||||
let height = sys.peek(infilePtr+10) | (sys.peek(infilePtr+11) << 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 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)
|
let ipfbuf = sys.malloc(imgLen)
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ const COL_HL_EXT = {
|
|||||||
"mv2": 213,
|
"mv2": 213,
|
||||||
"mv3": 213,
|
"mv3": 213,
|
||||||
"tav": 213,
|
"tav": 213,
|
||||||
|
"ipf": 190,
|
||||||
"ipf1": 190,
|
"ipf1": 190,
|
||||||
"ipf2": 190,
|
"ipf2": 190,
|
||||||
"txt": 223,
|
"txt": 223,
|
||||||
@@ -51,6 +52,7 @@ const EXEC_FUNS = {
|
|||||||
"tav": (f) => _G.shell.execute(`playtav "${f}" -i`),
|
"tav": (f) => _G.shell.execute(`playtav "${f}" -i`),
|
||||||
"tad": (f) => _G.shell.execute(`playtad "${f}" -i`),
|
"tad": (f) => _G.shell.execute(`playtad "${f}" -i`),
|
||||||
"pcm": (f) => _G.shell.execute(`playpcm "${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`),
|
"ipf1": (f) => _G.shell.execute(`decodeipf "${f}" -i`),
|
||||||
"ipf2": (f) => _G.shell.execute(`decodeipf "${f}" -i`),
|
"ipf2": (f) => _G.shell.execute(`decodeipf "${f}" -i`),
|
||||||
"bas": (f) => _G.shell.execute(`basic "${f}"`),
|
"bas": (f) => _G.shell.execute(`basic "${f}"`),
|
||||||
|
|||||||
@@ -562,7 +562,7 @@ NOTE FROM DEVELOPER
|
|||||||
|
|
||||||
TSVM Interchangeable Picture Format (aka iPF Type 1/2)
|
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
|
# File Structure
|
||||||
@@ -576,7 +576,7 @@ Image is divided into 4x4 blocks and each block is serialised, then the entire i
|
|||||||
uint8 Flags
|
uint8 Flags
|
||||||
0b p00z 000a
|
0b p00z 000a
|
||||||
- a: has alpha
|
- a: has alpha
|
||||||
- z: gzipped (p flag always sets this flag)
|
- z: Zstd-compressed (p flag always sets this flag)
|
||||||
- p: progressive ordering (Adam7)
|
- p: progressive ordering (Adam7)
|
||||||
uint8 iPF Type/Colour Mode
|
uint8 iPF Type/Colour Mode
|
||||||
0: Type 1 (4:2:0 chroma subsampling; 2048 colours?)
|
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)
|
uint32 UNCOMPRESSED SIZE (somewhat redundant but included for convenience)
|
||||||
|
|
||||||
- Chroma Subsampled Blocks
|
- 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.
|
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)
|
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
|
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 SKIP = 0x00.toByte()
|
||||||
private val PATCH = 0x01.toByte()
|
private val PATCH = 0x01.toByte()
|
||||||
private val REPEAT = 0x02.toByte()
|
private val REPEAT = 0x02.toByte()
|
||||||
|
|||||||
Reference in New Issue
Block a user