1 /*
2 * Nestopia UE
3 *
4 * Copyright (C) 2007-2008 R. Belmont
5 * Copyright (C) 2012-2021 R. Danbrook
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20 * MA 02110-1301, USA.
21 *
22 */
23
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <stdint.h>
27 #include <time.h>
28
29 #include "core/api/NstApiEmulator.hpp"
30 #include "core/api/NstApiInput.hpp"
31 #include "core/api/NstApiVideo.hpp"
32 #include "core/api/NstApiNsf.hpp"
33
34 #include <FL/gl.h>
35
36 #include "nstcommon.h"
37 #include "video.h"
38 #include "config.h"
39 #include "font.h"
40 #include "png.h"
41
42 using namespace Nes::Api;
43
44 static int overscan_offset, overscan_height;
45
46 static uint32_t videobuf[VIDBUF_MAXSIZE]; // Maximum possible internal size
47
48 static Video::RenderState::Filter filter;
49 static Video::RenderState renderstate;
50
51 static dimensions_t basesize, rendersize, screensize;
52 static osdtext_t osdtext;
53
54 extern void *custompalette;
55
56 extern nstpaths_t nstpaths;
57 extern Emulator emulator;
58
59 GLuint gl_texture_id = 0;
60
nst_ogl_init()61 void nst_ogl_init() {
62 // Initialize OpenGL
63 glEnable(GL_TEXTURE_2D);
64
65 glGenTextures(1, &gl_texture_id);
66 glBindTexture(GL_TEXTURE_2D, gl_texture_id);
67
68 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, conf.video_linear_filter ? GL_LINEAR : GL_NEAREST);
69 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
70 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
71 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
72
73 conf.video_fullscreen ?
74 glViewport(screensize.w / 2.0f - rendersize.w / 2.0f, 0, rendersize.w, rendersize.h) :
75 glViewport(0, 0, rendersize.w, rendersize.h);
76
77 glDisable(GL_DEPTH_TEST);
78 glDisable(GL_ALPHA_TEST);
79 glDisable(GL_BLEND);
80 glDisable(GL_LIGHTING);
81 glMatrixMode(GL_PROJECTION);
82 glLoadIdentity();
83 glOrtho(0.0, rendersize.w * conf.video_scale_factor, rendersize.h * conf.video_scale_factor, 0.0, -1.0, 1.0);
84 glMatrixMode(GL_MODELVIEW);
85 glLoadIdentity();
86 }
87
nst_ogl_deinit()88 void nst_ogl_deinit() {
89 // Deinitialize OpenGL
90 if (gl_texture_id) {
91 glDeleteTextures(1, &gl_texture_id);
92 }
93 }
94
nst_ogl_render()95 void nst_ogl_render() {
96 // Render the scene
97 glTexImage2D(GL_TEXTURE_2D,
98 0,
99 GL_RGBA,
100 basesize.w,
101 overscan_height,
102 0,
103 GL_BGRA,
104 GL_UNSIGNED_BYTE,
105 videobuf + overscan_offset);
106
107 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
108 glClear(GL_COLOR_BUFFER_BIT);
109
110 glBegin(GL_QUADS);
111 glTexCoord2f(1.0f, 1.0f);
112 glVertex2f(rendersize.w * conf.video_scale_factor, rendersize.h * conf.video_scale_factor);
113
114 glTexCoord2f(1.0f, 0.0f);
115 glVertex2f(rendersize.w * conf.video_scale_factor, 0.0);
116
117 glTexCoord2f(0.0f, 0.0f);
118 glVertex2f(0.0, 0.0);
119
120 glTexCoord2f(0.0f, 1.0f);
121 glVertex2f(0, rendersize.h * conf.video_scale_factor);
122 glEnd();
123 }
124
nst_video_refresh()125 void nst_video_refresh() {
126 // Refresh the video settings
127 nst_ogl_deinit();
128
129 nst_ogl_init();
130 }
131
video_init()132 void video_init() {
133 // Initialize video
134 nst_ogl_deinit();
135
136 video_set_dimensions();
137 video_set_filter();
138
139 nst_ogl_init();
140
141 if (nst_nsf()) { video_clear_buffer(); video_disp_nsf(); }
142 }
143
video_toggle_filterupdate()144 void video_toggle_filterupdate() {
145 // Clear the filter update flag
146 Video video(emulator);
147 video.ClearFilterUpdateFlag();
148 }
149
video_set_filter()150 void video_set_filter() {
151 // Set the filter
152 Video video(emulator);
153 int scalefactor = conf.video_scale_factor;
154 if (conf.video_scale_factor > 4) { scalefactor = 4; }
155 if ((conf.video_scale_factor > 3) && (conf.video_filter == 5)) { scalefactor = 3; }
156
157 switch(conf.video_filter) {
158 case 0: // None
159 filter = Video::RenderState::FILTER_NONE;
160 break;
161
162 case 1: // NTSC
163 filter = Video::RenderState::FILTER_NTSC;
164 break;
165
166 case 2: // xBR
167 switch (scalefactor) {
168 case 2:
169 filter = Video::RenderState::FILTER_2XBR;
170 break;
171 case 3:
172 filter = Video::RenderState::FILTER_3XBR;
173 break;
174 case 4:
175 filter = Video::RenderState::FILTER_4XBR;
176 break;
177 default:
178 filter = Video::RenderState::FILTER_NONE;
179 break;
180 }
181 break;
182
183 case 3: // scale HQx
184 switch (scalefactor) {
185 case 2:
186 filter = Video::RenderState::FILTER_HQ2X;
187 break;
188 case 3:
189 filter = Video::RenderState::FILTER_HQ3X;
190 break;
191 case 4:
192 filter = Video::RenderState::FILTER_HQ4X;
193 break;
194 default:
195 filter = Video::RenderState::FILTER_NONE;
196 break;
197 }
198 break;
199
200 case 4: // 2xSaI
201 filter = Video::RenderState::FILTER_2XSAI;
202 break;
203
204 case 5: // scale x
205 switch (scalefactor) {
206 case 2:
207 filter = Video::RenderState::FILTER_SCALE2X;
208 break;
209 case 3:
210 filter = Video::RenderState::FILTER_SCALE3X;
211 break;
212 default:
213 filter = Video::RenderState::FILTER_NONE;
214 break;
215 }
216 break;
217 break;
218 }
219
220 // Set the sprite limit: false = enable sprite limit, true = disable sprite limit
221 video.EnableUnlimSprites(conf.video_unlimited_sprites ? true : false);
222
223 // Set Palette options
224 switch (conf.video_palette_mode) {
225 case 0: // YUV
226 video.GetPalette().SetMode(Video::Palette::MODE_YUV);
227 break;
228
229 case 1: // RGB
230 video.GetPalette().SetMode(Video::Palette::MODE_RGB);
231 break;
232
233 case 2: // Custom
234 video.GetPalette().SetMode(Video::Palette::MODE_CUSTOM);
235 video.GetPalette().SetCustom((const unsigned char (*)[3])custompalette, Video::Palette::EXT_PALETTE);
236 break;
237
238 default: break;
239 }
240
241 // Set YUV Decoder/Picture options
242 if (video.GetPalette().GetMode() == Video::Palette::MODE_YUV) {
243 switch (conf.video_decoder) {
244 case 0: // Consumer
245 video.SetDecoder(Video::DECODER_CONSUMER);
246 break;
247
248 case 1: // Canonical
249 video.SetDecoder(Video::DECODER_CANONICAL);
250 break;
251
252 case 2: // Alternative (Canonical with yellow boost)
253 video.SetDecoder(Video::DECODER_ALTERNATIVE);
254 break;
255
256 default: break;
257 }
258 }
259
260 video.SetBrightness(conf.video_brightness);
261 video.SetSaturation(conf.video_saturation);
262 video.SetContrast(conf.video_contrast);
263 video.SetHue(conf.video_hue);
264
265 // Set NTSC options
266 if (conf.video_filter == 1) {
267 switch (conf.video_ntsc_mode) {
268 case 0: // Composite
269 video.SetSaturation(Video::DEFAULT_SATURATION_COMP);
270 video.SetSharpness(Video::DEFAULT_SHARPNESS_COMP);
271 video.SetColorResolution(Video::DEFAULT_COLOR_RESOLUTION_COMP);
272 video.SetColorBleed(Video::DEFAULT_COLOR_BLEED_COMP);
273 video.SetColorArtifacts(Video::DEFAULT_COLOR_ARTIFACTS_COMP);
274 video.SetColorFringing(Video::DEFAULT_COLOR_FRINGING_COMP);
275 break;
276
277 case 1: // S-Video
278 video.SetSaturation(Video::DEFAULT_SATURATION_SVIDEO);
279 video.SetSharpness(Video::DEFAULT_SHARPNESS_SVIDEO);
280 video.SetColorResolution(Video::DEFAULT_COLOR_RESOLUTION_SVIDEO);
281 video.SetColorBleed(Video::DEFAULT_COLOR_BLEED_SVIDEO);
282 video.SetColorArtifacts(Video::DEFAULT_COLOR_ARTIFACTS_SVIDEO);
283 video.SetColorFringing(Video::DEFAULT_COLOR_FRINGING_SVIDEO);
284 break;
285
286 case 2: // RGB
287 video.SetSaturation(Video::DEFAULT_SATURATION_RGB);
288 video.SetSharpness(Video::DEFAULT_SHARPNESS_RGB);
289 video.SetColorResolution(Video::DEFAULT_COLOR_RESOLUTION_RGB);
290 video.SetColorBleed(Video::DEFAULT_COLOR_BLEED_RGB);
291 video.SetColorArtifacts(Video::DEFAULT_COLOR_ARTIFACTS_RGB);
292 video.SetColorFringing(Video::DEFAULT_COLOR_FRINGING_RGB);
293 break;
294
295 case 3: // Monochrome
296 video.SetSaturation(Video::DEFAULT_SATURATION_MONO);
297 video.SetSharpness(Video::DEFAULT_SHARPNESS_MONO);
298 video.SetColorResolution(Video::DEFAULT_COLOR_RESOLUTION_MONO);
299 video.SetColorBleed(Video::DEFAULT_COLOR_BLEED_MONO);
300 video.SetColorArtifacts(Video::DEFAULT_COLOR_ARTIFACTS_MONO);
301 video.SetColorFringing(Video::DEFAULT_COLOR_FRINGING_MONO);
302 break;
303
304 case 4: // Custom
305 video.SetSaturation(conf.video_saturation);
306 video.SetSharpness(conf.video_ntsc_sharpness);
307 video.SetColorResolution(conf.video_ntsc_resolution);
308 video.SetColorBleed(conf.video_ntsc_bleed);
309 video.SetColorArtifacts(conf.video_ntsc_artifacts);
310 video.SetColorFringing(conf.video_ntsc_fringing);
311 break;
312
313 default: break;
314 }
315 }
316
317 // Set xBR options
318 if (conf.video_filter == 2) {
319 video.SetCornerRounding(conf.video_xbr_corner_rounding);
320 video.SetBlend(conf.video_xbr_pixel_blending);
321 }
322
323 // Set up the render state parameters
324 renderstate.filter = filter;
325 renderstate.width = basesize.w;
326 renderstate.height = basesize.h;
327 renderstate.bits.count = 32;
328
329 int e = 1; // Check Endianness
330 if ((int)*((unsigned char *)&e) == 1) { // Little Endian
331 renderstate.bits.mask.r = 0x00ff0000;
332 renderstate.bits.mask.g = 0x0000ff00;
333 renderstate.bits.mask.b = 0x000000ff;
334 }
335 else { // Big Endian
336 renderstate.bits.mask.r = 0x0000ff00;
337 renderstate.bits.mask.g = 0x00ff0000;
338 renderstate.bits.mask.b = 0xff000000;
339 }
340
341 if (NES_FAILED(video.SetRenderState(renderstate))) {
342 fprintf(stderr, "Nestopia core rejected render state\n");
343 exit(1);
344 }
345 }
346
nst_video_get_dimensions_render()347 dimensions_t nst_video_get_dimensions_render() {
348 // Return the dimensions of the rendered video
349 return rendersize;
350 }
351
nst_video_get_dimensions_screen()352 dimensions_t nst_video_get_dimensions_screen() {
353 // Return the dimensions of the screen
354 return screensize;
355 }
356
nst_video_set_dimensions_screen(dimensions_t scrsize)357 void nst_video_set_dimensions_screen(dimensions_t scrsize) {
358 screensize = scrsize;
359 }
360
video_set_dimensions()361 void video_set_dimensions() {
362 // Set up the video dimensions
363 int scalefactor = conf.video_scale_factor;
364 if (conf.video_scale_factor > 4) { scalefactor = 4; }
365 if ((conf.video_scale_factor > 3) && (conf.video_filter == 5)) { scalefactor = 3; }
366 int wscalefactor = conf.video_scale_factor;
367 int tvwidth = nst_pal() ? PAL_TV_WIDTH : TV_WIDTH;
368
369 switch(conf.video_filter) {
370 case 0: // None
371 basesize.w = Video::Output::WIDTH;
372 basesize.h = Video::Output::HEIGHT;
373 conf.video_tv_aspect == true ? rendersize.w = tvwidth * wscalefactor : rendersize.w = basesize.w * wscalefactor;
374 rendersize.h = basesize.h * wscalefactor;
375 overscan_offset = basesize.w * OVERSCAN_TOP;
376 overscan_height = basesize.h - OVERSCAN_TOP - OVERSCAN_BOTTOM;
377 break;
378
379 case 1: // NTSC
380 basesize.w = Video::Output::NTSC_WIDTH;
381 rendersize.w = (basesize.w / 2) * wscalefactor;
382 basesize.h = Video::Output::HEIGHT;
383 rendersize.h = basesize.h * wscalefactor;
384 overscan_offset = basesize.w * OVERSCAN_TOP;
385 overscan_height = basesize.h - OVERSCAN_TOP - OVERSCAN_BOTTOM;
386 break;
387
388 case 2: // xBR
389 case 3: // HqX
390 case 5: // ScaleX
391 basesize.w = Video::Output::WIDTH * scalefactor;
392 basesize.h = Video::Output::HEIGHT * scalefactor;
393 conf.video_tv_aspect == true ? rendersize.w = tvwidth * wscalefactor : rendersize.w = Video::Output::WIDTH * wscalefactor;
394 rendersize.h = Video::Output::HEIGHT * wscalefactor;
395 overscan_offset = basesize.w * OVERSCAN_TOP * scalefactor;
396 overscan_height = basesize.h - (OVERSCAN_TOP + OVERSCAN_BOTTOM) * scalefactor;
397 break;
398
399 case 4: // 2xSaI
400 basesize.w = Video::Output::WIDTH * 2;
401 basesize.h = Video::Output::HEIGHT * 2;
402 conf.video_tv_aspect == true ? rendersize.w = tvwidth * wscalefactor : rendersize.w = Video::Output::WIDTH * wscalefactor;
403 rendersize.h = Video::Output::HEIGHT * wscalefactor;
404 overscan_offset = basesize.w * OVERSCAN_TOP * 2;
405 overscan_height = basesize.h - (OVERSCAN_TOP + OVERSCAN_BOTTOM) * 2;
406 break;
407 }
408
409 if (!conf.video_unmask_overscan) {
410 rendersize.h -= (OVERSCAN_TOP + OVERSCAN_BOTTOM) * scalefactor;
411 }
412 else { overscan_offset = 0; overscan_height = basesize.h; }
413
414 // Calculate the aspect from the height because it's smaller
415 if (conf.video_fullscreen) {
416 float aspect = (float)screensize.h / (float)rendersize.h;
417 rendersize.w *= aspect;
418 rendersize.h *= aspect;
419 }
420 }
421
video_lock_screen(void * & ptr)422 long video_lock_screen(void*& ptr) {
423 ptr = videobuf;
424 return basesize.w * 4;
425 }
426
video_unlock_screen(void *)427 void video_unlock_screen(void*) {
428 int xscale = renderstate.width / Video::Output::WIDTH;;
429 int yscale = renderstate.height / Video::Output::HEIGHT;
430
431 if (osdtext.drawtext) {
432 nst_video_text_draw(osdtext.textbuf, osdtext.xpos * xscale, osdtext.ypos * yscale, osdtext.bg);
433 osdtext.drawtext--;
434 }
435
436 if (osdtext.drawtime) {
437 nst_video_text_draw(osdtext.timebuf, 208 * xscale, 218 * yscale, false);
438 }
439 }
440
video_screenshot_flip(unsigned char * pixels,int width,int height,int bytes)441 void video_screenshot_flip(unsigned char *pixels, int width, int height, int bytes) {
442 // Flip the pixels
443 int rowsize = width * bytes;
444 unsigned char *row = (unsigned char*)malloc(rowsize);
445 unsigned char *low = pixels;
446 unsigned char *high = &pixels[(height - 1) * rowsize];
447
448 for (; low < high; low += rowsize, high -= rowsize) {
449 memcpy(row, low, rowsize);
450 memcpy(low, high, rowsize);
451 memcpy(high, row, rowsize);
452 }
453 free(row);
454 }
455
video_screenshot(const char * filename)456 void video_screenshot(const char* filename) {
457 // Take a screenshot in .png format
458 unsigned char *pixels;
459 pixels = (unsigned char*)malloc(sizeof(unsigned char) * rendersize.w * rendersize.h * 4);
460
461 // Read the pixels and flip them vertically
462 glReadPixels(0, 0, rendersize.w, rendersize.h, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
463 video_screenshot_flip(pixels, rendersize.w, rendersize.h, 4);
464
465 if (filename == NULL) {
466 // Set the filename
467 char sshotpath[512];
468 snprintf(sshotpath, sizeof(sshotpath), "%sscreenshots/%s-%ld-%d.png", nstpaths.nstdir, nstpaths.gamename, time(NULL), rand() % 899 + 100);
469
470 // Save the file
471 lodepng_encode32_file(sshotpath, (const unsigned char*)pixels, rendersize.w, rendersize.h);
472 fprintf(stderr, "Screenshot: %s\n", sshotpath);
473 }
474 else {
475 lodepng_encode32_file(filename, (const unsigned char*)pixels, rendersize.w, rendersize.h);
476 }
477
478 free(pixels);
479 }
480
video_clear_buffer()481 void video_clear_buffer() {
482 // Write black to the video buffer
483 memset(videobuf, 0x00000000, VIDBUF_MAXSIZE);
484 }
485
video_disp_nsf()486 void video_disp_nsf() {
487 // Display NSF text
488 Nsf nsf(emulator);
489
490 int xscale = renderstate.width / Video::Output::WIDTH;;
491 int yscale = renderstate.height / Video::Output::HEIGHT;;
492
493 nst_video_text_draw(nsf.GetName(), 4 * xscale, 16 * yscale, false);
494 nst_video_text_draw(nsf.GetArtist(), 4 * xscale, 28 * yscale, false);
495 nst_video_text_draw(nsf.GetCopyright(), 4 * xscale, 40 * yscale, false);
496
497 char currentsong[10];
498 snprintf(currentsong, sizeof(currentsong), "%d / %d", nsf.GetCurrentSong() +1, nsf.GetNumSongs());
499 nst_video_text_draw(currentsong, 4 * xscale, 52 * yscale, false);
500
501 nst_ogl_render();
502 }
503
nst_video_print(const char * text,int xpos,int ypos,int seconds,bool bg)504 void nst_video_print(const char *text, int xpos, int ypos, int seconds, bool bg) {
505 snprintf(osdtext.textbuf, sizeof(osdtext.textbuf), "%s", text);
506 osdtext.xpos = xpos;
507 osdtext.ypos = ypos;
508 osdtext.drawtext = seconds * nst_pal() ? 50 : 60;
509 osdtext.bg = bg;
510 }
511
nst_video_print_time(const char * timebuf,bool drawtime)512 void nst_video_print_time(const char *timebuf, bool drawtime) {
513 snprintf(osdtext.timebuf, sizeof(osdtext.timebuf), "%s", timebuf);
514 osdtext.drawtime = drawtime;
515 }
516
nst_video_text_draw(const char * text,int xpos,int ypos,bool bg)517 void nst_video_text_draw(const char *text, int xpos, int ypos, bool bg) {
518 // Draw text on screen
519 uint32_t w = 0xc0c0c0c0; // "White", actually Grey
520 uint32_t b = 0x00000000; // Black
521 uint32_t g = 0x00358570; // Nestopia UE Green
522 uint32_t d = 0x00255f65; // Nestopia UE Dark Green
523
524 int numchars = strlen(text);
525
526 int letterypos;
527 int letterxpos;
528 int letternum = 0;
529
530 if (bg) { // Draw background borders
531 for (int i = 0; i < numchars * 8; i++) { // Rows above and below
532 videobuf[(xpos + i) + ((ypos - 1) * renderstate.width)] = g;
533 videobuf[(xpos + i) + ((ypos + 8) * renderstate.width)] = g;
534 }
535 for (int i = 0; i < 8; i++) { // Columns on both sides
536 videobuf[(xpos - 1) + ((ypos + i) * renderstate.width)] = g;
537 videobuf[(xpos + (numchars * 8)) + ((ypos + i) * renderstate.width)] = g;
538 }
539 }
540
541 for (int tpos = 0; tpos < (8 * numchars); tpos+=8) {
542 nst_video_text_match(text, &letterxpos, &letterypos, letternum);
543 for (int row = 0; row < 8; row++) { // Draw Rows
544 for (int col = 0; col < 8; col++) { // Draw Columns
545 switch (nesfont[row + letterypos][col + letterxpos]) {
546 case '.':
547 videobuf[xpos + ((ypos + row) * renderstate.width) + (col + tpos)] = w;
548 break;
549
550 case '+':
551 videobuf[xpos + ((ypos + row) * renderstate.width) + (col + tpos)] = g;
552 break;
553
554 default:
555 if (bg) { videobuf[xpos + ((ypos + row) * renderstate.width) + (col + tpos)] = d; }
556 break;
557 }
558 }
559 }
560 letternum++;
561 }
562 }
563
nst_video_text_match(const char * text,int * xpos,int * ypos,int strpos)564 void nst_video_text_match(const char *text, int *xpos, int *ypos, int strpos) {
565 // Match letters to draw on screen
566 switch (text[strpos]) {
567 case ' ': *xpos = 0; *ypos = 0; break;
568 case '!': *xpos = 8; *ypos = 0; break;
569 case '"': *xpos = 16; *ypos = 0; break;
570 case '#': *xpos = 24; *ypos = 0; break;
571 case '$': *xpos = 32; *ypos = 0; break;
572 case '%': *xpos = 40; *ypos = 0; break;
573 case '&': *xpos = 48; *ypos = 0; break;
574 case '\'': *xpos = 56; *ypos = 0; break;
575 case '(': *xpos = 64; *ypos = 0; break;
576 case ')': *xpos = 72; *ypos = 0; break;
577 case '*': *xpos = 80; *ypos = 0; break;
578 case '+': *xpos = 88; *ypos = 0; break;
579 case ',': *xpos = 96; *ypos = 0; break;
580 case '-': *xpos = 104; *ypos = 0; break;
581 case '.': *xpos = 112; *ypos = 0; break;
582 case '/': *xpos = 120; *ypos = 0; break;
583 case '0': *xpos = 0; *ypos = 8; break;
584 case '1': *xpos = 8; *ypos = 8; break;
585 case '2': *xpos = 16; *ypos = 8; break;
586 case '3': *xpos = 24; *ypos = 8; break;
587 case '4': *xpos = 32; *ypos = 8; break;
588 case '5': *xpos = 40; *ypos = 8; break;
589 case '6': *xpos = 48; *ypos = 8; break;
590 case '7': *xpos = 56; *ypos = 8; break;
591 case '8': *xpos = 64; *ypos = 8; break;
592 case '9': *xpos = 72; *ypos = 8; break;
593 case ':': *xpos = 80; *ypos = 8; break;
594 case ';': *xpos = 88; *ypos = 8; break;
595 case '<': *xpos = 96; *ypos = 8; break;
596 case '=': *xpos = 104; *ypos = 8; break;
597 case '>': *xpos = 112; *ypos = 8; break;
598 case '?': *xpos = 120; *ypos = 8; break;
599 case '@': *xpos = 0; *ypos = 16; break;
600 case 'A': *xpos = 8; *ypos = 16; break;
601 case 'B': *xpos = 16; *ypos = 16; break;
602 case 'C': *xpos = 24; *ypos = 16; break;
603 case 'D': *xpos = 32; *ypos = 16; break;
604 case 'E': *xpos = 40; *ypos = 16; break;
605 case 'F': *xpos = 48; *ypos = 16; break;
606 case 'G': *xpos = 56; *ypos = 16; break;
607 case 'H': *xpos = 64; *ypos = 16; break;
608 case 'I': *xpos = 72; *ypos = 16; break;
609 case 'J': *xpos = 80; *ypos = 16; break;
610 case 'K': *xpos = 88; *ypos = 16; break;
611 case 'L': *xpos = 96; *ypos = 16; break;
612 case 'M': *xpos = 104; *ypos = 16; break;
613 case 'N': *xpos = 112; *ypos = 16; break;
614 case 'O': *xpos = 120; *ypos = 16; break;
615 case 'P': *xpos = 0; *ypos = 24; break;
616 case 'Q': *xpos = 8; *ypos = 24; break;
617 case 'R': *xpos = 16; *ypos = 24; break;
618 case 'S': *xpos = 24; *ypos = 24; break;
619 case 'T': *xpos = 32; *ypos = 24; break;
620 case 'U': *xpos = 40; *ypos = 24; break;
621 case 'V': *xpos = 48; *ypos = 24; break;
622 case 'W': *xpos = 56; *ypos = 24; break;
623 case 'X': *xpos = 64; *ypos = 24; break;
624 case 'Y': *xpos = 72; *ypos = 24; break;
625 case 'Z': *xpos = 80; *ypos = 24; break;
626 case '[': *xpos = 88; *ypos = 24; break;
627 case '\\': *xpos = 96; *ypos = 24; break;
628 case ']': *xpos = 104; *ypos = 24; break;
629 case '^': *xpos = 112; *ypos = 24; break;
630 case '_': *xpos = 120; *ypos = 24; break;
631 case '`': *xpos = 0; *ypos = 32; break;
632 case 'a': *xpos = 8; *ypos = 32; break;
633 case 'b': *xpos = 16; *ypos = 32; break;
634 case 'c': *xpos = 24; *ypos = 32; break;
635 case 'd': *xpos = 32; *ypos = 32; break;
636 case 'e': *xpos = 40; *ypos = 32; break;
637 case 'f': *xpos = 48; *ypos = 32; break;
638 case 'g': *xpos = 56; *ypos = 32; break;
639 case 'h': *xpos = 64; *ypos = 32; break;
640 case 'i': *xpos = 72; *ypos = 32; break;
641 case 'j': *xpos = 80; *ypos = 32; break;
642 case 'k': *xpos = 88; *ypos = 32; break;
643 case 'l': *xpos = 96; *ypos = 32; break;
644 case 'm': *xpos = 104; *ypos = 32; break;
645 case 'n': *xpos = 112; *ypos = 32; break;
646 case 'o': *xpos = 120; *ypos = 32; break;
647 case 'p': *xpos = 0; *ypos = 40; break;
648 case 'q': *xpos = 8; *ypos = 40; break;
649 case 'r': *xpos = 16; *ypos = 40; break;
650 case 's': *xpos = 24; *ypos = 40; break;
651 case 't': *xpos = 32; *ypos = 40; break;
652 case 'u': *xpos = 40; *ypos = 40; break;
653 case 'v': *xpos = 48; *ypos = 40; break;
654 case 'w': *xpos = 56; *ypos = 40; break;
655 case 'x': *xpos = 64; *ypos = 40; break;
656 case 'y': *xpos = 72; *ypos = 40; break;
657 case 'z': *xpos = 80; *ypos = 40; break;
658 case '{': *xpos = 88; *ypos = 40; break;
659 case '|': *xpos = 96; *ypos = 40; break;
660 case '}': *xpos = 104; *ypos = 40; break;
661 case '~': *xpos = 112; *ypos = 40; break;
662 //case ' ': *xpos = 120; *ypos = 40; break; // Triangle
663 default: *xpos = 0; *ypos = 0; break;
664 }
665 }
666