mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-09 22:54:03 +09:00
film grain effect to alleviate 3d scene with low-res texture look
This commit is contained in:
@@ -53,6 +53,8 @@ let subtitlePosition = 0 // 0=bottom center (default)
|
|||||||
|
|
||||||
// Parse command line options
|
// Parse command line options
|
||||||
let interactive = false
|
let interactive = false
|
||||||
|
let userDefinedFilmGrain = false
|
||||||
|
let filmGrainLevel = null
|
||||||
|
|
||||||
if (exec_args.length > 2) {
|
if (exec_args.length > 2) {
|
||||||
for (let i = 2; i < exec_args.length; i++) {
|
for (let i = 2; i < exec_args.length; i++) {
|
||||||
@@ -60,6 +62,26 @@ if (exec_args.length > 2) {
|
|||||||
if (arg === "-i") {
|
if (arg === "-i") {
|
||||||
interactive = true
|
interactive = true
|
||||||
}
|
}
|
||||||
|
else if (arg.startsWith("--filter-film-grain")) {
|
||||||
|
// Extract noise level from argument
|
||||||
|
const parts = arg.split(/[=\s]/)
|
||||||
|
if (parts.length > 1) {
|
||||||
|
const level = parseInt(parts[1])
|
||||||
|
if (!isNaN(level) && level >= 1 && level <= 32767) {
|
||||||
|
filmGrainLevel = level
|
||||||
|
userDefinedFilmGrain = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Try next argument if no '=' found
|
||||||
|
else if (i + 1 < exec_args.length) {
|
||||||
|
const level = parseInt(exec_args[i + 1])
|
||||||
|
if (!isNaN(level) && level >= 1 && level <= 32767) {
|
||||||
|
filmGrainLevel = level
|
||||||
|
userDefinedFilmGrain = true
|
||||||
|
i++ // Skip next arg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,6 +293,13 @@ if (header.version < 1 || header.version > 8) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setFilmGrainLevel(header) {
|
||||||
|
// decide film grain strength by quality level
|
||||||
|
filmGrainLevel = [9,6,-4,3,-2,-2,-2][header.qualityLevel - 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
setFilmGrainLevel(header)
|
||||||
|
|
||||||
// Helper function to decode channel layout name
|
// Helper function to decode channel layout name
|
||||||
function getChannelLayoutName(layout) {
|
function getChannelLayoutName(layout) {
|
||||||
switch (layout) {
|
switch (layout) {
|
||||||
@@ -670,6 +699,7 @@ try {
|
|||||||
currentCueIndex++
|
currentCueIndex++
|
||||||
}
|
}
|
||||||
totalFilesProcessed++
|
totalFilesProcessed++
|
||||||
|
setFilmGrainLevel(header)
|
||||||
|
|
||||||
console.log(`\nStarting file ${currentFileIndex}:`)
|
console.log(`\nStarting file ${currentFileIndex}:`)
|
||||||
console.log(`Resolution: ${header.width}x${header.height}`)
|
console.log(`Resolution: ${header.width}x${header.height}`)
|
||||||
@@ -726,6 +756,8 @@ try {
|
|||||||
serial.println(` FIELD_SIZE: ${FIELD_SIZE}`)
|
serial.println(` FIELD_SIZE: ${FIELD_SIZE}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let thisFrameNoiseLevel = (filmGrainLevel >= 0) ? filmGrainLevel : -(filmGrainLevel - (trueFrameCount % 2))
|
||||||
|
|
||||||
// Call new TAV hardware decoder that handles Zstd decompression internally
|
// Call new TAV hardware decoder that handles Zstd decompression internally
|
||||||
// Note: No longer using JS gzip.decompFromTo - Kotlin handles Zstd natively
|
// Note: No longer using JS gzip.decompFromTo - Kotlin handles Zstd natively
|
||||||
decoderDbgInfo = graphics.tavDecodeCompressed(
|
decoderDbgInfo = graphics.tavDecodeCompressed(
|
||||||
@@ -739,7 +771,8 @@ try {
|
|||||||
header.waveletFilter, // TAV-specific parameter
|
header.waveletFilter, // TAV-specific parameter
|
||||||
header.decompLevels, // TAV-specific parameter
|
header.decompLevels, // TAV-specific parameter
|
||||||
isLossless,
|
isLossless,
|
||||||
header.version // TAV version for colour space detection
|
header.version, // TAV version for colour space detection
|
||||||
|
thisFrameNoiseLevel // Undocumented spooky noise filter
|
||||||
)
|
)
|
||||||
|
|
||||||
decodeTime = (sys.nanoTime() - decodeStart) / 1000000.0
|
decodeTime = (sys.nanoTime() - decodeStart) / 1000000.0
|
||||||
|
|||||||
@@ -4450,8 +4450,8 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
|||||||
|
|
||||||
// New tavDecode function that accepts compressed data and decompresses internally
|
// New tavDecode function that accepts compressed data and decompresses internally
|
||||||
fun tavDecodeCompressed(compressedDataPtr: Long, compressedSize: Int, currentRGBAddr: Long, prevRGBAddr: Long,
|
fun tavDecodeCompressed(compressedDataPtr: Long, compressedSize: Int, currentRGBAddr: Long, prevRGBAddr: Long,
|
||||||
width: Int, height: Int, qIndex: Int, qYGlobal: Int, qCoGlobal: Int, qCgGlobal: Int, channelLayout: Int,
|
width: Int, height: Int, qIndex: Int, qYGlobal: Int, qCoGlobal: Int, qCgGlobal: Int, channelLayout: Int,
|
||||||
frameCount: Int, waveletFilter: Int = 1, decompLevels: Int = 6, isLossless: Boolean = false, tavVersion: Int = 1): HashMap<String, Any> {
|
frameCount: Int, waveletFilter: Int = 1, decompLevels: Int = 6, isLossless: Boolean = false, tavVersion: Int = 1, filmGrainLevel: Int = 0): HashMap<String, Any> {
|
||||||
|
|
||||||
// Read compressed data from VM memory into byte array
|
// Read compressed data from VM memory into byte array
|
||||||
val compressedData = ByteArray(compressedSize)
|
val compressedData = ByteArray(compressedSize)
|
||||||
@@ -4481,7 +4481,7 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
|||||||
// Call the existing tavDecode function with decompressed data
|
// Call the existing tavDecode function with decompressed data
|
||||||
tavDecode(decompressedBuffer.toLong(), currentRGBAddr, prevRGBAddr,
|
tavDecode(decompressedBuffer.toLong(), currentRGBAddr, prevRGBAddr,
|
||||||
width, height, qIndex, qYGlobal, qCoGlobal, qCgGlobal, channelLayout,
|
width, height, qIndex, qYGlobal, qCoGlobal, qCgGlobal, channelLayout,
|
||||||
frameCount, waveletFilter, decompLevels, isLossless, tavVersion)
|
frameCount, waveletFilter, decompLevels, isLossless, tavVersion, filmGrainLevel)
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
// Clean up allocated buffer
|
// Clean up allocated buffer
|
||||||
@@ -4497,7 +4497,7 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
|||||||
// Original tavDecode function for backward compatibility (now handles decompressed data)
|
// Original tavDecode function for backward compatibility (now handles decompressed data)
|
||||||
fun tavDecode(blockDataPtr: Long, currentRGBAddr: Long, prevRGBAddr: Long,
|
fun tavDecode(blockDataPtr: Long, currentRGBAddr: Long, prevRGBAddr: Long,
|
||||||
width: Int, height: Int, qIndex: Int, qYGlobal: Int, qCoGlobal: Int, qCgGlobal: Int, channelLayout: Int,
|
width: Int, height: Int, qIndex: Int, qYGlobal: Int, qCoGlobal: Int, qCgGlobal: Int, channelLayout: Int,
|
||||||
frameCount: Int, waveletFilter: Int = 1, decompLevels: Int = 6, isLossless: Boolean = false, tavVersion: Int = 1): HashMap<String, Any> {
|
frameCount: Int, waveletFilter: Int = 1, decompLevels: Int = 6, isLossless: Boolean = false, tavVersion: Int = 1, filmGrainLevel: Int = 0): HashMap<String, Any> {
|
||||||
|
|
||||||
val dbgOut = HashMap<String, Any>()
|
val dbgOut = HashMap<String, Any>()
|
||||||
|
|
||||||
@@ -4552,13 +4552,13 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
|||||||
// Decode DWT coefficients directly to RGB buffer
|
// Decode DWT coefficients directly to RGB buffer
|
||||||
readPtr = tavDecodeDWTIntraTileRGB(qIndex, qYGlobal, channelLayout, readPtr, tileX, tileY, currentRGBAddr,
|
readPtr = tavDecodeDWTIntraTileRGB(qIndex, qYGlobal, channelLayout, readPtr, tileX, tileY, currentRGBAddr,
|
||||||
width, height, qY, qCo, qCg,
|
width, height, qY, qCo, qCg,
|
||||||
waveletFilter, decompLevels, isLossless, tavVersion, isMonoblock)
|
waveletFilter, decompLevels, isLossless, tavVersion, isMonoblock, filmGrainLevel)
|
||||||
}
|
}
|
||||||
0x02 -> { // TAV_MODE_DELTA
|
0x02 -> { // TAV_MODE_DELTA
|
||||||
// Coefficient delta encoding for efficient P-frames
|
// Coefficient delta encoding for efficient P-frames
|
||||||
readPtr = tavDecodeDeltaTileRGB(readPtr, channelLayout, tileX, tileY, currentRGBAddr,
|
readPtr = tavDecodeDeltaTileRGB(readPtr, channelLayout, tileX, tileY, currentRGBAddr,
|
||||||
width, height, qY, qCo, qCg,
|
width, height, qY, qCo, qCg,
|
||||||
waveletFilter, decompLevels, isLossless, tavVersion, isMonoblock)
|
waveletFilter, decompLevels, isLossless, tavVersion, isMonoblock, filmGrainLevel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4573,7 +4573,7 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
|||||||
|
|
||||||
private fun tavDecodeDWTIntraTileRGB(qIndex: Int, qYGlobal: Int, channelLayout: Int, readPtr: Long, tileX: Int, tileY: Int, currentRGBAddr: Long,
|
private fun tavDecodeDWTIntraTileRGB(qIndex: Int, qYGlobal: Int, channelLayout: Int, readPtr: Long, tileX: Int, tileY: Int, currentRGBAddr: Long,
|
||||||
width: Int, height: Int, qY: Int, qCo: Int, qCg: Int,
|
width: Int, height: Int, qY: Int, qCo: Int, qCg: Int,
|
||||||
waveletFilter: Int, decompLevels: Int, isLossless: Boolean, tavVersion: Int, isMonoblock: Boolean = false): Long {
|
waveletFilter: Int, decompLevels: Int, isLossless: Boolean, tavVersion: Int, isMonoblock: Boolean = false, filmGrainLevel: Int = 0): Long {
|
||||||
// Determine coefficient count based on mode
|
// Determine coefficient count based on mode
|
||||||
val coeffCount = if (isMonoblock) {
|
val coeffCount = if (isMonoblock) {
|
||||||
// Monoblock mode: entire frame
|
// Monoblock mode: entire frame
|
||||||
@@ -4674,6 +4674,16 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
|||||||
dequantiseDWTSubbandsPerceptual(qIndex, qYGlobal, quantisedCo, coTile, subbands, qCo.toFloat(), true, decompLevels)
|
dequantiseDWTSubbandsPerceptual(qIndex, qYGlobal, quantisedCo, coTile, subbands, qCo.toFloat(), true, decompLevels)
|
||||||
dequantiseDWTSubbandsPerceptual(qIndex, qYGlobal, quantisedCg, cgTile, subbands, qCg.toFloat(), true, decompLevels)
|
dequantiseDWTSubbandsPerceptual(qIndex, qYGlobal, quantisedCg, cgTile, subbands, qCg.toFloat(), true, decompLevels)
|
||||||
|
|
||||||
|
// Apply spooky noise filter if enabled
|
||||||
|
if (filmGrainLevel > 0) {
|
||||||
|
val random = java.util.Random()
|
||||||
|
for (i in 0 until coeffCount) {
|
||||||
|
yTile[i] += (random.nextInt(filmGrainLevel * 2 + 1) - filmGrainLevel).toFloat()
|
||||||
|
coTile[i] += (random.nextInt(filmGrainLevel * 2 + 1) - filmGrainLevel).toFloat()
|
||||||
|
cgTile[i] += (random.nextInt(filmGrainLevel * 2 + 1) - filmGrainLevel).toFloat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Debug: Check coefficient values before inverse DWT
|
// Debug: Check coefficient values before inverse DWT
|
||||||
if (tavDebugCurrentFrameNumber == tavDebugFrameTarget) {
|
if (tavDebugCurrentFrameNumber == tavDebugFrameTarget) {
|
||||||
var maxYDequant = 0.0f
|
var maxYDequant = 0.0f
|
||||||
@@ -4730,6 +4740,16 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
|||||||
cgTile[i] = quantisedCg[i] * qCg.toFloat()
|
cgTile[i] = quantisedCg[i] * qCg.toFloat()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply spooky noise filter if enabled
|
||||||
|
if (filmGrainLevel > 0) {
|
||||||
|
val random = java.util.Random()
|
||||||
|
for (i in 0 until coeffCount) {
|
||||||
|
yTile[i] += (random.nextInt(filmGrainLevel * 2 + 1) - filmGrainLevel).toFloat()
|
||||||
|
coTile[i] += (random.nextInt(filmGrainLevel * 2 + 1) - filmGrainLevel).toFloat()
|
||||||
|
cgTile[i] += (random.nextInt(filmGrainLevel * 2 + 1) - filmGrainLevel).toFloat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Debug: Uniform quantisation subband analysis for comparison
|
// Debug: Uniform quantisation subband analysis for comparison
|
||||||
if (tavDebugCurrentFrameNumber == tavDebugFrameTarget) {
|
if (tavDebugCurrentFrameNumber == tavDebugFrameTarget) {
|
||||||
val tileWidth = if (isMonoblock) width else TAV_PADDED_TILE_SIZE_X
|
val tileWidth = if (isMonoblock) width else TAV_PADDED_TILE_SIZE_X
|
||||||
@@ -5185,7 +5205,7 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
|||||||
|
|
||||||
private fun tavDecodeDeltaTileRGB(readPtr: Long, channelLayout: Int, tileX: Int, tileY: Int, currentRGBAddr: Long,
|
private fun tavDecodeDeltaTileRGB(readPtr: Long, channelLayout: Int, tileX: Int, tileY: Int, currentRGBAddr: Long,
|
||||||
width: Int, height: Int, qY: Int, qCo: Int, qCg: Int,
|
width: Int, height: Int, qY: Int, qCo: Int, qCg: Int,
|
||||||
waveletFilter: Int, decompLevels: Int, isLossless: Boolean, tavVersion: Int, isMonoblock: Boolean = false): Long {
|
waveletFilter: Int, decompLevels: Int, isLossless: Boolean, tavVersion: Int, isMonoblock: Boolean = false, filmGrainLevel: Int = 0): Long {
|
||||||
|
|
||||||
val tileIdx = if (isMonoblock) {
|
val tileIdx = if (isMonoblock) {
|
||||||
0 // Single tile index for monoblock
|
0 // Single tile index for monoblock
|
||||||
@@ -5302,6 +5322,16 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
|||||||
currentCg[i] = prevCg[i] + (deltaCg[i].toFloat() * qCg)
|
currentCg[i] = prevCg[i] + (deltaCg[i].toFloat() * qCg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply spooky noise filter if enabled
|
||||||
|
if (filmGrainLevel > 0) {
|
||||||
|
val random = java.util.Random()
|
||||||
|
for (i in 0 until coeffCount) {
|
||||||
|
currentY[i] += (random.nextInt(filmGrainLevel * 2 + 1) - filmGrainLevel).toFloat()
|
||||||
|
currentCo[i] += (random.nextInt(filmGrainLevel * 2 + 1) - filmGrainLevel).toFloat()
|
||||||
|
currentCg[i] += (random.nextInt(filmGrainLevel * 2 + 1) - filmGrainLevel).toFloat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Store current coefficients as previous for next frame
|
// Store current coefficients as previous for next frame
|
||||||
tavPreviousCoeffsY!![tileIdx] = currentY.clone()
|
tavPreviousCoeffsY!![tileIdx] = currentY.clone()
|
||||||
tavPreviousCoeffsCo!![tileIdx] = currentCo.clone()
|
tavPreviousCoeffsCo!![tileIdx] = currentCo.clone()
|
||||||
|
|||||||
Reference in New Issue
Block a user