pxt-common-packages
Version:
Microsoft MakeCode (PXT) common packages
1,142 lines (1,001 loc) • 33.7 kB
text/typescript
namespace pxsim {
export class RefImage extends RefObject {
_width: number;
_height: number;
_bpp: number;
data: Uint8Array;
isStatic = true;
revision: number;
constructor(w: number, h: number, bpp: number) {
super();
this.revision = 0;
this.data = new Uint8Array(w * h)
this._width = w
this._height = h
this._bpp = bpp
}
scan(mark: (path: string, v: any) => void) { }
gcKey() { return "Image" }
gcSize() { return 4 + (this.data.length + 3 >> 3) }
gcIsStatic() { return this.isStatic }
pix(x: number, y: number) {
return (x | 0) + (y | 0) * this._width
}
inRange(x: number, y: number) {
return 0 <= (x | 0) && (x | 0) < this._width &&
0 <= (y | 0) && (y | 0) < this._height;
}
color(c: number): number {
return c & 0xff
}
clamp(x: number, y: number) {
x |= 0
y |= 0
if (x < 0) x = 0
else if (x >= this._width)
x = this._width - 1
if (y < 0) y = 0
else if (y >= this._height)
y = this._height - 1
return [x, y]
}
makeWritable() {
this.revision++;
this.isStatic = false
}
toDebugString() {
return this._width + "x" + this._height
}
}
}
namespace pxsim.ImageMethods {
export function XX(x: number) { return (x << 16) >> 16 }
export function YY(x: number) { return x >> 16 }
export function width(img: RefImage) { return img._width }
export function height(img: RefImage) { return img._height }
export function isMono(img: RefImage) { return img._bpp == 1 }
export function isStatic(img: RefImage) { return img.gcIsStatic() }
export function revision(img: RefImage) { return img.revision }
export function setPixel(img: RefImage, x: number, y: number, c: number) {
img.makeWritable()
if (img.inRange(x, y))
img.data[img.pix(x, y)] = img.color(c)
}
export function getPixel(img: RefImage, x: number, y: number) {
if (img.inRange(x, y))
return img.data[img.pix(x, y)]
return 0
}
export function fill(img: RefImage, c: number) {
img.makeWritable()
img.data.fill(img.color(c))
}
export function fillRect(img: RefImage, x: number, y: number, w: number, h: number, c: number) {
if (w == 0 || h == 0 || x >= img._width || y >= img._height || x + w - 1 < 0 || y + h - 1 < 0)
return;
img.makeWritable()
let [x2, y2] = img.clamp(x + w - 1, y + h - 1);
[x, y] = img.clamp(x, y)
let p = img.pix(x, y)
w = x2 - x + 1
h = y2 - y + 1
let d = img._width - w
c = img.color(c)
while (h-- > 0) {
for (let i = 0; i < w; ++i)
img.data[p++] = c
p += d
}
}
export function _fillRect(img: RefImage, xy: number, wh: number, c: number) {
fillRect(img, XX(xy), YY(xy), XX(wh), YY(wh), c)
}
export function mapRect(img: RefImage, x: number, y: number, w: number, h: number, c: RefBuffer) {
if (c.data.length < 16)
return
img.makeWritable()
let [x2, y2] = img.clamp(x + w - 1, y + h - 1);
[x, y] = img.clamp(x, y)
let p = img.pix(x, y)
w = x2 - x + 1
h = y2 - y + 1
let d = img._width - w
while (h-- > 0) {
for (let i = 0; i < w; ++i) {
img.data[p] = c.data[img.data[p]]
p++
}
p += d
}
}
export function _mapRect(img: RefImage, xy: number, wh: number, c: RefBuffer) {
mapRect(img, XX(xy), YY(xy), XX(wh), YY(wh), c)
}
export function equals(img: RefImage, other: RefImage) {
if (!other || img._bpp != other._bpp || img._width != other._width || img._height != other._height) {
return false;
}
let imgData = img.data;
let otherData = other.data;
let len = imgData.length;
for (let i = 0; i < len; i++) {
if (imgData[i] != otherData[i]) {
return false;
}
}
return true;
}
export function getRows(img: RefImage, x: number, dst: RefBuffer) {
x |= 0
if (!img.inRange(x, 0))
return
let dp = 0
let len = Math.min(dst.data.length, (img._width - x) * img._height)
let sp = x
let hh = 0
while (len--) {
if (hh++ >= img._height) {
hh = 1
sp = ++x
}
dst.data[dp++] = img.data[sp]
sp += img._width
}
}
export function setRows(img: RefImage, x: number, src: RefBuffer) {
x |= 0
if (!img.inRange(x, 0))
return
let sp = 0
let len = Math.min(src.data.length, (img._width - x) * img._height)
let dp = x
let hh = 0
while (len--) {
if (hh++ >= img._height) {
hh = 1
dp = ++x
}
img.data[dp] = src.data[sp++]
dp += img._width
}
}
export function clone(img: RefImage) {
let r = new RefImage(img._width, img._height, img._bpp)
r.data.set(img.data)
return r
}
export function flipX(img: RefImage) {
img.makeWritable()
const w = img._width
const h = img._height
for (let i = 0; i < h; ++i) {
img.data.subarray(i * w, (i + 1) * w).reverse()
}
}
export function flipY(img: RefImage) {
img.makeWritable()
const w = img._width
const h = img._height
const d = img.data
for (let i = 0; i < w; ++i) {
let top = i
let bot = i + (h - 1) * w
while (top < bot) {
let c = d[top]
d[top] = d[bot]
d[bot] = c
top += w
bot -= w
}
}
}
export function transposed(img: RefImage) {
const w = img._width
const h = img._height
const d = img.data
const r = new RefImage(h, w, img._bpp)
const n = r.data
let src = 0
for (let i = 0; i < h; ++i) {
let dst = i
for (let j = 0; j < w; ++j) {
n[dst] = d[src++]
dst += w
}
}
return r
}
export function copyFrom(img: RefImage, from: RefImage) {
if (img._width != from._width || img._height != from._height ||
img._bpp != from._bpp)
return;
img.data.set(from.data)
}
export function scroll(img: RefImage, dx: number, dy: number) {
img.makeWritable()
dx |= 0
dy |= 0
if (dx != 0) {
const img2 = clone(img)
img.data.fill(0)
drawTransparentImage(img, img2, dx, dy)
} else if (dy < 0) {
dy = -dy
if (dy < img._height)
img.data.copyWithin(0, dy * img._width)
else
dy = img._height
img.data.fill(0, (img._height - dy) * img._width)
} else if (dy > 0) {
if (dy < img._height)
img.data.copyWithin(dy * img._width, 0)
else
dy = img._height
img.data.fill(0, 0, dy * img._width)
}
// TODO implement dx
}
export function replace(img: RefImage, from: number, to: number) {
to &= 0xf;
const d = img.data
for (let i = 0; i < d.length; ++i)
if (d[i] == from) d[i] = to
}
export function doubledX(img: RefImage) {
const w = img._width
const h = img._height
const d = img.data
const r = new RefImage(w * 2, h, img._bpp)
const n = r.data
let dst = 0
for (let src = 0; src < d.length; ++src) {
let c = d[src]
n[dst++] = c
n[dst++] = c
}
return r
}
export function doubledY(img: RefImage) {
const w = img._width
const h = img._height
const d = img.data
const r = new RefImage(w, h * 2, img._bpp)
const n = r.data
let src = 0
let dst0 = 0
let dst1 = w
for (let i = 0; i < h; ++i) {
for (let j = 0; j < w; ++j) {
let c = d[src++]
n[dst0++] = c
n[dst1++] = c
}
dst0 += w
dst1 += w
}
return r
}
export function doubled(img: RefImage) {
return doubledX(doubledY(img))
}
function drawImageCore(img: RefImage, from: RefImage, x: number, y: number, clear: boolean, check: boolean) {
x |= 0
y |= 0
const w = from._width
let h = from._height
const sh = img._height
const sw = img._width
if (x + w <= 0) return false
if (x >= sw) return false
if (y + h <= 0) return false
if (y >= sh) return false
if (clear)
fillRect(img, x, y, from._width, from._height, 0)
else if (!check)
img.makeWritable()
const len = x < 0 ? Math.min(sw, w + x) : Math.min(sw - x, w)
const fdata = from.data
const tdata = img.data
for (let p = 0; h--; y++ , p += w) {
if (0 <= y && y < sh) {
let dst = y * sw
let src = p
if (x < 0)
src += -x
else
dst += x
for (let i = 0; i < len; ++i) {
const v = fdata[src++]
if (v) {
if (check) {
if (tdata[dst])
return true
} else {
tdata[dst] = v
}
}
dst++
}
}
}
return false
}
export function drawImage(img: RefImage, from: RefImage, x: number, y: number) {
drawImageCore(img, from, x, y, true, false)
}
export function drawTransparentImage(img: RefImage, from: RefImage, x: number, y: number) {
drawImageCore(img, from, x, y, false, false)
}
export function overlapsWith(img: RefImage, other: RefImage, x: number, y: number) {
return drawImageCore(img, other, x, y, false, true)
}
function drawLineLow(img: RefImage, x0: number, y0: number, x1: number, y1: number, c: number) {
let dx = x1 - x0;
let dy = y1 - y0;
let yi = img._width;
if (dy < 0) {
yi = -yi;
dy = -dy;
}
let D = 2 * dy - dx;
dx <<= 1;
dy <<= 1;
c = img.color(c);
let ptr = img.pix(x0, y0)
for (let x = x0; x <= x1; ++x) {
img.data[ptr] = c
if (D > 0) {
ptr += yi;
D -= dx;
}
D += dy;
ptr++;
}
}
function drawLineHigh(img: RefImage, x0: number, y0: number, x1: number, y1: number, c: number) {
let dx = x1 - x0;
let dy = y1 - y0;
let xi = 1;
if (dx < 0) {
xi = -1;
dx = -dx;
}
let D = 2 * dx - dy;
dx <<= 1;
dy <<= 1;
c = img.color(c);
let ptr = img.pix(x0, y0);
for (let y = y0; y <= y1; ++y) {
img.data[ptr] = c;
if (D > 0) {
ptr += xi;
D -= dy;
}
D += dx;
ptr += img._width;
}
}
export function _drawLine(img: RefImage, xy: number, wh: number, c: number) {
drawLine(img, XX(xy), YY(xy), XX(wh), YY(wh), c)
}
export function drawLine(img: RefImage, x0: number, y0: number, x1: number, y1: number, c: number) {
x0 |= 0
y0 |= 0
x1 |= 0
y1 |= 0
if (x1 < x0) {
drawLine(img, x1, y1, x0, y0, c);
return;
}
let w = x1 - x0;
let h = y1 - y0;
if (h == 0) {
if (w == 0)
setPixel(img, x0, y0, c);
else
fillRect(img, x0, y0, w + 1, 1, c);
return;
}
if (w == 0) {
if (h > 0)
fillRect(img, x0, y0, 1, h + 1, c);
else
fillRect(img, x0, y1, 1, -h + 1, c);
return;
}
if (x1 < 0 || x0 >= img._width)
return;
if (x0 < 0) {
y0 -= (h * x0 / w) | 0;
x0 = 0;
}
if (x1 >= img._width) {
let d = (img._width - 1) - x1;
y1 += (h * d / w) | 0;
x1 = img._width - 1
}
if (y0 < y1) {
if (y0 >= img._height || y1 < 0)
return;
if (y0 < 0) {
x0 -= (w * y0 / h) | 0;
y0 = 0;
}
if (y1 >= img._height) {
let d = (img._height - 1) - y1;
x1 += (w * d / h) | 0;
y1 = img._height
}
} else {
if (y1 >= img._height || y0 < 0)
return;
if (y1 < 0) {
x1 -= (w * y1 / h) | 0;
y1 = 0;
}
if (y0 >= img._height) {
let d = (img._height - 1) - y0;
x0 += (w * d / h) | 0;
y0 = img._height
}
}
img.makeWritable()
if (h < 0) {
h = -h;
if (h < w)
drawLineLow(img, x0, y0, x1, y1, c);
else
drawLineHigh(img, x1, y1, x0, y0, c);
} else {
if (h < w)
drawLineLow(img, x0, y0, x1, y1, c);
else
drawLineHigh(img, x0, y0, x1, y1, c);
}
}
export function drawIcon(img: RefImage, icon: RefBuffer, x: number, y: number, color: number) {
const src: Uint8Array = icon.data
if (!image.isValidImage(icon))
return
if (src[1] != 1)
return // only mono
let width = image.bufW(src)
let height = image.bufH(src)
let byteH = image.byteHeight(height, 1)
x |= 0
y |= 0
const destHeight = img._height
const destWidth = img._width
if (x + width <= 0) return
if (x >= destWidth) return
if (y + height <= 0) return
if (y >= destHeight) return
img.makeWritable()
let srcPointer = 8
color = img.color(color)
const screen = img.data
for (let i = 0; i < width; ++i) {
let destX = x + i
if (0 <= destX && destX < destWidth) {
let destIndex = destX + y * destWidth
let srcIndex = srcPointer
let destY = y
let destEnd = Math.min(destHeight, height + y)
if (y < 0) {
srcIndex += ((-y) >> 3)
destY += ((-y) >> 3) * 8
destIndex += (destY - y) * destWidth
}
let mask = 0x01
let srcByte = src[srcIndex++]
while (destY < destEnd) {
if (destY >= 0 && (srcByte & mask)) {
screen[destIndex] = color
}
mask <<= 1
if (mask == 0x100) {
mask = 0x01
srcByte = src[srcIndex++]
}
destIndex += destWidth
destY++
}
}
srcPointer += byteH
}
}
export function _drawIcon(img: RefImage, icon: RefBuffer, xy: number, color: number) {
drawIcon(img, icon, XX(xy), YY(xy), color)
}
export function fillCircle(img: RefImage, cx: number, cy: number, r: number, c: number) {
let x = r - 1;
let y = 0;
let dx = 1;
let dy = 1;
let err = dx - (r << 1);
while (x >= y) {
fillRect(img, cx + x, cy - y, 1, 1 + (y << 1), c);
fillRect(img, cx + y, cy - x, 1, 1 + (x << 1), c);
fillRect(img, cx - x, cy - y, 1, 1 + (y << 1), c);
fillRect(img, cx - y, cy - x, 1, 1 + (x << 1), c);
if (err <= 0) {
y++;
err += dy;
dy += 2;
}
if (err > 0) {
x--;
dx += 2;
err += dx - (r << 1);
}
}
}
export function _fillCircle(img: RefImage, cxy: number, r: number, c: number) {
fillCircle(img, XX(cxy), YY(cxy), r, c);
}
interface LineGenState {
x: number;
y: number;
x0: number;
y0: number;
x1: number;
y1: number;
W: number;
H: number;
dx: number;
dy: number;
yi: number;
xi: number;
D: number;
nextFuncIndex: number;
}
interface ValueRange {
min: number;
max: number;
}
function nextYRange_Low(x: number, line: LineGenState, yRange: ValueRange) {
while (line.x === x && line.x <= line.x1 && line.x < line.W) {
if (0 <= line.x) {
if (line.y < yRange.min) yRange.min = line.y;
if (line.y > yRange.max) yRange.max = line.y
}
if (line.D > 0) {
line.y += line.yi;
line.D -= line.dx;
}
line.D += line.dy;
++line.x;
}
}
function nextYRange_HighUp(x: number, line: LineGenState, yRange: ValueRange) {
while (line.x == x && line.y >= line.y1 && line.x < line.W) {
if (0 <= line.x) {
if (line.y < yRange.min) yRange.min = line.y;
if (line.y > yRange.max) yRange.max = line.y;
}
if (line.D > 0) {
line.x += line.xi;
line.D += line.dy;
}
line.D += line.dx;
--line.y;
}
}
function nextYRange_HighDown(x: number, line: LineGenState, yRange: ValueRange) {
while (line.x == x && line.y <= line.y1 && line.x < line.W) {
if (0 <= line.x) {
if (line.y < yRange.min) yRange.min = line.y;
if (line.y > yRange.max) yRange.max = line.y;
}
if (line.D > 0) {
line.x += line.xi;
line.D -= line.dy;
}
line.D += line.dx;
++line.y;
}
}
function initYRangeGenerator(X0: number, Y0: number, X1: number, Y1: number): LineGenState {
const line: LineGenState = {
x: X0,
y: Y0,
x0: X0,
y0: Y0,
x1: X1,
y1: Y1,
W: 0,
H: 0,
dx: X1 - X0,
dy: Y1 - Y0,
yi: 0,
xi: 0,
D: 0,
nextFuncIndex: 0,
};
if ((line.dy < 0 ? -line.dy : line.dy) < line.dx) {
line.yi = 1;
if (line.dy < 0) {
line.yi = -1;
line.dy = -line.dy;
}
line.D = 2 * line.dy - line.dx;
line.dx = line.dx << 1;
line.dy = line.dy << 1;
line.nextFuncIndex = 0;
return line;
} else {
line.xi = 1;
if (line.dy < 0) {
line.D = 2 * line.dx + line.dy;
line.dx = line.dx << 1;
line.dy = line.dy << 1;
line.nextFuncIndex = 1;
return line;
} else {
line.D = 2 * line.dx - line.dy;
line.dx = line.dx << 1;
line.dy = line.dy << 1;
line.nextFuncIndex = 2;
return line;
}
}
}
export function fillTriangle(img: RefImage, x0: number, y0: number, x1: number, y1: number, x2: number, y2: number, c: number) {
if (x1 < x0) {
[x1, x0] = [x0, x1];
[y1, y0] = [y0, y1];
}
if (x2 < x1) {
[x2, x1] = [x1, x2];
[y2, y1] = [y1, y2];
}
if (x1 < x0) {
[x1, x0] = [x0, x1];
[y1, y0] = [y0, y1];
}
const lines: LineGenState[] = [
initYRangeGenerator(x0, y0, x2, y2),
initYRangeGenerator(x0, y0, x1, y1),
initYRangeGenerator(x1, y1, x2, y2)
];
lines[0].W = lines[1].W = lines[2].W = width(img);
lines[0].H = lines[1].H = lines[2].H = height(img);
type FP_NEXT = (x: number, line: LineGenState, yRange: ValueRange) => void;
const nextFuncList: FP_NEXT[] = [
nextYRange_Low,
nextYRange_HighUp,
nextYRange_HighDown
];
const fpNext0 = nextFuncList[lines[0].nextFuncIndex];
const fpNext1 = nextFuncList[lines[1].nextFuncIndex];
const fpNext2 = nextFuncList[lines[2].nextFuncIndex];
const yRange= {
min: lines[0].H,
max: -1
};
for (let x = lines[1].x0; x <= lines[1].x1; x++) {
yRange.min = lines[0].H; yRange.max = -1;
fpNext0(x, lines[0], yRange);
fpNext1(x, lines[1], yRange);
fillRect(img, x, yRange.min, 1, yRange.max - yRange.min + 1, c);
}
fpNext2(lines[2].x0, lines[2], yRange);
for (let x = lines[2].x0 + 1; x <= lines[2].x1; x++) {
yRange.min = lines[0].H; yRange.max = -1;
fpNext0(x, lines[0], yRange);
fpNext2(x, lines[2], yRange);
fillRect(img, x, yRange.min, 1, yRange.max - yRange.min + 1, c);
}
}
export function _fillTriangle(img: RefImage, args: RefCollection) {
fillTriangle(
img,
args.getAt(0) | 0,
args.getAt(1) | 0,
args.getAt(2) | 0,
args.getAt(3) | 0,
args.getAt(4) | 0,
args.getAt(5) | 0,
args.getAt(6) | 0,
);
}
export function fillPolygon4(img: RefImage, x0: number, y0: number, x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, c: number) {
const lines: LineGenState[]= [
(x0 < x1) ? initYRangeGenerator(x0, y0, x1, y1) : initYRangeGenerator(x1, y1, x0, y0),
(x1 < x2) ? initYRangeGenerator(x1, y1, x2, y2) : initYRangeGenerator(x2, y2, x1, y1),
(x2 < x3) ? initYRangeGenerator(x2, y2, x3, y3) : initYRangeGenerator(x3, y3, x2, y2),
(x0 < x3) ? initYRangeGenerator(x0, y0, x3, y3) : initYRangeGenerator(x3, y3, x0, y0)
];
lines[0].W = lines[1].W = lines[2].W = lines[3].W = width(img);
lines[0].H = lines[1].H = lines[2].H = lines[3].H = height(img);
let minX = Math.min(Math.min(x0, x1), Math.min(x2, x3));
let maxX = Math.min(Math.max(Math.max(x0, x1), Math.max(x2, x3)), lines[0].W - 1);
type FP_NEXT = (x: number, line: LineGenState, yRange: ValueRange) => void;
const nextFuncList: FP_NEXT[] = [
nextYRange_Low,
nextYRange_HighUp,
nextYRange_HighDown
];
const fpNext0 = nextFuncList[lines[0].nextFuncIndex];
const fpNext1 = nextFuncList[lines[1].nextFuncIndex];
const fpNext2 = nextFuncList[lines[2].nextFuncIndex];
const fpNext3 = nextFuncList[lines[3].nextFuncIndex];
const yRange: ValueRange = {
min: lines[0].H,
max: -1
};
for (let x = minX; x <= maxX; x++) {
yRange.min = lines[0].H; yRange.max = -1;
fpNext0(x, lines[0], yRange);
fpNext1(x, lines[1], yRange);
fpNext2(x, lines[2], yRange);
fpNext3(x, lines[3], yRange);
fillRect(img, x,yRange.min, 1, yRange.max - yRange.min + 1, c);
}
}
export function _fillPolygon4(img: RefImage, args: RefCollection) {
fillPolygon4(
img,
args.getAt(0) | 0,
args.getAt(1) | 0,
args.getAt(2) | 0,
args.getAt(3) | 0,
args.getAt(4) | 0,
args.getAt(5) | 0,
args.getAt(6) | 0,
args.getAt(7) | 0,
args.getAt(8) | 0,
);
}
export function _blitRow(img: RefImage, xy: number, from: RefImage, xh: number) {
blitRow(img, XX(xy), YY(xy), from, XX(xh), YY(xh))
}
export function blitRow(img: RefImage, x: number, y: number, from: RefImage, fromX: number, fromH: number) {
x |= 0
y |= 0
fromX |= 0
fromH |= 0
if (!img.inRange(x, 0) || !img.inRange(fromX, 0) || fromH <= 0)
return
let fy = 0
let stepFY = ((from._width << 16) / fromH) | 0
let endY = y + fromH
if (endY > img._height)
endY = img._height
if (y < 0) {
fy += -y * stepFY
y = 0
}
while (y < endY) {
img.data[img.pix(x, y)] = from.data[from.pix(fromX, fy >> 16)]
y++
fy += stepFY
}
}
export function _blit(img: RefImage, src: RefImage, args: RefCollection): boolean {
return blit(img, src, args);
}
export function blit(dst: RefImage, src: RefImage, args: RefCollection): boolean {
const xDst = args.getAt(0) as number;
const yDst = args.getAt(1) as number;
const wDst = args.getAt(2) as number;
const hDst = args.getAt(3) as number;
const xSrc = args.getAt(4) as number;
const ySrc = args.getAt(5) as number;
const wSrc = args.getAt(6) as number;
const hSrc = args.getAt(7) as number;
const transparent = args.getAt(8) as number;
const check = args.getAt(9) as number;
const xSrcStep = ((wSrc << 16) / wDst) | 0;
const ySrcStep = ((hSrc << 16) / hDst) | 0;
const xDstClip = Math.abs(Math.min(0, xDst));
const yDstClip = Math.abs(Math.min(0, yDst));
const xDstStart = xDst + xDstClip;
const yDstStart = yDst + yDstClip;
const xDstEnd = Math.min(dst._width, xDst + wDst);
const yDstEnd = Math.min(dst._height, yDst + hDst);
const xSrcStart = Math.max(0, (xSrc << 16) + xDstClip * xSrcStep);
const ySrcStart = Math.max(0, (ySrc << 16) + yDstClip * ySrcStep);
const xSrcEnd = Math.min(src._width, xSrc + wSrc) << 16;
const ySrcEnd = Math.min(src._height, ySrc + hSrc) << 16;
if (!check)
dst.makeWritable();
for (let yDstCur = yDstStart, ySrcCur = ySrcStart; yDstCur < yDstEnd && ySrcCur < ySrcEnd; ++yDstCur, ySrcCur += ySrcStep) {
const ySrcCurI = ySrcCur >> 16;
for (let xDstCur = xDstStart, xSrcCur = xSrcStart; xDstCur < xDstEnd && xSrcCur < xSrcEnd; ++xDstCur, xSrcCur += xSrcStep) {
const xSrcCurI = xSrcCur >> 16;
const cSrc = getPixel(src, xSrcCurI, ySrcCurI);
if (check && cSrc) {
const cDst = getPixel(dst, xDstCur, yDstCur);
if (cDst) {
return true;
}
continue;
}
if (!transparent || cSrc) {
setPixel(dst, xDstCur, yDstCur, cSrc);
}
}
}
return false;
}
}
namespace pxsim.image {
export function byteHeight(h: number, bpp: number) {
if (bpp == 1)
return h * bpp + 7 >> 3
else
return ((h * bpp + 31) >> 5) << 2
}
function isLegacyImage(buf: RefBuffer) {
if (!buf || buf.data.length < 5)
return false;
if (buf.data[0] != 0xe1 && buf.data[0] != 0xe4)
return false;
const bpp = buf.data[0] & 0xf;
const sz = buf.data[1] * byteHeight(buf.data[2], bpp)
if (4 + sz != buf.data.length)
return false;
return true;
}
export function bufW(data: Uint8Array) {
return data[2] | (data[3] << 8)
}
export function bufH(data: Uint8Array) {
return data[4] | (data[5] << 8)
}
export function isValidImage(buf: RefBuffer) {
if (!buf || buf.data.length < 5)
return false;
if (buf.data[0] != 0x87)
return false
if (buf.data[1] != 1 && buf.data[1] != 4)
return false;
const bpp = buf.data[1];
const sz = bufW(buf.data) * byteHeight(bufH(buf.data), bpp)
if (8 + sz != buf.data.length)
return false;
return true;
}
export function create(w: number, h: number) {
// truncate decimal sizes
w |= 0
h |= 0
return new RefImage(w, h, getScreenState().bpp())
}
export function ofBuffer(buf: RefBuffer): RefImage {
const src: Uint8Array = buf.data
let srcP = 4
let w = 0, h = 0, bpp = 0
if (isLegacyImage(buf)) {
w = src[1]
h = src[2]
bpp = src[0] & 0xf;
// console.log("using legacy image")
} else if (isValidImage(buf)) {
srcP = 8
w = bufW(src)
h = bufH(src)
bpp = src[1]
}
if (w == 0 || h == 0)
return null
const r = new RefImage(w, h, bpp)
const dst = r.data
r.isStatic = buf.isStatic
if (bpp == 1) {
for (let i = 0; i < w; ++i) {
let dstP = i
let mask = 0x01
let v = src[srcP++]
for (let j = 0; j < h; ++j) {
if (mask == 0x100) {
mask = 0x01
v = src[srcP++]
}
if (v & mask)
dst[dstP] = 1
dstP += w
mask <<= 1
}
}
} else if (bpp == 4) {
for (let i = 0; i < w; ++i) {
let dstP = i
for (let j = 0; j < h >> 1; ++j) {
const v = src[srcP++]
dst[dstP] = v & 0xf
dstP += w
dst[dstP] = v >> 4
dstP += w
}
if (h & 1)
dst[dstP] = src[srcP++] & 0xf
srcP = (srcP + 3) & ~3
}
}
return r
}
export function toBuffer(img: RefImage): RefBuffer {
let col = byteHeight(img._height, img._bpp)
let sz = 8 + img._width * col
let r = new Uint8Array(sz)
r[0] = 0x87
r[1] = img._bpp
r[2] = img._width & 0xff
r[3] = img._width >> 8
r[4] = img._height & 0xff
r[5] = img._height >> 8
let dstP = 8
const w = img._width
const h = img._height
const data = img.data
for (let i = 0; i < w; ++i) {
if (img._bpp == 4) {
let p = i
for (let j = 0; j < h; j += 2) {
r[dstP++] = ((data[p + w] & 0xf) << 4) | ((data[p] || 0) & 0xf)
p += 2 * w
}
dstP = (dstP + 3) & ~3
} else if (img._bpp == 1) {
let mask = 0x01
let p = i
for (let j = 0; j < h; j++) {
if (data[p])
r[dstP] |= mask
mask <<= 1
p += w
if (mask == 0x100) {
mask = 0x01
dstP++
}
}
if (mask != 0x01)
dstP++
}
}
return new RefBuffer(r)
}
export function doubledIcon(buf: RefBuffer): RefBuffer {
let img = ofBuffer(buf)
if (!img)
return null
img = ImageMethods.doubled(img)
return toBuffer(img)
}
}
namespace pxsim.pxtcore {
export function updateScreen(img: RefImage) {
const state = getScreenState();
if (state)
state.showImage(img)
}
export function updateStats(s: string) {
const state = getScreenState();
if (state)
state.updateStats(s);
}
export function setPalette(b: RefBuffer) {
const state = getScreenState();
if (state)
state.setPalette(b)
}
export function setupScreenStatusBar(barHeight: number) {
const state = getScreenState();
if (state)
state.setupScreenStatusBar(barHeight);
}
export function updateScreenStatusBar(img: RefImage) {
const state = getScreenState();
if (state)
state.updateScreenStatusBar(img);
}
export function setScreenBrightness(b: number) {
// I guess we could at least turn the screen off, when b==0,
// otherwise, it probably doesn't make much sense to do anything.
const state = getScreenState();
if (state)
state.setScreenBrightness(b);
}
}