From d4fae0071b6d8959713a08cebf8a0e54a7800931 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Thu, 2 Oct 2025 20:20:11 +0900 Subject: [PATCH] tav: ictcp decoding fix --- assets/disk0/tvdos/bin/playtav.js | 2 + assets/disk0/tvdos/bin/playtev.js | 36 ++++++++++------- assets/disk0/tvdos/include/playgui.mjs | 8 +++- .../torvald/tsvm/GraphicsJSR223Delegate.kt | 40 ++++++++++++------- 4 files changed, 55 insertions(+), 31 deletions(-) diff --git a/assets/disk0/tvdos/bin/playtav.js b/assets/disk0/tvdos/bin/playtav.js index 9e18219..5d6fcb1 100644 --- a/assets/disk0/tvdos/bin/playtav.js +++ b/assets/disk0/tvdos/bin/playtav.js @@ -657,6 +657,8 @@ try { akku: akku2, fileName: fullFilePathStr, fileOrd: currentFileIndex, + resolution: `${header.width}x${header.height}`, + colourSpace: header.version % 2 == 0 ? "ICtCp" : "YCoCg", currentStatus: 1 } gui.printBottomBar(guiStatus) diff --git a/assets/disk0/tvdos/bin/playtev.js b/assets/disk0/tvdos/bin/playtev.js index ce9dd17..8245d5a 100644 --- a/assets/disk0/tvdos/bin/playtev.js +++ b/assets/disk0/tvdos/bin/playtev.js @@ -74,18 +74,17 @@ let notifHideTimer = 0 const NOTIF_SHOWUPTIME = 3000000000 let [cy, cx] = con.getyx() -let seqreadserial = require("seqread") -let seqreadtape = require("seqreadtape") +let gui = require("playgui") let seqread = undefined let fullFilePathStr = fullFilePath.full // Select seqread driver to use if (fullFilePathStr.startsWith('$:/TAPE') || fullFilePathStr.startsWith('$:\\TAPE')) { - seqread = seqreadtape + seqread = require("seqreadtape") seqread.prepare(fullFilePathStr) seqread.seek(0) } else { - seqread = seqreadserial + seqread = require("seqread") seqread.prepare(fullFilePathStr) } @@ -746,20 +745,29 @@ try { if (interactive) { notifHideTimer += (t2 - t1) if (!notifHidden && notifHideTimer > (NOTIF_SHOWUPTIME + FRAME_TIME)) { - con.move(1, 1) - print(' '.repeat(79)) + // clearing function here notifHidden = true } - if (!hasSubtitle) { - con.move(31, 1) - con.color_pair(253, 0) - print(`Frame: ${frameCount}/${totalFrames} (${((frameCount / akku2 * 100)|0) / 100}f) `) - con.move(32, 1) - con.color_pair(253, 0) - print(`VRate: ${(getVideoRate() / 1024 * 8)|0} kbps `) - con.move(1, 1) + + con.color_pair(253, 0) + let guiStatus = { + fps: fps, + videoRate: getVideoRate(), + frameCount: frameCount, + totalFrames: totalFrames, + qY: qualityY, + qCo: qualityCo, + qCg: qualityCg, + akku: akku2, + fileName: fullFilePathStr, + fileOrd: 1, + resolution: `${width}x${height}${(isInterlaced) ? 'i' : ''}`, + colourSpace: colorSpace, + currentStatus: 1 } + gui.printBottomBar(guiStatus) + gui.printTopBar(guiStatus, 1) } t1 = t2 diff --git a/assets/disk0/tvdos/include/playgui.mjs b/assets/disk0/tvdos/include/playgui.mjs index ef26107..a67b39f 100644 --- a/assets/disk0/tvdos/include/playgui.mjs +++ b/assets/disk0/tvdos/include/playgui.mjs @@ -218,7 +218,9 @@ status = { akku: float, fileName: String, fileOrd: int, - currentStatus: int (0: stop/init, 1: play, 2: pause) + currentStatus: int (0: stop/init, 1: play, 2: pause), + resolution: string, + colourSpace: string } */ @@ -258,8 +260,10 @@ function printTopBar(status, moreInfo) { let sF = `F ${(''+status.frameCount).padStart((''+status.totalFrames).length, ' ')}/${status.totalFrames}` let sQ = `Q${(''+status.qY).padStart(4,' ')},${(''+status.qCo).padStart(2,' ')},${(''+status.qCg).padStart(2,' ')}` let sFPS = `${(status.frameCount / status.akku).toFixed(2)}f` + let sRes = `${status.resolution}` + let sCol = `${status.colourSpace}` - let sLeft = sF + BAR + sQ + BAR + sFPS + BAR + let sLeft = sF + BAR + sQ + BAR + sFPS + BAR + sRes + BAR + sCol + BAR let filenameSpace = 80 - sLeft.length if (filename.length > filenameSpace) { filename = filename.slice(0, filenameSpace - 1) + '~' diff --git a/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt b/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt index ed7c14f..522d1f6 100644 --- a/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt +++ b/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt @@ -4965,24 +4965,34 @@ class GraphicsJSR223Delegate(private val vm: VM) { for (x in 0 until width) { val idx = y * width + x - // ICtCp to RGB conversion (BT.2100 -> sRGB) - val I = iData[idx] - val Ct = ctData[idx] - val Cp = cpData[idx] + // ICtCp to sRGB conversion (adapted from encoder ICtCp functions) + val I = iData[idx].toDouble() / 255.0 + val Ct = (ctData[idx].toDouble() - 127.5) / 255.0 + val Cp = (cpData[idx].toDouble() - 127.5) / 255.0 - // ICtCp to LMS - val L = I + 0.00975f * Ct + 0.20524f * Cp - val M = I - 0.11387f * Ct + 0.13321f * Cp - val S = I + 0.03259f * Ct - 0.67851f * Cp + // ICtCp -> L'M'S' (inverse matrix) + val Lp = I + 0.015718580108730416 * Ct + 0.2095810681164055 * Cp + val Mp = I - 0.015718580108730416 * Ct - 0.20958106811640548 * Cp + val Sp = I + 1.0212710798422344 * Ct - 0.6052744909924316 * Cp - // LMS to RGB (simplified conversion) - val r = 3.2406f * L - 1.5372f * M - 0.4986f * S - val g = -0.9689f * L + 1.8758f * M + 0.0415f * S - val b = 0.0557f * L - 0.2040f * M + 1.0570f * S + // HLG decode: L'M'S' -> linear LMS + val L = HLG_EOTF(Lp) + val M = HLG_EOTF(Mp) + val S = HLG_EOTF(Sp) - rowRgbBuffer[bufferIdx++] = (r * 255f).toInt().coerceIn(0, 255).toByte() - rowRgbBuffer[bufferIdx++] = (g * 255f).toInt().coerceIn(0, 255).toByte() - rowRgbBuffer[bufferIdx++] = (b * 255f).toInt().coerceIn(0, 255).toByte() + // LMS -> linear sRGB (inverse matrix) + val rLin = 6.1723815689243215 * L -5.319534979827695 * M + 0.14699442094633924 * S + val gLin = -1.3243428148026244 * L + 2.560286104841917 * M -0.2359203727576164 * S + val bLin = -0.011819739235953752 * L -0.26473549971186555 * M + 1.2767952602537955 * S + + // Gamma encode to sRGB + val rSrgb = srgbUnlinearise(rLin) + val gSrgb = srgbUnlinearise(gLin) + val bSrgb = srgbUnlinearise(bLin) + + rowRgbBuffer[bufferIdx++] = (rSrgb * 255.0).toInt().coerceIn(0, 255).toByte() + rowRgbBuffer[bufferIdx++] = (gSrgb * 255.0).toInt().coerceIn(0, 255).toByte() + rowRgbBuffer[bufferIdx++] = (bSrgb * 255.0).toInt().coerceIn(0, 255).toByte() } // OPTIMISATION: Bulk copy entire row at once