apple2js/js/canvas.js

1157 lines
39 KiB
JavaScript
Raw Normal View History

2019-03-01 05:21:18 +00:00
/* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation. No representations are made about the suitability of this
* software for any purpose. It is provided "as is" without express or
* implied warranty.
*/
2019-03-01 05:21:18 +00:00
import { base64_decode, base64_encode } from './base64';
2020-06-05 19:53:56 +00:00
import { allocMemPages } from './util';
2019-03-01 05:21:18 +00:00
var enhanced = false;
2017-09-23 04:42:57 +00:00
var multiScreen = false;
var textMode = true;
var mixedMode = false;
var hiresMode = false;
var pageMode = 1;
var _80colMode = false;
var altCharMode = false;
2020-06-05 19:53:56 +00:00
var an3 = false;
var doubleHiresMode = false;
2016-11-22 05:17:34 +00:00
var monoDHRMode = false;
var colorDHRMode = false;
var mixedDHRMode = false;
var highColorHGRMode = false;
var highColorTextMode = false;
var oneSixtyMode = false;
2016-11-22 05:17:34 +00:00
2017-10-01 18:18:41 +00:00
function dim(c) {
return [
c[0] * 0.75 & 0xff,
c[1] * 0.75 & 0xff,
c[2] * 0.75 & 0xff
];
}
// hires colors
2020-01-01 20:33:24 +00:00
var orangeCol = [255, 106, 60];
var greenCol = [ 20, 245, 60];
var blueCol = [ 20, 207, 253];
var violetCol = [255, 68, 253];
var whiteCol = [255, 255, 255];
var blackCol = [ 0, 0, 0];
2017-10-01 18:18:41 +00:00
/****************************************************************************
*
* Text/Lores Graphics
*
***************************************************************************/
2019-03-01 05:21:18 +00:00
export function LoresPage(page, charset, e, context)
{
// $00-$3F inverse
// $40-$7F flashing
// $80-$FF normal
var _page = page;
2017-09-23 04:42:57 +00:00
var _imageData;
var _buffer = [];
var _refreshing = false;
2020-01-03 05:12:34 +00:00
var _monoMode = false;
var _blink = false;
var _dirty = {
top: 385,
bottom: -1,
left: 561,
right: -1
};
var _colors = [
2020-01-01 20:33:24 +00:00
[ 0, 0, 0], // 0x0 black
[227, 30, 96], // 0x1 deep red
[ 96, 78, 189], // 0x2 dark blue
[255, 68, 253], // 0x3 purple
[ 0, 163, 96], // 0x4 dark green
[156, 156, 156], // 0x5 dark gray
[ 20, 207, 253], // 0x6 medium blue
[208, 195, 255], // 0x7 light blue
[ 96, 114, 3], // 0x8 brown
[255, 106, 60], // 0x9 orange
[156, 156, 156], // 0xa light gray
[255, 160, 208], // 0xb pink
[ 20, 245, 60], // 0xc green
[208, 221, 141], // 0xd yellow
[114, 255, 208], // 0xe aquamarine
[255, 255, 255], // 0xf white
];
function _init() {
2017-09-23 04:42:57 +00:00
var idx;
_imageData = context.createImageData(560, 384);
for (idx = 0; idx < 560 * 384 * 4; idx++) {
_imageData.data[idx] = 0xff;
}
_buffer[0] = allocMemPages(0x4);
_buffer[1] = allocMemPages(0x4);
}
function _drawPixel(data, off, color) {
var c0 = color[0], c1 = color[1], c2 = color[2];
data[off + 0] = data[off + 4] = c0;
data[off + 1] = data[off + 5] = c1;
data[off + 2] = data[off + 6] = c2;
2019-11-24 03:05:52 +00:00
var nextOff = off + 560 * 4;
2020-01-03 05:12:34 +00:00
data[nextOff] = data[nextOff + 4] = c0;
data[nextOff + 1] = data[nextOff + 5] = c1;
data[nextOff + 2] = data[nextOff + 6] = c2;
}
function _drawHalfPixel(data, off, color) {
var c0 = color[0], c1 = color[1], c2 = color[2];
data[off + 0] = c0;
data[off + 1] = c1;
data[off + 2] = c2;
2019-11-24 03:05:52 +00:00
var nextOff = off + 560 * 4;
2020-01-03 05:12:34 +00:00
data[nextOff] = c0;
data[nextOff + 1] = c1;
data[nextOff + 2] = c2;
}
2016-11-22 05:17:34 +00:00
_init();
return {
bank0: function() {
var self = this;
return {
start: function() {
return self._start();
},
end: function() {
return self._end();
},
read: function(page, off) {
return self._read(page, off, 0);
},
write: function(page, off, val) {
return self._write(page, off, val, 0);
}
};
},
bank1: function() {
var self = this;
return {
start: function() {
return self._start();
},
end: function() {
return self._end();
},
read: function(page, off) {
return self._read(page, off, 1);
},
write: function(page, off, val) {
return self._write(page, off, val, 1);
}
};
},
// These are used by both bank 0 and 1
_start: function() {
return (0x04 * _page);
},
_end: function() { return (0x04 * _page) + 0x03; },
_read: function(page, off, bank) {
2020-01-02 18:57:26 +00:00
var addr = (page << 8) | off, base = addr & 0x3FF;
return _buffer[bank][base];
},
2016-11-28 01:28:49 +00:00
_write: function(page, off, val, bank) {
var addr = (page << 8) | off,
2020-01-02 18:57:26 +00:00
base = addr & 0x3FF,
fore, back;
2017-09-23 04:42:57 +00:00
if (_buffer[bank][base] == val && !_refreshing) {
return;
2017-09-23 04:42:57 +00:00
}
_buffer[bank][base] = val;
var col = (base % 0x80) % 0x28,
adj = off - col;
2016-11-22 05:17:34 +00:00
// 000001cd eabab000 -> 000abcde
var ab = (adj & 0x18),
cd = (page & 0x03) << 1,
2017-09-24 14:06:57 +00:00
ee = adj >> 7;
var idx, jdx;
2017-09-24 14:06:57 +00:00
var row = ab | cd | ee;
2013-12-19 22:28:55 +00:00
var b;
2017-09-23 04:42:57 +00:00
var data = _imageData.data;
if ((row < 24) && (col < 40)) {
2019-11-24 03:05:52 +00:00
var y = row << 4;
if (y < _dirty.top) { _dirty.top = y; }
y += 16;
if (y > _dirty.bottom) { _dirty.bottom = y; }
var x = col * 14;
if (x < _dirty.left) { _dirty.left = x; }
x += 14;
if (x > _dirty.right) { _dirty.right = x; }
var color;
2017-09-23 04:42:57 +00:00
if (textMode || hiresMode || (mixedMode && row > 19)) {
2017-10-01 18:18:41 +00:00
var inverse;
2017-09-24 14:06:57 +00:00
if (e) {
2017-10-01 18:18:41 +00:00
if (!_80colMode && !altCharMode) {
inverse = ((val & 0xc0) == 0x40) && _blink;
2017-09-24 14:06:57 +00:00
}
2017-10-01 18:18:41 +00:00
} else {
inverse = !((val & 0x80) || (val & 0x40) && _blink);
2017-09-24 14:06:57 +00:00
}
2020-01-03 05:12:34 +00:00
fore = inverse ? blackCol : whiteCol;
back = inverse ? whiteCol : blackCol;
2017-10-01 18:18:41 +00:00
if (_80colMode) {
2016-11-22 05:17:34 +00:00
if (!enhanced) {
val = (val >= 0x40 && val < 0x60) ? val - 0x40 : val;
} else if (!altCharMode) {
val = (val >= 0x40 && val < 0x80) ? val - 0x40 : val;
}
off = (col * 14 + (bank ? 0 : 1) * 7 + row * 560 * 8 * 2) * 4;
2016-11-22 05:17:34 +00:00
for (jdx = 0; jdx < 8; jdx++) {
b = charset[val * 8 + jdx];
for (idx = 0; idx < 7; idx++) {
color = (b & 0x01) ? back : fore;
_drawHalfPixel(data, off, color);
b >>= 1;
off += 4;
}
off += 553 * 4 + 560 * 4;
}
} else {
2016-11-22 05:17:34 +00:00
val = _buffer[0][base];
if (!enhanced) {
val = (val >= 0x40 && val < 0x60) ? val - 0x40 : val;
} else if (!altCharMode) {
val = (val >= 0x40 && val < 0x80) ? val - 0x40 : val;
}
off = (col * 14 + row * 560 * 8 * 2) * 4;
2016-11-22 05:17:34 +00:00
if (highColorTextMode) {
2017-09-23 04:42:57 +00:00
fore = _colors[_buffer[1][base] >> 4];
back = _colors[_buffer[1][base] & 0x0f];
2016-11-22 05:17:34 +00:00
}
2017-09-24 14:06:57 +00:00
if (e) {
for (jdx = 0; jdx < 8; jdx++) {
b = charset[val * 8 + jdx];
for (idx = 0; idx < 7; idx++) {
color = (b & 0x01) ? back : fore;
_drawPixel(data, off, color);
b >>= 1;
off += 8;
}
off += 546 * 4 + 560 * 4;
}
} else {
2020-01-03 05:12:34 +00:00
var colorMode = mixedMode && !textMode && !_monoMode;
2017-10-01 18:18:41 +00:00
// var val0 = col > 0 ? _buffer[0][base - 1] : 0;
// var val2 = col < 39 ? _buffer[0][base + 1] : 0;
2017-09-24 14:06:57 +00:00
for (jdx = 0; jdx < 8; jdx++) {
2017-10-01 18:18:41 +00:00
var odd = !(col & 0x1);
b = charset[val * 8 + jdx] << 1;
if (colorMode) {
// var b0 = charset[val0 * 8 + jdx];
// var b2 = charset[val2 * 8 + jdx];
if (inverse) { b ^= 0x1ff; }
}
2017-09-24 14:06:57 +00:00
for (idx = 0; idx < 7; idx++) {
2017-10-01 18:18:41 +00:00
if (colorMode) {
if (b & 0x80) {
if ((b & 0x1c0) != 0x80) {
color = whiteCol;
} else {
color = odd ? violetCol : greenCol;
}
} else {
color = blackCol;
}
odd = !odd;
} else {
color = (b & 0x80) ? fore : back;
}
2017-09-24 14:06:57 +00:00
_drawPixel(data, off, color);
b <<= 1;
off += 8;
}
off += 546 * 4 + 560 * 4;
}
}
}
} else {
2020-06-05 19:53:56 +00:00
if (!_80colMode && bank == 1) {
2019-09-29 22:52:22 +00:00
return;
}
2020-06-05 19:53:56 +00:00
if (_80colMode && !an3) {
off = (col * 14 + (bank ? 0 : 1) * 7 + row * 560 * 8 * 2) * 4;
2020-01-03 05:12:34 +00:00
if (_monoMode) {
fore = whiteCol;
2017-10-01 18:18:41 +00:00
back = blackCol;
for (jdx = 0; jdx < 8; jdx++) {
2014-06-26 13:46:01 +00:00
b = (jdx < 8) ? (val & 0x0f) : (val >> 4);
b |= (b << 4);
if (bank & 0x1) {
b <<= 1;
}
for (idx = 0; idx < 7; idx++) {
color = (b & 0x80) ? fore : back;
_drawHalfPixel(data, off, color);
b <<= 1;
off += 4;
}
2014-06-26 13:46:01 +00:00
off += 553 * 4 + 560 * 4;
2016-11-22 05:17:34 +00:00
}
} else {
2014-06-26 13:46:01 +00:00
if (bank & 0x1) {
val = ((val & 0x77) << 1) | ((val & 0x88) >> 3);
}
for (jdx = 0; jdx < 8; jdx++) {
2016-11-22 05:17:34 +00:00
color = _colors[(jdx < 4) ?
2017-09-24 14:06:57 +00:00
(val & 0x0f) : (val >> 4)];
for (idx = 0; idx < 7; idx++) {
_drawHalfPixel(data, off, color);
off += 4;
}
2014-06-26 13:46:01 +00:00
off += 553 * 4 + 560 * 4;
}
}
} else {
off = (col * 14 + row * 560 * 8 * 2) * 4;
2020-01-03 05:12:34 +00:00
if (_monoMode) {
fore = whiteCol;
2017-10-01 18:18:41 +00:00
back = blackCol;
for (jdx = 0; jdx < 8; jdx++) {
b = (jdx < 4) ? (val & 0x0f) : (val >> 4);
b |= (b << 4);
b |= (b << 8);
if (col & 0x1) {
b <<= 2;
}
for (idx = 0; idx < 14; idx++) {
color = (b & 0x8000) ? fore : back;
_drawHalfPixel(data, off, color);
b <<= 1;
off += 4;
}
off += 546 * 4 + 560 * 4;
2016-11-22 05:17:34 +00:00
}
} else {
for (jdx = 0; jdx < 8; jdx++) {
2017-09-24 14:06:57 +00:00
color = _colors[(jdx < 4) ? (val & 0x0f) : (val >> 4)];
2016-11-22 05:17:34 +00:00
for (idx = 0; idx < 7; idx++) {
_drawPixel(data, off, color);
off += 8;
}
off += 546 * 4 + 560 * 4;
}
}
}
}
}
},
2016-11-28 01:28:49 +00:00
refresh: function() {
2016-11-22 05:17:34 +00:00
var addr = 0x400 * _page;
_refreshing = true;
for (var idx = 0; idx < 0x400; idx++, addr++) {
this._write(addr >> 8, addr & 0xff, _buffer[0][idx], 0);
2017-09-23 04:42:57 +00:00
if (_80colMode) {
this._write(addr >> 8, addr & 0xff, _buffer[1][idx], 1);
2017-09-23 04:42:57 +00:00
}
}
_refreshing = false;
},
2016-11-28 01:28:49 +00:00
blink: function() {
var addr = 0x400 * _page;
2016-11-22 05:17:34 +00:00
_refreshing = true;
_blink = !_blink;
for (var idx = 0; idx < 0x400; idx++, addr++) {
var b = _buffer[0][idx];
if ((b & 0xC0) == 0x40) {
this._write(addr >> 8, addr & 0xff, _buffer[0][idx], 0);
}
}
_refreshing = false;
},
2020-01-03 05:12:34 +00:00
mono: function(on) {
_monoMode = on;
this.refresh();
},
2017-09-23 04:42:57 +00:00
blit: function(mixed) {
if (_dirty.top === 385) { return false; }
var top = _dirty.top;
var bottom = _dirty.bottom;
var left = _dirty.left;
var right = _dirty.right;
2017-09-23 04:42:57 +00:00
if (mixed) {
if (bottom < 320) { return false; }
if (top < 320) { top = 320; }
2017-09-23 04:42:57 +00:00
}
context.putImageData(
_imageData, 0, 0, left, top, right - left, bottom - top
);
_dirty = {
top: 385,
bottom: -1,
left: 561,
right: -1
};
return true;
},
2017-09-24 14:06:57 +00:00
start: function() {
var self = this;
setInterval(function() {
self.blink();
}, 267);
return this._start();
},
end: function() {
return this._end();
},
read: function(page, off) {
return this._read(page, off, 0);
},
write: function(page, off, val) {
return this._write(page, off, val, 0);
},
getState: function() {
return {
page: _page,
2020-01-03 05:12:34 +00:00
mono: _monoMode,
2017-09-23 04:42:57 +00:00
buffer: [
base64_encode(_buffer[0]),
base64_encode(_buffer[1])
]
};
},
setState: function(state) {
_page = state.page;
2020-01-03 05:12:34 +00:00
_monoMode = state.mono;
_buffer[0] = base64_decode(state.buffer[0]);
_buffer[1] = base64_decode(state.buffer[1]);
this.refresh();
2020-09-04 05:39:16 +00:00
},
getText() {
function rowToBase(row) {
var ab = (row >> 3) & 3;
var cd = (row >> 1) & 0x3;
var e = row & 1;
return (cd << 8) | (e << 7) | (ab << 5) | (ab << 3);
}
function mapCharCode(charCode) {
charCode &= 0x7F;
if (charCode < 0x20) {
charCode += 0x40;
}
if (!e && (charCode >= 0x60)) {
charCode -= 0x40;
}
return charCode;
}
var buffer = '', line, charCode;
var row, col, base;
for (row = 0; row < 24; row++) {
base = rowToBase(row);
line = '';
if (e && _80colMode) {
for (col = 0; col < 80; col++) {
charCode = mapCharCode(_buffer[1 - col % 2][base + Math.floor(col / 2)]);
line += String.fromCharCode(charCode);
}
} else {
for (col = 0; col < 40; col++) {
charCode = mapCharCode(_buffer[0][base + col]);
line += String.fromCharCode(charCode);
}
}
line = line.trimRight();
buffer += line + '\n';
}
return buffer;
}
};
}
/****************************************************************************
*
* Hires Graphics
*
***************************************************************************/
2019-03-01 05:21:18 +00:00
export function HiresPage(page, context)
2016-11-22 05:17:34 +00:00
{
var _page = page;
2017-09-23 04:42:57 +00:00
var _imageData;
var _dirty = {
top: 385,
bottom: -1,
left: 561,
right: -1
};
2017-09-23 04:42:57 +00:00
var r4 = [
0, // Black
2, // Dark Blue
4, // Dark Green
6, // Medium Blue
8, // Brown
5, // Gray 1
12, // Light Green
14, // Aqua
1, // Red
3, // Purple
10, // Gray 2
7, // Pink
9, // Orange
11, // Light Blue
13, // Yellow
15 // White
];
var dcolors = [
2020-01-01 20:33:24 +00:00
[ 0, 0, 0], // 0x0 black
[227, 30, 96], // 0x1 deep red
[ 96, 78, 189], // 0x2 dark blue
[255, 68, 253], // 0x3 purple
[ 0, 163, 96], // 0x4 dark green
[156, 156, 156], // 0x5 dark gray
[ 20, 207, 253], // 0x6 medium blue
[208, 195, 255], // 0x7 light blue
[ 96, 114, 3], // 0x8 brown
[255, 106, 60], // 0x9 orange
[156, 156, 156], // 0xa light gray
[255, 160, 208], // 0xb pink
[ 20, 245, 60], // 0xc green
[208, 221, 141], // 0xd yellow
[114, 255, 208], // 0xe aquamarine
[255, 255, 255], // 0xf white
];
var _buffer = [];
var _refreshing = false;
2020-01-03 05:12:34 +00:00
var _monoMode = false;
function _init() {
2017-09-23 04:42:57 +00:00
var idx;
_imageData = context.createImageData(560, 384);
for (idx = 0; idx < 560 * 384 * 4; idx++) {
_imageData.data[idx] = 0xff;
}
_buffer[0] = allocMemPages(0x20);
_buffer[1] = allocMemPages(0x20);
}
function _drawPixel(data, off, color) {
var c0 = color[0], c1 = color[1], c2 = color[2];
2016-11-22 05:17:34 +00:00
data[off + 0] = data[off + 4] = c0;
data[off + 1] = data[off + 5] = c1;
data[off + 2] = data[off + 6] = c2;
2019-11-24 03:05:52 +00:00
var nextOff = off + 560 * 4;
2020-01-03 05:12:34 +00:00
data[nextOff] = data[nextOff + 4] = c0;
data[nextOff + 1] = data[nextOff + 5] = c1;
data[nextOff + 2] = data[nextOff + 6] = c2;
}
function _drawHalfPixel(data, off, color) {
var c0 = color[0], c1 = color[1], c2 = color[2];
data[off + 0] = c0;
data[off + 1] = c1;
data[off + 2] = c2;
2019-11-24 03:05:52 +00:00
var nextOff = off + 560 * 4;
2020-01-03 05:12:34 +00:00
data[nextOff] = c0;
data[nextOff + 1] = c1;
data[nextOff + 2] = c2;
}
2016-11-22 05:17:34 +00:00
//
// 160x192 pixels alternate 3 and 4 base pixels wide
//
function _draw3Pixel(data, off, color) {
var c0 = color[0], c1 = color[1], c2 = color[2];
data[off + 0] = data[off + 4] = data[off + 8] = c0;
data[off + 1] = data[off + 5] = data[off + 9] = c1;
data[off + 2] = data[off + 6] = data[off + 10] = c2;
var nextOff = off + 560 * 4;
data[nextOff] = data[nextOff + 4] = data[nextOff + 8] = c0;
data[nextOff + 1] = data[nextOff + 5] = data[nextOff + 9] = c1;
data[nextOff + 2] = data[nextOff + 6] = data[nextOff + 10] = c2;
}
function _draw4Pixel(data, off, color) {
var c0 = color[0], c1 = color[1], c2 = color[2];
data[off + 0] = data[off + 4] = data[off + 8] = data[off + 12] = c0;
data[off + 1] = data[off + 5] = data[off + 9] = data[off + 13] = c1;
data[off + 2] = data[off + 6] = data[off + 10] = data[off + 14] = c2;
var nextOff = off + 560 * 4;
data[nextOff] = data[nextOff + 4] = data[nextOff + 8] = data[nextOff + 12] = c0;
data[nextOff + 1] = data[nextOff + 5] = data[nextOff + 9] = data[nextOff + 13] = c1;
data[nextOff + 2] = data[nextOff + 6] = data[nextOff + 10] = data[nextOff + 14] = c2;
}
_init();
return {
bank0: function() {
var self = this;
return {
start: function() {
return self._start();
},
end: function() {
return self._end();
},
read: function(page, off) {
return self._read(page, off, 0);
},
write: function(page, off, val) {
return self._write(page, off, val, 0);
}
};
},
bank1: function() {
var self = this;
return {
start: function() {
return self._start();
},
end: function() {
return self._end();
},
read: function(page, off) {
return self._read(page, off, 1);
},
write: function(page, off, val) {
return self._write(page, off, val, 1);
}
};
},
_start: function() { return (0x20 * _page); },
2016-11-28 01:28:49 +00:00
_end: function() { return (0x020 * _page) + 0x1f; },
2016-11-28 01:28:49 +00:00
_read: function(page, off, bank) {
2020-01-02 18:57:26 +00:00
var addr = (page << 8) | off, base = addr & 0x1FFF;
return _buffer[bank][base];
},
2016-11-28 01:28:49 +00:00
_write: function(page, off, val, bank) {
2020-01-02 18:57:26 +00:00
var addr = (page << 8) | off, base = addr & 0x1FFF,
2016-11-22 05:17:34 +00:00
idx, jdx;
2017-09-23 04:42:57 +00:00
if (_buffer[bank][base] == val && !_refreshing) {
return;
2017-09-23 04:42:57 +00:00
}
_buffer[bank][base] = val;
var hbs = val & 0x80;
var col = (base % 0x80) % 0x28,
adj = off - col;
2016-11-22 05:17:34 +00:00
// 000001cd eabab000 -> 000abcde
var ab = (adj & 0x18),
cd = (page & 0x03) << 1,
2016-11-22 05:17:34 +00:00
e = adj >> 7;
var rowa = ab | cd | e,
rowb = base >> 10;
2017-09-23 04:42:57 +00:00
var dx, dy, data = _imageData.data;
if ((rowa < 24) && (col < 40)) {
2017-09-23 04:42:57 +00:00
if (!multiScreen && !hiresMode) {
return;
2017-09-23 04:42:57 +00:00
}
2016-11-22 05:17:34 +00:00
2019-11-24 03:05:52 +00:00
var y = rowa << 4 | rowb << 1;
if (y < _dirty.top) { _dirty.top = y; }
y += 2;
if (y > _dirty.bottom) { _dirty.bottom = y; }
2020-01-01 20:33:24 +00:00
var x = col * 14 - 2;
if (x < _dirty.left) { _dirty.left = x; }
2020-01-01 20:33:24 +00:00
x += 18;
if (x > _dirty.right) { _dirty.right = x; }
2019-11-24 03:05:52 +00:00
dy = rowa << 4 | rowb << 1;
2016-11-22 05:17:34 +00:00
var bz, b0, b1, b2, b3, b4, c, hb;
if (oneSixtyMode && !_monoMode) {
// 1 byte = two pixels, but 3:4 ratio
var c3 = val & 0xf;
var c4 = val >> 4;
dx = col * 2 + (bank ^ 1);
off = dx * 28 + dy * 280 * 4 * 2;
_draw3Pixel(data, off, dcolors[c3]);
_draw4Pixel(data, off + 12, dcolors[c4]);
} else if (doubleHiresMode) {
val &= 0x7f;
// Every 4 bytes is 7 pixels
// 2 bytes per bank
2017-09-23 04:42:57 +00:00
// b0 b1 b2 b3
// c0 c1 c2 c3 c4 c5 c6
// 76543210 76543210 76543210 76543210
// 1111222 2333344 4455556 6667777
2019-11-24 03:05:52 +00:00
var mod = col % 2, mcol = col - mod, baseOff = base - mod;
bz = _buffer[0][baseOff - 1];
b0 = _buffer[1][baseOff];
b1 = _buffer[0][baseOff];
b2 = _buffer[1][baseOff + 1];
b3 = _buffer[0][baseOff + 1];
b4 = _buffer[1][baseOff + 2];
2017-09-23 04:42:57 +00:00
c = [
0,
((b0 & 0x0f) >> 0), // 0
((b0 & 0x70) >> 4) | ((b1 & 0x01) << 3), // 1
((b1 & 0x1e) >> 1), // 2
((b1 & 0x60) >> 5) | ((b2 & 0x03) << 2), // 3
((b2 & 0x3c) >> 2), // 4
((b2 & 0x40) >> 6) | ((b3 & 0x07) << 1), // 5
((b3 & 0x78) >> 3), // 6
0
], // 7
hb = [
0,
b0 & 0x80, // 0
b0 & 0x80, // 1
b1 & 0x80, // 2
b2 & 0x80, // 3
b2 & 0x80, // 4
b3 & 0x80, // 5
b3 & 0x80, // 6
0
]; // 7
2013-12-19 22:28:55 +00:00
if (col > 0) {
c[0] = (bz & 0x78) >> 3;
2016-11-22 05:17:34 +00:00
hb[0] = bz & 0x80;
2013-12-19 22:28:55 +00:00
}
if (col < 39) {
2013-12-19 22:28:55 +00:00
c[8] = b4 & 0x0f;
2016-11-22 05:17:34 +00:00
hb[8] = b4 & 0x80;
}
dx = mcol * 14;
off = dx * 4 + dy * 280 * 4 * 2;
2016-11-22 05:17:34 +00:00
var monoColor = null;
2020-01-03 05:12:34 +00:00
if (_monoMode || monoDHRMode) {
2016-11-22 05:17:34 +00:00
monoColor = whiteCol;
}
2013-12-19 22:28:55 +00:00
for (idx = 1; idx < 8; idx++) {
2016-11-22 05:17:34 +00:00
hbs = hb[idx];
2013-12-19 22:28:55 +00:00
var dcolor = dcolors[r4[c[idx]]];
var bits = c[idx-1] | (c[idx] << 4) | (c[idx+1] << 8);
for (jdx = 0; jdx < 4; jdx++, off += 4) {
2016-11-22 05:17:34 +00:00
if (monoColor) {
2013-12-19 22:28:55 +00:00
if (bits & 0x10) {
2016-11-22 05:17:34 +00:00
_drawHalfPixel(data, off, monoColor);
2013-12-19 22:28:55 +00:00
} else {
_drawHalfPixel(data, off, blackCol);
2013-12-19 22:28:55 +00:00
}
2016-11-22 05:17:34 +00:00
} else if (mixedDHRMode) {
if (hbs) {
_drawHalfPixel(data, off, dcolor);
} else {
if (bits & 0x10) {
_drawHalfPixel(data, off, whiteCol);
} else {
_drawHalfPixel(data, off, blackCol);
}
}
} else if (colorDHRMode) {
_drawHalfPixel(data, off, dcolor);
2017-09-23 04:42:57 +00:00
} else if (
((c[idx] != c[idx - 1]) && (c[idx] != c[idx + 1])) &&
(((bits & 0x1c) == 0x1c) ||
((bits & 0x70) == 0x70) ||
((bits & 0x38) == 0x38))
) {
_drawHalfPixel(data, off, whiteCol);
2017-09-23 04:42:57 +00:00
} else if (
(bits & 0x38) ||
(c[idx] == c[idx + 1]) ||
(c[idx] == c[idx - 1])
) {
2014-07-30 19:01:57 +00:00
_drawHalfPixel(data, off, dcolor);
2017-09-23 04:42:57 +00:00
} else if (bits & 0x28) {
_drawHalfPixel(data, off, dim(dcolor));
2016-11-22 05:17:34 +00:00
} else {
_drawHalfPixel(data, off, blackCol);
}
bits >>= 1;
}
}
2013-12-19 22:28:55 +00:00
if (!_refreshing) {
_refreshing = true;
var bb = bank ? 0 : 1;
for (var rr = addr - 1; rr <= addr + 1; rr++) {
var vv = _buffer[bb][rr - 0x2000 * _page];
this._write(rr >> 8, rr & 0xff, vv, bb);
}
_refreshing = false;
}
} else {
val = _buffer[0][base];
hbs = val & 0x80;
val &= 0x7f;
dx = col * 14 - 2;
2016-11-22 05:17:34 +00:00
b0 = col > 0 ? _buffer[0][base - 1] : 0;
b2 = col < 39 ? _buffer[0][base + 1] : 0;
val |= (b2 & 0x3) << 7;
var v0 = b0 & 0x20, v1 = b0 & 0x40, v2 = val & 0x1,
2016-11-22 05:17:34 +00:00
odd = !(col & 0x1),
color,
oddCol = (hbs ? orangeCol : greenCol),
evenCol = (hbs ? blueCol : violetCol);
off = dx * 4 + dy * 280 * 4 * 2;
2017-09-23 04:42:57 +00:00
2020-01-03 05:12:34 +00:00
monoColor = _monoMode ? whiteCol : null;
2017-09-23 04:42:57 +00:00
for (idx = 0; idx < 9; idx++, off += 8) {
val >>= 1;
2016-11-28 01:28:49 +00:00
if (v1) {
2017-09-23 04:42:57 +00:00
if (monoColor) {
color = monoColor;
2016-11-22 05:17:34 +00:00
} else if (highColorHGRMode) {
color = dcolors[_buffer[1][base] >> 4];
} else if (v0 || v2) {
color = whiteCol;
} else {
color = odd ? oddCol : evenCol;
}
} else {
2017-09-23 04:42:57 +00:00
if (monoColor) {
color = blackCol;
2016-11-22 05:17:34 +00:00
} else if (highColorHGRMode) {
color = dcolors[_buffer[1][base] & 0x0f];
} else if (odd && v2 && v0) {
color = v0 ? dim(evenCol) : evenCol;
} else if (!odd && v0 && v2) {
color = v2 ? dim(oddCol) : oddCol;
} else {
color = blackCol;
}
}
if (dx > -1 && dx < 560) {
_drawPixel(data, off, color);
}
dx += 2;
2016-11-22 05:17:34 +00:00
v0 = v1;
v1 = v2;
v2 = val & 0x01;
odd = !odd;
}
}
}
},
refresh: function() {
var page, off, idx, addr = 0x2000 * _page;
_refreshing = true;
for (idx = 0; idx < 0x2000; idx++, addr++) {
page = addr >> 8;
off = addr & 0xff;
this._write(page, off, _buffer[0][idx], 0);
2017-09-23 04:42:57 +00:00
if (_80colMode) {
this._write(page, off, _buffer[1][idx], 1);
2017-09-23 04:42:57 +00:00
}
}
_refreshing = false;
},
2020-01-03 05:12:34 +00:00
mono: function(on) {
_monoMode = on;
this.refresh();
},
2017-09-23 04:42:57 +00:00
blit: function(mixed) {
if (_dirty.top === 385) { return false; }
var top = _dirty.top;
var bottom = _dirty.bottom;
var left = _dirty.left;
var right = _dirty.right;
2017-09-23 04:42:57 +00:00
if (mixed) {
if (top > 320) { return false; }
if (bottom > 320) { bottom = 320; }
2017-09-23 04:42:57 +00:00
}
context.putImageData(
_imageData, 0, 0, left, top, right - left, bottom - top
);
_dirty = {
top: 385,
bottom: -1,
left: 561,
right: -1
};
return true;
},
2017-09-24 14:06:57 +00:00
start: function() {
return this._start();
},
end: function() {
return this._end();
},
read: function(page, off) {
return this._read(page, off, 0);
},
write: function(page, off, val) {
return this._write(page, off, val, 0);
},
getState: function() {
return {
page: _page,
2020-01-03 05:12:34 +00:00
mono: _monoMode,
2017-09-23 04:42:57 +00:00
buffer: [
base64_encode(_buffer[0]),
base64_encode(_buffer[1])
]
};
},
setState: function(state) {
_page = state.page;
2020-01-03 05:12:34 +00:00
_monoMode = state.mono;
_buffer[0] = base64_decode(state.buffer[0]);
_buffer[1] = base64_decode(state.buffer[1]);
this.refresh();
}
};
}
2019-03-01 05:21:18 +00:00
export function VideoModes(gr, hgr, gr2, hgr2, e) {
var _grs = [gr, gr2];
var _hgrs = [hgr, hgr2];
2020-06-05 19:53:56 +00:00
var _flag = 0;
function _refresh() {
2020-06-05 19:53:56 +00:00
highColorTextMode = !an3 && textMode && !_80colMode;
highColorHGRMode = !an3 && hiresMode && !_80colMode;
doubleHiresMode = !an3 && hiresMode && _80colMode;
oneSixtyMode = _flag == 1 && doubleHiresMode;
mixedDHRMode = _flag == 2 && doubleHiresMode;
monoDHRMode = _flag == 3 && doubleHiresMode;
2020-06-05 19:53:56 +00:00
gr.refresh();
gr2.refresh();
hgr.refresh();
hgr2.refresh();
}
return {
refresh: function() {
_refresh();
},
reset: function() {
textMode = true;
mixedMode = false;
hiresMode = true;
pageMode = 1;
2016-11-22 05:17:34 +00:00
_80colMode = false;
altCharMode = false;
2016-11-22 05:17:34 +00:00
2020-06-05 19:53:56 +00:00
_flag = 0;
an3 = true;
_refresh();
},
text: function(on) {
var old = textMode;
textMode = on;
2016-11-22 05:17:34 +00:00
if (old != on) {
_refresh();
}
},
_80col: function(on) {
2017-09-24 14:06:57 +00:00
if (!e) { return; }
2020-06-05 19:53:56 +00:00
var old = _80colMode;
_80colMode = on;
2016-11-22 05:17:34 +00:00
if (old != on) {
_refresh();
}
},
altchar: function(on) {
2017-09-24 14:06:57 +00:00
if (!e) { return; }
2020-06-05 19:53:56 +00:00
var old = altCharMode;
altCharMode = on;
if (old != on) {
_refresh();
}
},
hires: function(on) {
var old = hiresMode;
hiresMode = on;
2020-06-18 03:31:17 +00:00
if (!on) {
_flag = 0;
}
2016-11-22 05:17:34 +00:00
if (old != on) {
_refresh();
}
},
2020-06-05 19:53:56 +00:00
an3: function(on) {
2017-09-24 14:06:57 +00:00
if (!e) { return; }
2016-11-22 05:17:34 +00:00
2020-06-05 19:53:56 +00:00
var old = an3;
an3 = on;
2016-11-22 05:17:34 +00:00
if (on) {
2020-06-05 19:53:56 +00:00
_flag = ((_flag << 1) | (_80colMode ? 0x0 : 0x1)) & 0x3;
2016-11-22 05:17:34 +00:00
}
if (old != on) {
_refresh();
}
},
doubleHires: function(on) {
this.an3(!on);
},
2016-11-22 05:17:34 +00:00
mixed: function(on) {
var old = mixedMode;
mixedMode = on;
if (old != on) {
_refresh();
}
},
page: function(pageNo) {
var old = pageMode;
pageMode = pageNo;
if (old != pageNo) {
_refresh();
}
},
2019-03-01 05:21:18 +00:00
enhanced: function(on) {
enhanced = on;
},
multiScreen: function(on) {
multiScreen = on;
},
isText: function() {
return textMode;
},
isMixed: function() {
return mixedMode;
},
isPage2: function() {
return pageMode == 2;
},
isHires: function() {
return hiresMode;
},
isDoubleHires: function() {
return doubleHiresMode;
},
is80Col: function() {
return _80colMode;
},
isAltChar: function() {
return altCharMode;
},
blit: function() {
var blitted = false;
2017-09-23 04:42:57 +00:00
if (multiScreen) {
blitted = _grs[0].blit() || blitted;
blitted = _grs[1].blit() || blitted;
blitted = _hgrs[0].blit() || blitted;
blitted = _hgrs[1].blit() || blitted;
} else {
2017-09-23 04:42:57 +00:00
if (hiresMode && !textMode) {
if (mixedMode) {
blitted = _grs[pageMode - 1].blit(true) || blitted;
blitted = _hgrs[pageMode - 1].blit(true) || blitted;
2017-09-23 04:42:57 +00:00
} else {
blitted = _hgrs[pageMode - 1].blit();
2017-09-23 04:42:57 +00:00
}
} else {
blitted = _grs[pageMode - 1].blit();
2017-09-23 04:42:57 +00:00
}
}
return blitted;
},
getState: function() {
return {
grs: [_grs[0].getState(), _grs[1].getState()],
hgrs: [_hgrs[0].getState(), _hgrs[1].getState()],
textMode: textMode,
mixedMode: mixedMode,
hiresMode: hiresMode,
pageMode: pageMode,
_80colMode: _80colMode,
altCharMode: altCharMode,
2020-06-05 19:53:56 +00:00
an3: an3
};
},
setState: function(state) {
textMode = state.textMode;
mixedMode = state.mixedMode;
hiresMode = state.hiresMode;
pageMode = state.pageMode;
_80colMode = state._80colMode;
altCharMode = state.altCharMode;
2020-06-05 19:53:56 +00:00
an3 = state.an3;
_grs[0].setState(state.grs[0]);
_grs[1].setState(state.grs[1]);
_hgrs[0].setState(state.hgrs[0]);
_hgrs[1].setState(state.hgrs[1]);
},
2020-01-03 05:12:34 +00:00
mono: function(on) {
_grs[0].mono(on);
_grs[1].mono(on);
_hgrs[0].mono(on);
_hgrs[1].mono(on);
2020-09-04 05:39:16 +00:00
},
getText() {
return _grs[pageMode - 1].getText();
}
};
}