pxt-ev3/sim/state/screen.ts

312 lines
8.8 KiB
TypeScript
Raw Normal View History

namespace pxsim {
function OFF(x: number, y: number) {
return x + y * visuals.SCREEN_WIDTH
}
export class EV3ScreenState {
changed: boolean = true;
points: Uint8Array;
constructor() {
this.points = new Uint8Array(visuals.SCREEN_WIDTH * visuals.SCREEN_HEIGHT)
}
applyMode(off: number, v: number) {
if (v & Draw.Clear)
this.points[off] = 0;
else if (v & Draw.Xor)
this.points[off] = this.points[off] ? 0 : 255;
else
this.points[off] = 255;
}
setPixel(x: number, y: number, v: number) {
this.applyMode(OFF(x, y), v)
this.changed = true;
}
clear() {
for (let i = 0; i < this.points.length; ++i)
this.points[i] = 0;
this.changed = true;
}
blitLineCore(x: number, y: number, w: number, buf: RefBuffer, mode: Draw, offset = 0) {
if (y < 0 || y >= visuals.SCREEN_HEIGHT)
return;
if (x + w <= 0)
return;
if (x >= visuals.SCREEN_WIDTH)
return;
let off = OFF(x, y);
const off0 = OFF(0, y);
const off1 = OFF(visuals.SCREEN_WIDTH - 1, y);
let mask = 0x01
let dp = offset
for (let i = 0; i < w; ++i) {
if ((buf.data[dp] & mask) && off0 <= off && off <= off1) {
this.applyMode(off, mode);
}
off++
mask <<= 1
if (mask & 0x100) {
mask = 0x01
dp++
}
}
this.changed = true;
}
clearLine(x: number, y: number, w: number) {
let off = OFF(x, y);
const off0 = OFF(0, y);
const off1 = OFF(visuals.SCREEN_WIDTH - 1, y);
for (let i = 0; i < w; ++i) {
if (off0 <= off && off <= off1) {
this.points[off] = 0
}
off++
}
}
didChange() {
const res = this.changed;
this.changed = false;
return res;
}
}
}
namespace pxsim.screen {
function XX(v: number) { return (v << 16) >> 16 }
function YY(v: number) { return v >> 16 }
export function _setPixel(x: number, y: number, mode: Draw) {
2017-12-18 22:04:17 +01:00
const screenState = ev3board().screenState;
screenState.setPixel(x, y, mode);
}
export function _blitLine(xw: number, y: number, buf: RefBuffer, mode: Draw) {
2017-12-18 22:04:17 +01:00
const screenState = ev3board().screenState;
screenState.blitLineCore(XX(xw), y, YY(xw), buf, mode)
}
2017-10-30 15:42:08 +01:00
export function isValidImage(buf: RefBuffer) {
return buf.data.length >= 3 && buf.data[0] == 0xf0;
}
export function PIX2BYTES(x: number) {
return ((x + 7) >> 3)
}
export function clear(): void {
2017-12-18 22:04:17 +01:00
const screenState = ev3board().screenState;
2017-10-30 15:42:08 +01:00
screenState.clear()
}
export function dump() {
// No need for this one.
}
export function imageOf(buf: RefBuffer) {
return incr(buf)
}
}
namespace pxsim.screen {
function DMESG(msg: string) {
control.dmesg(msg)
}
const NULL: RefBuffer = null;
function revbits(v: number) {
v = (v & 0xf0) >> 4 | (v & 0x0f) << 4;
v = (v & 0xcc) >> 2 | (v & 0x33) << 2;
v = (v & 0xaa) >> 1 | (v & 0x55) << 1;
return v;
}
export function unpackPNG(png: RefBuffer) {
function memcmp(off: number, mark: string) {
for (let i = 0; i < mark.length; ++i) {
if (mark.charCodeAt(i) != png.data[off + i])
return 1
}
return 0
}
function readInt(off: number) {
return ((png.data[off] << 24) | (png.data[off + 1] << 16) |
(png.data[off + 2] << 8) | (png.data[off + 3])) >>> 0
}
if (!png) {
DMESG("PNG: Missing image");
return NULL;
}
if (png.data.length < 45) {
DMESG("PNG: File too small");
return NULL;
}
if (memcmp(0, "\x89PNG\r\n\x1A\n") != 0) {
DMESG("PNG: Invalid header");
return NULL;
}
if (memcmp(12, "IHDR") != 0) {
DMESG("PNG: missing IHDR");
return NULL;
}
const lenIHDR = readInt(8);
const width = readInt(16);
const height = readInt(20);
const lenIDAT = readInt(33);
const sizeOfHD = 41;
if (lenIHDR != 13) {
DMESG("PNG: bad IHDR len");
return NULL;
}
if (memcmp(24, "\x01\x00\x00\x00\x00") != 0) {
DMESG("PNG: not 1-bit grayscale");
return NULL;
}
if (memcmp(37, "IDAT") != 0) {
DMESG("PNG: missing IDAT");
return NULL;
}
if (lenIDAT + sizeOfHD >= png.data.length) {
DMESG("PNG: buffer too short");
return NULL;
}
if (width > 300 || height > 300) {
DMESG("PNG: too big");
return NULL;
}
const byteW = (width + 7) >> 3;
const sz = (byteW + 1) * height;
const tmp = new Uint8Array(sz + 1);
// uncompress doesn't take the zlib header, hence + 2
const two = tinf.uncompress(png.data.slice(sizeOfHD + 2, sizeOfHD + lenIDAT), tmp);
if (two.length != sz) {
DMESG("PNG: invalid compressed size");
return NULL;
}
const res = output.createBuffer(2 + byteW * height);
res.data[0] = 0xf0;
res.data[1] = width;
let dst = 2
let src = 0
let lastMask = (1 << (width & 7)) - 1;
if (lastMask == 0)
lastMask = 0xff;
for (let i = 0; i < height; ++i) {
if (two[src++] != 0) {
DMESG("PNG: unsupported filter");
decr(res);
return NULL;
}
for (let j = 0; j < byteW; ++j) {
res.data[dst] = ~revbits(two[src++]);
if (j == byteW - 1) {
res.data[dst] &= lastMask;
}
dst++;
}
}
return res;
}
}
namespace pxsim.ImageMethods {
const bitdouble = [
0x00, 0x03, 0x0c, 0x0f, 0x30, 0x33, 0x3c, 0x3f, 0xc0, 0xc3, 0xcc, 0xcf, 0xf0, 0xf3, 0xfc, 0xff,
]
2017-10-30 15:42:08 +01:00
export function buffer(buf: RefBuffer) {
return incr(buf)
}
2017-10-30 15:42:08 +01:00
export function width(buf: RefBuffer) {
if (!screen.isValidImage(buf)) return 0
return buf.data[1]
}
export function height(buf: RefBuffer) {
if (!screen.isValidImage(buf)) return 0
const bw = screen.PIX2BYTES(buf.data[1]);
const h = ((buf.data.length - 2) / bw) | 0;
return h
}
2017-10-30 15:42:08 +01:00
export function draw(buf: RefBuffer, x: number, y: number, mode: Draw): void {
2017-12-18 22:04:17 +01:00
const screenState = ev3board().screenState;
2017-10-30 15:42:08 +01:00
if (!screen.isValidImage(buf))
return;
if (mode & (Draw.Double | Draw.Quad)) {
2017-10-30 15:42:08 +01:00
buf = doubled(buf);
if (mode & Draw.Quad) {
let pbuf = buf;
2017-10-30 15:42:08 +01:00
buf = doubled(buf);
decr(pbuf);
}
}
let pixwidth = buf.data[1];
let ptr = 2;
2017-10-30 15:42:08 +01:00
const bytewidth = screen.PIX2BYTES(pixwidth);
pixwidth = Math.min(pixwidth, visuals.SCREEN_WIDTH);
while (ptr + bytewidth <= buf.data.length) {
if (mode & (Draw.Clear | Draw.Xor | Draw.Transparent)) {
// no erase of background
} else {
screenState.clearLine(x, y, pixwidth)
}
screenState.blitLineCore(x, y, pixwidth, buf, mode, ptr);
y++;
ptr += bytewidth;
}
2017-10-30 15:42:08 +01:00
if (mode & (Draw.Double | Draw.Quad))
decr(buf);
}
2017-10-30 15:42:08 +01:00
export function doubled(buf: RefBuffer): RefBuffer {
if (!screen.isValidImage(buf))
return null;
const w = buf.data[1];
if (w > 126)
return null;
2017-10-30 15:42:08 +01:00
const bw = screen.PIX2BYTES(w);
const h = ((buf.data.length - 2) / bw) | 0;
2017-10-30 15:42:08 +01:00
const bw2 = screen.PIX2BYTES(w * 2);
const out = pins.createBuffer(2 + bw2 * h * 2)
out.data[0] = 0xf0;
out.data[1] = w * 2;
let src = 2
let dst = 2
for (let i = 0; i < h; ++i) {
for (let jj = 0; jj < 2; ++jj) {
let p = src;
for (let j = 0; j < bw; ++j) {
const v = buf.data[p++]
out.data[dst++] = bitdouble[v & 0xf];
out.data[dst++] = bitdouble[v >> 4];
}
}
src += bw;
}
return out;
}
2017-10-30 15:42:08 +01:00
}