|
|
|
@ -93,7 +93,6 @@ |
|
|
|
<div id="display-box"> |
|
|
|
<div id="display-box"> |
|
|
|
<!-- Internal res = displayWidth × displayHeight (160×80). CSS does the upscaling. --> |
|
|
|
<!-- Internal res = displayWidth × displayHeight (160×80). CSS does the upscaling. --> |
|
|
|
<canvas id="canvas_root" width="160" height="80"></canvas> |
|
|
|
<canvas id="canvas_root" width="160" height="80"></canvas> |
|
|
|
<div id="resolution">160 × 80 @ 25fps</div> |
|
|
|
|
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<div id="controls"></div> |
|
|
|
<div id="controls"></div> |
|
|
|
@ -107,81 +106,68 @@ |
|
|
|
<script src="./public/mono-display.js"></script> |
|
|
|
<script src="./public/mono-display.js"></script> |
|
|
|
|
|
|
|
|
|
|
|
<script> |
|
|
|
<script> |
|
|
|
// ------------------------------------------------------------------------- |
|
|
|
(function() { |
|
|
|
// Demo helpers — build synthetic .bin ArrayBuffers for all 4 content types |
|
|
|
const { MonoDisplayDriver, MonoDisplayFile, ElementType } = window.MonoDisplay; |
|
|
|
// ------------------------------------------------------------------------- |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function concat(...parts) { |
|
|
|
|
|
|
|
const arrays = parts.map(p => p instanceof ArrayBuffer ? new Uint8Array(p) : p); |
|
|
|
|
|
|
|
const total = arrays.reduce((s, a) => s + a.byteLength, 0); |
|
|
|
|
|
|
|
const out = new Uint8Array(total); |
|
|
|
|
|
|
|
let off = 0; |
|
|
|
|
|
|
|
for (const a of arrays) { out.set(a, off); off += a.byteLength; } |
|
|
|
|
|
|
|
return out.buffer; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function u8(n) { return new Uint8Array([n]); } |
|
|
|
|
|
|
|
function u16le(n) { const b = new Uint8Array(2); new DataView(b.buffer).setUint16(0, n, true); return b; } |
|
|
|
|
|
|
|
function u24le(n) { return new Uint8Array([n & 0xFF, (n >> 8) & 0xFF, (n >> 16) & 0xFF]); } |
|
|
|
|
|
|
|
function u32le(n) { const b = new Uint8Array(4); new DataView(b.buffer).setUint32(0, n, true); return b; } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Wrap section payload in a valid 1-section file envelope */ |
|
|
|
|
|
|
|
function makeFile(sectionType, sectionData) { |
|
|
|
|
|
|
|
const fileHdr = concat(MAGIC, u32le(1), u16le(1), u16le(0)); // magic+ver+numSec+reserved |
|
|
|
|
|
|
|
const secHdr = concat(u8(sectionType), u24le(sectionData.byteLength)); |
|
|
|
|
|
|
|
return concat(fileHdr, secHdr, sectionData); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 160×80 checkerboard — static section (type 0x01) |
|
|
|
|
|
|
|
function makeCheckerboard() { |
|
|
|
|
|
|
|
const W = 160, H = 80; |
|
|
|
|
|
|
|
const pixels = new Uint8Array(W * H); |
|
|
|
|
|
|
|
for (let y = 0; y < H; y++) |
|
|
|
|
|
|
|
for (let x = 0; x < W; x++) |
|
|
|
|
|
|
|
pixels[y * W + x] = (x + y) % 2; |
|
|
|
|
|
|
|
return makeFile(0x01, new Uint8Array(concat(u16le(W), u16le(H), pixels))); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 160×80 two-frame blink — animation section (type 0x02) |
|
|
|
|
|
|
|
function makeAnimation() { |
|
|
|
|
|
|
|
const W = 160, H = 80; |
|
|
|
|
|
|
|
const f = (fill, ms) => new Uint8Array(concat(u32le(ms), new Uint8Array(W * H).fill(fill))); |
|
|
|
|
|
|
|
return makeFile(0x02, new Uint8Array(concat(u16le(W), u16le(H), u16le(2), f(1, 200), f(0, 200)))); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Text section (type 0x03) |
|
|
|
|
|
|
|
function makeTextBin(text, align /* 0=left 1=center 2=right */) { |
|
|
|
|
|
|
|
const enc = new TextEncoder().encode(text); |
|
|
|
|
|
|
|
return makeFile(0x03, new Uint8Array(concat(u16le(enc.byteLength), enc, u8(0), u8(align ?? 1)))); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Scrolltext section (type 0x04) |
|
|
|
|
|
|
|
function makeScrollTextBin(text, speedPps, direction /* 0=left 1=right */) { |
|
|
|
|
|
|
|
const enc = new TextEncoder().encode(text); |
|
|
|
|
|
|
|
return makeFile(0x04, new Uint8Array(concat(u16le(enc.byteLength), enc, u16le(speedPps ?? 60), u8(direction ?? 0)))); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------------- |
|
|
|
|
|
|
|
// Driver — 160×80, 25fps, green-on-dark |
|
|
|
|
|
|
|
// ------------------------------------------------------------------------- |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const driver = new MonoDisplayDriver("canvas_root", { |
|
|
|
const driver = new MonoDisplayDriver("canvas_root", { |
|
|
|
onColor: "#33ff66", |
|
|
|
onColor: "#EC0", |
|
|
|
offColor: "#0a0a0a", |
|
|
|
offColor: "#000", |
|
|
|
fps: 25, |
|
|
|
fps: 25, |
|
|
|
// displayWidth/Height default to 160×80 |
|
|
|
|
|
|
|
// scale stays 1 — CSS handles visual upscaling |
|
|
|
|
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------------- |
|
|
|
const W = 160, H = 80; |
|
|
|
// Demo catalogue |
|
|
|
|
|
|
|
// ------------------------------------------------------------------------- |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const demos = [ |
|
|
|
const demos = [ |
|
|
|
{ label: "Checkerboard (static)", make: makeCheckerboard }, |
|
|
|
{ |
|
|
|
{ label: "Blink (animation)", make: makeAnimation }, |
|
|
|
label: "Checkerboard (static)", |
|
|
|
{ label: "Text (centered)", make: () => makeTextBin("Hello, World!", 1) }, |
|
|
|
make() { |
|
|
|
{ label: "Scrolltext (left)", make: () => makeScrollTextBin("MONO DISPLAY — scrolling ticker — 日本語テスト 🚀 ", 80, 0) }, |
|
|
|
const pixels = new Uint8Array(W * H); |
|
|
|
|
|
|
|
for (let y = 0; y < H; y++) |
|
|
|
|
|
|
|
for (let x = 0; x < W; x++) |
|
|
|
|
|
|
|
pixels[y * W + x] = (x + y) % 2; |
|
|
|
|
|
|
|
return new MonoDisplayFile({ |
|
|
|
|
|
|
|
width: W, height: H, |
|
|
|
|
|
|
|
elements_always: [{ type: ElementType.Image2D, pixels, width: W, height: H }], |
|
|
|
|
|
|
|
}).toBuffer(); |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
label: "Blink (animation)", |
|
|
|
|
|
|
|
make() { |
|
|
|
|
|
|
|
return new MonoDisplayFile({ |
|
|
|
|
|
|
|
width: W, height: H, |
|
|
|
|
|
|
|
elements_always: [{ |
|
|
|
|
|
|
|
type: ElementType.Animation, |
|
|
|
|
|
|
|
width: W, height: H, |
|
|
|
|
|
|
|
frames: [ |
|
|
|
|
|
|
|
{ pixels: new Uint8Array(W * H).fill(1), durationMs: 200 }, |
|
|
|
|
|
|
|
{ pixels: new Uint8Array(W * H).fill(0), durationMs: 200 }, |
|
|
|
|
|
|
|
], |
|
|
|
|
|
|
|
}], |
|
|
|
|
|
|
|
}).toBuffer(); |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
label: "Text (centered)", |
|
|
|
|
|
|
|
make() { |
|
|
|
|
|
|
|
return new MonoDisplayFile({ |
|
|
|
|
|
|
|
elements_always: [{ type: ElementType.Text, text: "Hello, World!", align: 1 }], |
|
|
|
|
|
|
|
}).toBuffer(); |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
label: "Scrolltext (left)", |
|
|
|
|
|
|
|
make() { |
|
|
|
|
|
|
|
return new MonoDisplayFile({ |
|
|
|
|
|
|
|
elements_always: [{ |
|
|
|
|
|
|
|
type: ElementType.ScrollText, |
|
|
|
|
|
|
|
text: "MONO DISPLAY — scrolling ticker — 🚀 ", |
|
|
|
|
|
|
|
speedPps: 80, |
|
|
|
|
|
|
|
direction: 0, |
|
|
|
|
|
|
|
}], |
|
|
|
|
|
|
|
}).toBuffer(); |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
}, |
|
|
|
]; |
|
|
|
]; |
|
|
|
|
|
|
|
|
|
|
|
const controls = document.getElementById("controls"); |
|
|
|
const controls = document.getElementById("controls"); |
|
|
|
@ -199,8 +185,8 @@ |
|
|
|
controls.appendChild(btn); |
|
|
|
controls.appendChild(btn); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Auto-load first demo |
|
|
|
|
|
|
|
controls.firstElementChild.click(); |
|
|
|
controls.firstElementChild.click(); |
|
|
|
|
|
|
|
})(); |
|
|
|
</script> |
|
|
|
</script> |
|
|
|
|
|
|
|
|
|
|
|
</body> |
|
|
|
</body> |
|
|
|
|