playmov: coloured ascii mode

This commit is contained in:
minjaesong
2026-06-07 22:38:41 +09:00
parent ce45929c4e
commit c8fc363445
7 changed files with 166 additions and 18 deletions

View File

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

View File

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

View File

@@ -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) },

View File

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

View File

@@ -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) },