mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-08 14:24:05 +09:00
playmov: coloured ascii mode
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
* .step() -> { type:'frame'|'idle'|'eof'|'newfile'|'error', frameCount }
|
||||
* .blit() present the current native frame to the screen
|
||||
* .sampleGray(dst,w,h) fill an ASCII brightness buffer from the framebuffer
|
||||
* .sampleColour(dst,w,h) fill a per-cell RGB buffer (w*h*3) from the framebuffer
|
||||
* .subtitle {visible,text,position,useUnicode,dirty} (resolved by the lib)
|
||||
* .pause(b)/.isPaused() .setVolume(v)/.getVolume()
|
||||
* .seekSeconds(n) .cue(d) .cues
|
||||
|
||||
@@ -360,6 +360,42 @@ function sampleGrayScreen(width, height, dst, dstW, dstH, mode) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── sampleColour source ──────────────────────────────────────────────────────
|
||||
// Companion to sampleGrayScreen: fill an RGB buffer (dst, length dstW·dstH·3,
|
||||
// laid out R,G,B per cell) by point-sampling the GPU framebuffer at the CENTRE
|
||||
// of each cell. Used by the player's colour-ASCII postprocessor — aa.mjs picks
|
||||
// each glyph from brightness, this supplies the per-cell ink colour. Same
|
||||
// backend-specific `mode` (4/5/8-bpp unpacking) and same cheap ~dstW·dstH peek
|
||||
// count as sampleGrayScreen.
|
||||
function sampleColourScreen(width, height, dst, dstW, dstH, mode) {
|
||||
for (let y = 0; y < dstH; y++) {
|
||||
let sy = ((y + 0.5) * height / dstH) | 0
|
||||
if (sy >= height) sy = height - 1
|
||||
let dstRow = y * dstW * 3
|
||||
for (let x = 0; x < dstW; x++) {
|
||||
let sx = ((x + 0.5) * width / dstW) | 0
|
||||
if (sx >= width) sx = width - 1
|
||||
let off = sy * 560 + sx
|
||||
let fb1 = sys.peek(DISP_RG - off) & 255
|
||||
let fb2 = sys.peek(DISP_BA - off) & 255
|
||||
let r, g, b
|
||||
if (mode == 5) {
|
||||
r = ((fb1 >>> 2) & 31) * 255 / 31
|
||||
g = (((fb1 & 3) << 3) | ((fb2 >>> 5) & 7)) * 255 / 31
|
||||
b = (fb2 & 31) * 255 / 31
|
||||
} else if (mode == 8) {
|
||||
r = fb1; g = fb2; b = sys.peek(DISP_PLANE3 - off) & 255
|
||||
} else { // mode 4
|
||||
r = (fb1 >>> 4) * 17
|
||||
g = (fb1 & 15) * 17
|
||||
b = (fb2 >>> 4) * 17
|
||||
}
|
||||
let di = dstRow + x * 3
|
||||
dst[di] = r | 0; dst[di + 1] = g | 0; dst[di + 2] = b | 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports = {
|
||||
MAGIC_MOV, MAGIC_TEV, MAGIC_TAV, MAGIC_TAP, MAGIC_UCF,
|
||||
MP2_FRAME_SIZE, QLUT,
|
||||
@@ -369,5 +405,5 @@ exports = {
|
||||
openSeqread, readMagic, detectFormat, magicEquals,
|
||||
luma8,
|
||||
makeAudioRouter, makeSubtitleEngine, makeBias,
|
||||
sampleGrayScreen
|
||||
sampleGrayScreen, sampleColourScreen
|
||||
}
|
||||
|
||||
@@ -150,6 +150,7 @@ function create(magic, sr, fileLength, opts, common) {
|
||||
|
||||
// Frame is already on the display planes, so the player can sample the screen.
|
||||
function sampleGray(dst, w, h) { common.sampleGrayScreen(width, height, dst, w, h, 4) }
|
||||
function sampleColour(dst, w, h) { common.sampleColourScreen(width, height, dst, w, h, 4) }
|
||||
|
||||
return {
|
||||
info: info,
|
||||
@@ -164,6 +165,7 @@ function create(magic, sr, fileLength, opts, common) {
|
||||
blit: blit,
|
||||
bias() { if (autoBg) applyBias() }, // skipped when an explicit bg packet set the colour
|
||||
sampleGray: sampleGray,
|
||||
sampleColour: sampleColour,
|
||||
pause(p) { paused = p; if (p) audioR.stop(); else { audioR.resume(); lastT = sys.nanoTime() } },
|
||||
isPaused() { return paused },
|
||||
setVolume(v) { audioR.setVolume(v) },
|
||||
|
||||
@@ -615,6 +615,7 @@ function create(magic, sr, fileLength, opts, common, isTap) {
|
||||
// Player calls blit() before sampleGray() in ASCII mode, so the framebuffer
|
||||
// already holds the current frame regardless of kind.
|
||||
function sampleGray(dst, w, h) { common.sampleGrayScreen(width, height, dst, w, h, gpuGraphicsMode) }
|
||||
function sampleColour(dst, w, h) { common.sampleColourScreen(width, height, dst, w, h, gpuGraphicsMode) }
|
||||
|
||||
// ── TAP still: decode the single image now ──────────────────────────────
|
||||
if (isTap) {
|
||||
@@ -658,6 +659,7 @@ function create(magic, sr, fileLength, opts, common, isTap) {
|
||||
blit: blit,
|
||||
bias() { applyBias() },
|
||||
sampleGray: sampleGray,
|
||||
sampleColour: sampleColour,
|
||||
|
||||
pause(p) {
|
||||
paused = p
|
||||
|
||||
@@ -189,6 +189,7 @@ function create(magic, sr, fileLength, opts, common) {
|
||||
// Player calls blit() (which uploads currentFrameSrc) before sampleGray in
|
||||
// ASCII mode, so we read the framebuffer the upload just produced.
|
||||
function sampleGray(dst, w, h) { common.sampleGrayScreen(width, height, dst, w, h, 4) }
|
||||
function sampleColour(dst, w, h) { common.sampleColourScreen(width, height, dst, w, h, 4) }
|
||||
|
||||
return {
|
||||
info: info,
|
||||
@@ -204,6 +205,7 @@ function create(magic, sr, fileLength, opts, common) {
|
||||
blit: blit,
|
||||
bias() { applyBias() },
|
||||
sampleGray: sampleGray,
|
||||
sampleColour: sampleColour,
|
||||
pause(p) { paused = p; if (p) audioR.stop(); else { audioR.resume(); lastT = sys.nanoTime() } },
|
||||
isPaused() { return paused },
|
||||
setVolume(v) { audioR.setVolume(v) },
|
||||
|
||||
Reference in New Issue
Block a user