film grain effect to alleviate 3d scene with low-res texture look

This commit is contained in:
minjaesong
2025-10-08 02:07:29 +09:00
parent f918cd429c
commit d08511a39d
2 changed files with 72 additions and 9 deletions

View File

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

View File

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