-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathZXScreen.js
More file actions
265 lines (223 loc) · 8.17 KB
/
ZXScreen.js
File metadata and controls
265 lines (223 loc) · 8.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
///////////////////////////////////////////////////////////////////////////////
/// @file ZXScreen.js
///
/// @brief Screen abstraction for the MinZX 48K Spectrum emulator
///
/// @author David Crespo Tascon
///
/// @copyright (c) David Crespo Tascon
/// This code is released under the MIT license,
/// a copy of which is available in the associated LICENSE file,
/// or at http://opensource.org/licenses/MIT
///////////////////////////////////////////////////////////////////////////////
"use strict";
// Documentation for spectrum screen:
// http://www.breakintoprogram.co.uk/computers/zx-spectrum/screen-memory-layout
class ZXScreen
{
constructor(canvasIdForScreen)
{
// initial border color: white
this.border = 7;
// create canvas and context
this.canvas = document.getElementById(canvasIdForScreen);
this.ctx = this.canvas.getContext('2d');
// create image data for screeen, with given border
const xborder = 32;
const yborder = 24;
this.zxid = new ZXScreenAsImageData(this.ctx, xborder, yborder);
// initialscale
this.setScale(2);
}
getScale() { return this.scale; }
setScale(scale)
{
// scale factor
this.scale = scale;
// resize canvas using screen and scale
this.canvas.width = this.zxid.getWidth() * this.scale;
this.canvas.height = this.zxid.getHeight() * this.scale;
// we want pixels! do not smooth them, please
this.ctx.imageSmoothingEnabled = false;
}
update(mem, flashstate)
{
// copy screen memory
const off = 0x4000;
const scrlen = 6912;
const scr = new Uint8Array(scrlen);
for (let i = 0; i < scrlen; i++)
scr[i] = mem[off + i];
// generate image data from array, border and flash state
this.zxid.putSpectrumImage(scr, this.border, flashstate);
// set identity transform for removing previous scale factor
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
// Draw the image data to the canvas at 1:1 scale
this.ctx.fillStyle = 'black';
this.ctx.fillRect(0, 0, 100, 100);
this.ctx.putImageData(this.zxid.imgdata, 0, 0);
// Draw canvas onto itself using scale factor
this.ctx.scale(this.scale, this.scale);
this.ctx.drawImage(this.canvas, 0, 0);
}
}
// ZX Spectrum colors. Using 192 for non-bright value, 255 for bright value.
const zxcolors = [
[ 0, 0, 0, 255],
[ 0, 0, 192, 255],
[192, 0, 0, 255],
[192, 0, 192, 255],
[ 0, 192, 0, 255],
[ 0, 192, 192, 255],
[192, 192, 0, 255],
[192, 192, 192, 255],
[ 0, 0, 0, 255],
[ 0, 0, 255, 255],
[255, 0, 0, 255],
[255, 0, 255, 255],
[ 0, 255, 0, 255],
[ 0, 255, 255, 255],
[255, 255, 0, 255],
[255, 255, 255, 255]
];
class ZXScreenAsImageData
{
constructor(ctx, xborder, yborder)
{
// if border not present, default to 0
this.xborder = xborder ? xborder : 0;
this.yborder = yborder ? yborder : 0;
// add border to image data dimensions
this.width = 256 + 2 * this.xborder;
this.height = 192 + 2 * this.yborder;
// create image data
this.imgdata = ctx.createImageData(this.width, this.height);
// fill image with white
let bytecnt = this.width * this.height * 4;
for (let i = 0; i < bytecnt; i++)
this.imgdata.data[i] = 255;
}
// accessors fo actual dimensions
getWidth () { return this.width; }
getHeight() { return this.height; }
// color for bitmap bits with 0 (PAPER) value
getAttrColorIndexForBit0(attr)
{
let bri = (attr & 0x40) != 0 ? 0x08 : 0x00;
let rgb = (attr & 0x38) >> 3;
return zxcolors[rgb | bri];
}
// color for bitmap bits with 0 (INK) value
getAttrColorIndexForBit1(attr)
{
let bri = (attr & 0x40) != 0 ? 0x08 : 0x00;
let rgb = (attr & 0x07);
return zxcolors[rgb | bri];
}
// Generate image data for spectrum screen
// - zxscreen: spectrum screen data (6912 btes),
// - border: border color
// - flashinv: indicates if flash attribute is to be inverted now
putSpectrumImage(zxscreen, border, flashinv)
{
// de-interlace zx-screen to a linear bitmap
const linscr = this.zx_row_adjust(zxscreen);
// source and destination indices
let isrc = 0;
let idst = 0;
// shortcut for image data
const data = this.imgdata.data;
// fill all pixels with border color
const bcol = this.getAttrColorIndexForBit1(border & 0x07);
const pixelcnt = this.width * this.height * 4;
for (let i = 0; i < pixelcnt; i++) {
data[idst++] = bcol[0];
data[idst++] = bcol[1];
data[idst++] = bcol[2];
data[idst++] = bcol[3];
}
// calculate where to start for topleft pixel
idst = 0;
idst += 4 * this.yborder * this.width;
idst += 4 * this.xborder
// calculate how much to advance from the right end of a line
// to the left start of the next
const idst_extra = 4 * 2 * this.xborder;
// traverse all 24 rows
for (let row = 0; row < 24; row++)
{
// traverse 8 subrows in row
for (let subrow = 0; subrow < 8; subrow++)
{
// index of attribute for first character in row
let iatt = 6144 + 32 * row;
// traverse 32 bitmap bytes in subrow
for (let x = 0; x < 32; x++)
{
const attr = linscr[iatt++]; // attribute
let byte = linscr[isrc++]; // bitmap byte
// PAPER color (for bits with value 0)
let col0 = this.getAttrColorIndexForBit0(attr);
// PAPER color (for bits with value 0)
let col1 = this.getAttrColorIndexForBit1(attr);
// if attribute has FLASH, and we are in that part of cycle,
// invert (switch PAPER and INK colors)
if (flashinv && (attr & 0x80)) {
let aux = col0; col0 = col1; col1 = aux;
}
// traverse 8 bits in byte
for (let b = 0; b < 8; b++)
{
let bit = 0;
if ((byte & 0x80) != 0)
bit = 1;
byte <<= 1;
// put INK color for 1, PAPER color for 0
if (bit) {
data[idst++] = col1[0]; // r
data[idst++] = col1[1]; // g
data[idst++] = col1[2]; // b
data[idst++] = col1[3]; // a
}
else {
data[idst++] = col0[0]; // r
data[idst++] = col0[1]; // g
data[idst++] = col0[2]; // b
data[idst++] = col0[3]; // a
}
}
}
// advance to next line
idst += idst_extra;
}
}
}
// 'deinterlace' screen rows
zx_row_adjust(src)
{
// enforce screen size
if (src.length != 6912) {
console.log("zx_row_adjust: unexpected data length " + src.length);
return null;
}
// create array for deinterlaced screen
let dst = new Uint8Array(6912);
// traverse all 192 rows
for (let row = 0; row < 192; row++)
{
// bit juggle for calculating spectrum row index
let rzx = ((row & 0x38) >> 3) | ((row & 0x07) << 3) | (row & 0xC0);
let isrc = row * 32;
let idst = rzx * 32;
// copy row
for (let col = 0; col < 32; col++)
dst[idst++] = src[isrc++]
}
// copy attributes
for (let i = 0; i < 768; i++)
{
dst[6144+i] = src[6144+i];
}
return dst;
}
}