pxt-calliope/sim/state/ledmatrix.ts

397 lines
15 KiB
TypeScript
Raw Normal View History

2016-08-30 20:51:32 +02:00
namespace pxsim {
export enum DisplayMode {
bw,
greyscale
}
export class LedMatrixState {
image = createInternalImage(5);
brigthness = 255;
displayMode = DisplayMode.bw;
font: Image = createFont();
disabled: boolean;
2016-08-30 20:51:32 +02:00
animationQ: AnimationQueue;
constructor(runtime: Runtime) {
this.animationQ = new AnimationQueue(runtime);
}
}
export class Image extends RefObject {
public height: number;
2016-08-30 20:51:32 +02:00
public width: number;
public data: number[];
constructor(width: number, data: number[]) {
super();
this.width = width;
this.data = data;
this.height = (this.data.length / this.width) | 0;
2016-08-30 20:51:32 +02:00
}
public print() {
console.debug(`Image id:${this.id} size:${this.width}x${this.height}`)
2016-08-30 20:51:32 +02:00
}
public get(x: number, y: number): number {
x = x | 0;
y = y | 0;
if (x < 0 || x >= this.width || y < 0 || y >= this.height) return 0;
2016-08-30 20:51:32 +02:00
return this.data[y * this.width + x];
}
public set(x: number, y: number, v: number) {
x = x | 0;
y = y | 0;
if (x < 0 || x >= this.width || y < 0 || y >= this.height) return;
2016-08-30 20:51:32 +02:00
this.data[y * this.width + x] = Math.max(0, Math.min(255, v));
}
public copyTo(xSrcIndex: number, length: number, target: Image, xTargetIndex: number): void {
xSrcIndex = xSrcIndex | 0;
length = length | 0;
xTargetIndex = xTargetIndex | 0;
2016-08-30 20:51:32 +02:00
for (let x = 0; x < length; x++) {
for (let y = 0; y < this.height; y++) {
2016-08-30 20:51:32 +02:00
let value = this.get(xSrcIndex + x, y);
target.set(xTargetIndex + x, y, value);
}
}
}
public shiftLeft(cols: number) {
cols = cols | 0;
2016-08-30 20:51:32 +02:00
for (let x = 0; x < this.width; ++x)
for (let y = 0; y < this.height; ++y)
2016-08-30 20:51:32 +02:00
this.set(x, y, x < this.width - cols ? this.get(x + cols, y) : 0);
}
public shiftRight(cols: number) {
cols = cols >> 0;
for (let x = this.width - 1; x >= 0; --x)
for (let y = 0; y < this.height; ++y)
this.set(x, y, x >= cols ? this.get(x - cols, y) : 0);
2016-08-30 20:51:32 +02:00
}
public clear(): void {
for (let i = 0; i < this.data.length; ++i)
this.data[i] = 0;
}
}
export function createInternalImage(width: number): Image {
width = width >> 0;
2016-08-30 20:51:32 +02:00
let img = createImage(width)
return img
}
export function createImage(width: number): Image {
width = width >> 0;
2016-08-30 20:51:32 +02:00
return new Image(width, new Array(width * 5));
}
export function createImageFromBuffer(data: number[]): Image {
return new Image((data.length / 5) | 0, data);
2016-08-30 20:51:32 +02:00
}
export function createImageFromString(text: string): Image {
let font = board().ledMatrixState.font;
let w = font.width;
let sprite = createInternalImage(6 * text.length - 1);
let k = 0;
for (let i = 0; i < text.length; i++) {
let charCode = text.charCodeAt(i);
let charStart = (charCode - 32) * 5;
if (charStart < 0 || charStart + 5 > w) {
charCode = " ".charCodeAt(0);
charStart = (charCode - 32) * 5;
}
font.copyTo(charStart, 5, sprite, k);
k = k + 5;
if (i < text.length - 1) {
k = k + 1;
}
}
return sprite;
}
export const FONT_DATA = [0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x8, 0x8, 0x0, 0x8, 0xa, 0x4a, 0x40, 0x0, 0x0, 0xa, 0x5f, 0xea, 0x5f, 0xea, 0xe, 0xd9, 0x2e, 0xd3, 0x6e, 0x19, 0x32, 0x44, 0x89, 0x33, 0xc, 0x92, 0x4c, 0x92, 0x4d, 0x8, 0x8, 0x0, 0x0, 0x0, 0x4, 0x88, 0x8, 0x8, 0x4, 0x8, 0x4, 0x84, 0x84, 0x88, 0x0, 0xa, 0x44, 0x8a, 0x40, 0x0, 0x4, 0x8e, 0xc4, 0x80, 0x0, 0x0, 0x0, 0x4, 0x88, 0x0, 0x0, 0xe, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x1, 0x22, 0x44, 0x88, 0x10, 0xc, 0x92, 0x52, 0x52, 0x4c, 0x4, 0x8c, 0x84, 0x84, 0x8e, 0x1c, 0x82, 0x4c, 0x90, 0x1e, 0x1e, 0xc2, 0x44, 0x92, 0x4c, 0x6, 0xca, 0x52, 0x5f, 0xe2, 0x1f, 0xf0, 0x1e, 0xc1, 0x3e, 0x2, 0x44, 0x8e, 0xd1, 0x2e, 0x1f, 0xe2, 0x44, 0x88, 0x10, 0xe, 0xd1, 0x2e, 0xd1, 0x2e, 0xe, 0xd1, 0x2e, 0xc4, 0x88, 0x0, 0x8, 0x0, 0x8, 0x0, 0x0, 0x4, 0x80, 0x4, 0x88, 0x2, 0x44, 0x88, 0x4, 0x82, 0x0, 0xe, 0xc0, 0xe, 0xc0, 0x8, 0x4, 0x82, 0x44, 0x88, 0xe, 0xd1, 0x26, 0xc0, 0x4, 0xe, 0xd1, 0x35, 0xb3, 0x6c, 0xc, 0x92, 0x5e, 0xd2, 0x52, 0x1c, 0x92, 0x5c, 0x92, 0x5c, 0xe, 0xd0, 0x10, 0x10, 0xe, 0x1c, 0x92, 0x52, 0x52, 0x5c, 0x1e, 0xd0, 0x1c, 0x90, 0x1e, 0x1e, 0xd0, 0x1c, 0x90, 0x10, 0xe, 0xd0, 0x13, 0x71, 0x2e, 0x12, 0x52, 0x5e, 0xd2, 0x52, 0x1c, 0x88, 0x8, 0x8, 0x1c, 0x1f, 0xe2, 0x42, 0x52, 0x4c, 0x12, 0x54, 0x98, 0x14, 0x92, 0x10, 0x10, 0x10, 0x10, 0x1e, 0x11, 0x3b, 0x75, 0xb1, 0x31, 0x11, 0x39, 0x35, 0xb3, 0x71, 0xc, 0x92, 0x52, 0x52, 0x4c, 0x1c, 0x92, 0x5c, 0x90, 0x10, 0xc, 0x92, 0x52, 0x4c, 0x86, 0x1c, 0x92, 0x5c, 0x92, 0x51, 0xe, 0xd0, 0xc, 0x82, 0x5c, 0x1f, 0xe4, 0x84, 0x84, 0x84, 0x12, 0x52, 0x52, 0x52, 0x4c, 0x11, 0x31, 0x31, 0x2a, 0x44, 0x11, 0x31, 0x35, 0xbb, 0x71, 0x12, 0x52, 0x4c, 0x92, 0x52, 0x11, 0x2a, 0x44, 0x84, 0x84, 0x1e, 0xc4, 0x88, 0x10, 0x1e, 0xe, 0xc8, 0x8, 0x8, 0xe, 0x10, 0x8, 0x4, 0x82, 0x41, 0xe, 0xc2, 0x42, 0x42, 0x4e, 0x4, 0x8a, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0x8, 0x4, 0x80, 0x0, 0x0, 0x0, 0xe, 0xd2, 0x52, 0x4f, 0x10, 0x10, 0x1c, 0x92, 0x5c, 0x0, 0xe, 0xd0, 0x10, 0xe, 0x2, 0x42, 0x4e, 0xd2, 0x4e, 0xc, 0x92, 0x5c, 0x90, 0xe, 0x6, 0xc8, 0x1c, 0x88, 0x8, 0xe, 0xd2, 0x4e, 0xc2, 0x4c, 0x10, 0x10, 0x1c, 0x92, 0x52, 0x8, 0x0, 0x8, 0x8, 0x8, 0x2, 0x40, 0x2, 0x42, 0x4c, 0x10, 0x14, 0x98, 0x14, 0x92, 0x8, 0x8, 0x8, 0x8, 0x6, 0x0, 0x1b, 0x75, 0xb1, 0x31, 0x0, 0x1c, 0x92, 0x52, 0x52, 0x0, 0xc, 0x92, 0x52, 0x4c, 0x0, 0x1c, 0x92, 0x5c, 0x90, 0x0, 0xe, 0xd2, 0x4e, 0xc2, 0x0, 0xe, 0xd0, 0x10, 0x10, 0x0, 0x6, 0xc8, 0x4, 0x98, 0x8, 0x8, 0xe, 0xc8, 0x7, 0x0, 0x12, 0x52, 0x52, 0x4f, 0x0, 0x11, 0x31, 0x2a, 0x44, 0x0, 0x11, 0x31, 0x35, 0xbb, 0x0, 0x12, 0x4c, 0x8c, 0x92, 0x0, 0x11, 0x2a, 0x44, 0x98, 0x0, 0x1e, 0xc4, 0x88, 0x1e, 0x6, 0xc4, 0x8c, 0x84, 0x86, 0x8, 0x8, 0x8, 0x8, 0x8, 0x18, 0x8, 0xc, 0x88, 0x18, 0x0, 0x0, 0xc, 0x83, 0x60];
2016-08-30 20:51:32 +02:00
export function createFont(): Image {
let nb = FONT_DATA.length;
2016-08-30 20:51:32 +02:00
let n = nb / 5;
let font = createInternalImage(nb);
for (let c = 0; c < n; c++) {
for (let row = 0; row < 5; row++) {
let char = FONT_DATA[c * 5 + row];
2016-08-30 20:51:32 +02:00
for (let col = 0; col < 5; col++) {
if ((char & (1 << col)) != 0)
font.set((c * 5 + 4) - col, row, 255);
}
}
}
return font;
}
}
namespace pxsim.fonts {
export function charCodeBuffer(charCode: number): RefBuffer {
if (charCode < DAL.MICROBIT_FONT_ASCII_START || charCode > DAL.MICROBIT_FONT_ASCII_END)
return undefined;
const b = board();
const led = b.ledMatrixState;
const font = led.font;
const h = font.height;
const w = font.width;
const buf = control.createBuffer(h);
const offset = (charCode - DAL.MICROBIT_FONT_ASCII_START) * h;
for (let row = 0; row < h; ++row)
buf.data[row] = FONT_DATA[offset + row];
return buf;
}
}
2016-08-30 20:51:32 +02:00
namespace pxsim.images {
export function createImage(img: Image) {
return img
}
export function createBigImage(img: Image) {
return img
}
}
namespace pxsim.ImageMethods {
2017-02-04 05:39:18 +01:00
export function showImage(leds: Image, offset: number, interval: number) {
pxtrt.nullCheck(leds)
offset = offset >> 0;
interval = interval >> 0;
2017-12-15 23:45:38 +01:00
let cb = getResume();
let first = true;
leds = clampPixelBrightness(leds);
2017-12-15 23:45:38 +01:00
board().ledMatrixState.animationQ.enqueue({
interval,
frame: () => {
if (first) {
leds.copyTo(offset, 5, board().ledMatrixState.image, 0)
first = false;
return true;
}
return false;
},
whenDone: cb
})
2016-08-30 20:51:32 +02:00
}
export function plotImage(leds: Image, offset: number): void {
pxtrt.nullCheck(leds)
offset = offset >> 0;
2017-12-15 23:45:38 +01:00
leds = clampPixelBrightness(leds);
2017-12-15 23:45:38 +01:00
board().ledMatrixState.animationQ.enqueue({
interval: 0,
frame: () => {
leds.copyTo(offset, 5, board().ledMatrixState.image, 0)
return false;
}
})
2016-08-30 20:51:32 +02:00
}
export function height(leds: Image): number {
pxtrt.nullCheck(leds)
return leds.height;
2016-08-30 20:51:32 +02:00
}
export function width(leds: Image): number {
pxtrt.nullCheck(leds)
2016-08-30 20:51:32 +02:00
return leds.width;
}
export function plotFrame(leds: Image, frame: number) {
ImageMethods.plotImage(leds, frame * leds.height);
2016-08-30 20:51:32 +02:00
}
2017-02-04 05:39:18 +01:00
export function showFrame(leds: Image, frame: number, interval: number) {
ImageMethods.showImage(leds, frame * leds.height, interval);
2016-08-30 20:51:32 +02:00
}
export function pixel(leds: Image, x: number, y: number): number {
pxtrt.nullCheck(leds)
2016-08-30 20:51:32 +02:00
return leds.get(x, y);
}
export function setPixel(leds: Image, x: number, y: number, v: number) {
pxtrt.nullCheck(leds)
2016-08-30 20:51:32 +02:00
leds.set(x, y, v);
}
export function clear(leds: Image) {
pxtrt.nullCheck(leds)
2016-08-30 20:51:32 +02:00
leds.clear();
}
export function setPixelBrightness(i: Image, x: number, y: number, b: number) {
pxtrt.nullCheck(i)
2016-08-30 20:51:32 +02:00
i.set(x, y, b);
}
export function pixelBrightness(i: Image, x: number, y: number): number {
pxtrt.nullCheck(i)
2016-08-30 20:51:32 +02:00
return i.get(x, y);
}
export function scrollImage(leds: Image, stride: number, interval: number): void {
pxtrt.nullCheck(leds)
stride = stride >> 0;
interval = interval >> 0;
2016-08-30 20:51:32 +02:00
if (stride == 0) stride = 1;
let cb = getResume();
let off = stride > 0 ? 0 : leds.width - 1;
let display = board().ledMatrixState.image;
leds = clampPixelBrightness(leds);
2016-08-30 20:51:32 +02:00
board().ledMatrixState.animationQ.enqueue({
interval: interval,
frame: () => {
if (off >= leds.width || off < 0) return false;
if (stride > 0) {
display.shiftLeft(stride);
const c = Math.min(stride, leds.width - off);
leds.copyTo(off, c, display, 5 - stride)
} else {
display.shiftRight(-stride);
const c = Math.min(-stride, leds.width - off);
leds.copyTo(off, c, display, 0)
}
2016-08-30 20:51:32 +02:00
off += stride;
return true;
},
whenDone: cb
})
}
function clampPixelBrightness(img: Image): Image {
let res = img;
if (led.displayMode() === DisplayMode.greyscale && led.brightness() < 0xff) {
res = new Image(img.width, img.data);
const b = led.brightness();
for (let x = 0; x < res.width; ++x) {
for (let y = 0; y < 5; ++y) {
if (pixelBrightness(res, x, y) > b) {
setPixelBrightness(res, x, y, b);
}
}
}
}
return res;
}
2016-08-30 20:51:32 +02:00
}
namespace pxsim.basic {
export function showNumber(x: number, interval: number) {
interval |= 0;
2017-12-15 23:45:38 +01:00
if (interval <= 0)
interval = 1;
2016-08-30 20:51:32 +02:00
let leds = createImageFromString(x.toString());
if (x < 0 || x >= 10) ImageMethods.scrollImage(leds, 1, interval);
else showLeds(leds, interval * 5);
}
export function showString(s: string, interval: number) {
interval |= 0;
2017-12-15 23:45:38 +01:00
if (interval <= 0)
interval = 1;
2016-08-30 20:51:32 +02:00
if (s.length == 0) {
clearScreen();
pause(interval * 5);
} else if (s.length > 1) {
ImageMethods.scrollImage(createImageFromString(s + " "), 1, interval);
2016-08-30 20:51:32 +02:00
} else {
showLeds(createImageFromString(s), interval * 5);
2016-08-30 20:51:32 +02:00
}
}
export function showLeds(leds: Image, interval: number): void {
interval |= 0;
showAnimation(leds, interval);
2016-08-30 20:51:32 +02:00
}
export function clearScreen() {
board().ledMatrixState.image.clear();
runtime.queueDisplayUpdate()
}
export function showAnimation(leds: Image, interval: number): void {
interval |= 0;
2016-08-30 20:51:32 +02:00
ImageMethods.scrollImage(leds, 5, interval);
}
export function plotLeds(leds: Image): void {
ImageMethods.plotImage(leds, 0);
}
}
namespace pxsim.led {
export function plot(x: number, y: number) {
x |= 0;
y |= 0;
2017-12-15 23:45:38 +01:00
board().ledMatrixState.image.set(x, y, 0xff);
runtime.queueDisplayUpdate()
}
export function plotBrightness(x: number, y: number, brightness: number) {
x |= 0;
y |= 0;
2017-12-15 23:45:38 +01:00
const state = board().ledMatrixState;
brightness |= 0;
brightness = Math.max(0, Math.min(led.brightness(), brightness));
2017-12-15 23:45:38 +01:00
if (brightness != 0 && brightness != 0xff && state.displayMode != DisplayMode.greyscale)
state.displayMode = DisplayMode.greyscale;
2017-12-15 23:45:38 +01:00
state.image.set(x, y, brightness);
2016-08-30 20:51:32 +02:00
runtime.queueDisplayUpdate()
}
export function unplot(x: number, y: number) {
x |= 0;
y |= 0;
2016-08-30 20:51:32 +02:00
board().ledMatrixState.image.set(x, y, 0);
runtime.queueDisplayUpdate()
}
export function pointBrightness(x: number, y: number): number {
x |= 0;
y |= 0;
return board().ledMatrixState.image.get(x, y);
2016-08-30 20:51:32 +02:00
}
export function brightness(): number {
return board().ledMatrixState.brigthness;
}
export function setBrightness(value: number): void {
value |= 0;
2017-12-15 23:45:38 +01:00
board().ledMatrixState.brigthness = Math.max(0, Math.min(255, value));
2016-08-30 20:51:32 +02:00
runtime.queueDisplayUpdate()
}
export function stopAnimation(): void {
board().ledMatrixState.animationQ.cancelAll();
2016-09-14 16:54:35 +02:00
board().ledMatrixState.image.clear();
2016-08-30 20:51:32 +02:00
}
export function setDisplayMode(mode: DisplayMode): void {
board().ledMatrixState.displayMode = mode;
runtime.queueDisplayUpdate()
}
2017-12-15 23:45:38 +01:00
export function displayMode(): DisplayMode {
2017-12-14 19:34:32 +01:00
return board().ledMatrixState.displayMode;
}
2016-08-30 20:51:32 +02:00
export function screenshot(): Image {
let img = createImage(5)
board().ledMatrixState.image.copyTo(0, 5, img, 0);
return img;
}
export function enable(on: boolean) {
board().ledMatrixState.disabled = !on;
runtime.queueDisplayUpdate();
}
2016-08-30 20:51:32 +02:00
}