Synchronet now requires the libarchive development package (e.g. libarchive-dev on Debian-based Linux distros, libarchive.org for more info) to build successfully.

Commit a5c619fc authored by Deucе's avatar Deucе 👌🏾

Use display aspect ratio, not pixel for tracking.

People are used to thinking about the aspect ratio of displays and
used to not thinking at all about that of pixels.  Most of the modes
are simplt 4:3 modes and the text area is the entire window.  The
only oddball here is the Commodore 64 and 128 40-column modes.
Because they have a border around them that's wider on the sides than
the top/bottom, the display aspect ratio is actually narrower than
a normal NTSC screen (6:5).  It seems the PAL version actually has
square pixels, but nobody has asked for a PAL Commodore mode, and I
think that has a different colour palette too so I'm not doing it.

Just to frustrate DigitalMan a bit, the default custom aspect ratio
is now 4:3 (but can be configured).  At present, modifying the custom
mode while *in* the custom modes "works", which no sane person would
want when adjusting the aspect ratio.
parent b8154048
Pipeline #2233 passed with stage
in 8 minutes and 8 seconds
......@@ -26,6 +26,102 @@ static struct graphics_buffer *free_list;
x = 255; \
} while(0)
/*
* Corrects width/height to have the specified aspect ratio
*/
void
aspect_fix(int *x, int *y, int aspect_width, int aspect_height)
{
int bestx, besty;
// Nothing we can do here...
if (aspect_width == 0 || aspect_height == 0)
return;
bestx = lround((double)*y * aspect_width / aspect_height);
besty = lround((double)*x * aspect_height / aspect_width);
if (bestx < *x && bestx > 0)
*x = bestx;
else
*y = besty;
}
/*
* Given a width/height of a source image, adjust it to match the current aspect
* ratio. Will not reduce either number
*/
void
aspect_correct(int *x, int *y, int aspect_width, int aspect_height)
{
int width = *x;
int height;
if (!aspect_height || !aspect_width)
return;
height = lround((double)(width * aspect_height) / aspect_width);
if (height < *y) {
height = *y;
width = lround(((double)height * aspect_width) / aspect_height);
}
*x = width;
*y = height;
}
/*
* Essentially the opposite of the above. Given an output width/height, translates to
* the size of the source image.
*
* Note that this is much trickier as the "source" bitmap may have been integer scaled
* differently in both directions... so what this does is reverse the aspect ratio
* calculation, then find the next lowest even multiple of the mode bitmap size.
*/
void
aspect_reverse(int *x, int *y, int scrnwidth, int scrnheight, int aspect_width, int aspect_height)
{
int width = *x;
int height = *y;
int cheight;
int cwidth;
if (!aspect_height || !aspect_width) {
width = scrnwidth * (*x / scrnwidth);
if (width < scrnwidth)
width = scrnwidth;
height = scrnheight * (*x / scrnheight);
if (height < scrnheight)
height = scrnheight;
return;
}
// First, find the "controlling" dimension... the one that won't be scaled (ie: the one that gets smaller)
cwidth = lround((double)(height * aspect_width) / aspect_height * scrnwidth / scrnheight);
cheight = lround((double)(width * aspect_height) / aspect_width * scrnheight / scrnwidth);
if (cwidth > width) {
// Width controls, so this is simply finding the largest width multiple that fits in the box
width = scrnwidth * (*x / scrnwidth);
if (width < scrnwidth)
width = scrnwidth;
// Now we need to find the largest bitmap height that would fit in the output height
// So, we scale the height to bitmap size...
height = lround((double)*y / ((double)scrnwidth / scrnheight) * ((double)aspect_width / aspect_height));
// And do the same calculation...
height = lround((double)scrnheight * ((double)height / scrnheight));
}
else if (cheight > height) {
// Height controls
height = scrnheight * (*x / scrnheight);
if (height < scrnheight)
height = scrnheight;
width = lround((double)*x / ((double)scrnheight / scrnwidth) * ((double)aspect_height / aspect_width));
width = lround((double)scrnwidth * ((double)width / scrnwidth));
}
*x = width;
*y = height;
}
void
init_r2y(void)
{
......@@ -94,7 +190,7 @@ release_buffer(struct graphics_buffer *buf)
}
struct graphics_buffer *
do_scale(struct rectlist* rect, int xscale, int yscale, double ratio)
do_scale(struct rectlist* rect, int xscale, int yscale, int aspect_width, int aspect_height)
{
struct graphics_buffer* ret1 = get_buffer();
struct graphics_buffer* ret2 = get_buffer();
......@@ -185,14 +281,10 @@ do_scale(struct rectlist* rect, int xscale, int yscale, double ratio)
yscale = tmp;
}
// Calculate the scaled height from ratio...
fheight = lround((double)(rect->rect.height * (yscale)) / ratio);
if (fheight < rect->rect.height * yscale)
fheight = rect->rect.height * yscale;
fwidth = lround((double)(rect->rect.width * (xscale)) * ratio);
if (fwidth < rect->rect.width * xscale)
fwidth = rect->rect.width * xscale;
// Calculate the scaled height from rxscaleatio...
fwidth = rect->rect.width * xscale;
fheight = rect->rect.height * yscale;
aspect_correct(&fwidth, &fheight, aspect_width, aspect_height);
// Now make sure target is big enough...
size_t needsz = fwidth * fheight * sizeof(uint32_t);
......@@ -222,6 +314,7 @@ do_scale(struct rectlist* rect, int xscale, int yscale, double ratio)
#if 0
fprintf(stderr, "Plan:\n"
"start: %dx%d\n"
"pointymulti: %d\n"
"pointy5: %d\n"
"pointy3: %d\n"
......@@ -230,7 +323,7 @@ fprintf(stderr, "Plan:\n"
"Multiply: %dx%d\n"
"hinterp: %zu -> %zu\n"
"winterp: %zu -> %zu\n",
pointymult, pointy5, pointy3, xbr4, xbr2, xmult, ymult, csrc->h * yscale, ratio < 1 ? fheight : csrc->h * yscale, csrc->w * xscale, ratio > 1 ? fwidth : csrc->w * xscale);
csrc->w, csrc->h, pointymult, pointy5, pointy3, xbr4, xbr2, xmult, ymult, csrc->h * yscale, fheight, csrc->w * xscale, fwidth);
#endif
// And scale...
if (ymult != 1 || xmult != 1) {
......
......@@ -15,4 +15,7 @@ struct graphics_buffer * get_buffer(void);
void release_buffer(struct graphics_buffer *);
void init_r2y(void);
struct graphics_buffer * do_scale(struct rectlist* rect, int xscale, int yscale, double ratio);
struct graphics_buffer * do_scale(struct rectlist* rect, int xscale, int yscale, int aspect_width, int aspect_height);
void aspect_correct(int *x, int *y, int aspect_width, int aspect_height);
void aspect_reverse(int *x, int *y, int scrnwidth, int scrnheight, int aspect_width, int aspect_height);
void aspect_fix(int *x, int *y, int aspect_width, int aspect_height);
......@@ -330,65 +330,27 @@ void sdl_flush(void)
static bool
window_can_scale_internally(int winwidth, int winheight)
{
int idealmw;
int idealmh;
int idealh;
int idealw;
// First, figure out if width or height controlls the image size.
idealmw = lround((double)cvstat.scale_numerator / cvstat.scale_denominator * cvstat.scrnwidth);
idealmh = lround((double)cvstat.scale_denominator / cvstat.scale_numerator * cvstat.scrnheight);
idealw = lround(winheight * cvstat.scale_numerator / cvstat.scale_denominator * cvstat.scrnwidth / cvstat.scrnheight);
idealh = lround(winwidth * cvstat.scale_denominator / cvstat.scale_numerator * cvstat.scrnheight / cvstat.scrnwidth);
if (idealw < winwidth) {
// Height controls size...
if (winheight % idealmh == 0)
return true;
}
else {
// Width controls size...
if (winwidth % idealmw == 0)
return true;
}
int fw, fh;
aspect_fix(&winwidth, &winheight, cvstat.aspect_width, cvstat.aspect_height);
fw = winwidth;
fh = winheight;
aspect_reverse(&winwidth, &winheight, cvstat.scrnwidth, cvstat.scrnheight, cvstat.aspect_width, cvstat.aspect_height);
if (fw == winwidth || fh == winheight)
return true;
return false;
}
static void
internal_scaling_factors(int winwidth, int winheight, int *x, int *y)
{
int idealmh;
int idealmw;
int idealh;
int idealw;
// First, figure out if width or height controlls the image size.
idealmw = lround((double)cvstat.scale_numerator / cvstat.scale_denominator * cvstat.scrnwidth);
idealmh = lround((double)cvstat.scale_denominator / cvstat.scale_numerator * cvstat.scrnheight);
idealw = lround((double)winheight * cvstat.scale_numerator / cvstat.scale_denominator * cvstat.scrnwidth / cvstat.scrnheight);
idealh = lround((double)winwidth * cvstat.scale_denominator / cvstat.scale_numerator * cvstat.scrnheight / cvstat.scrnwidth);
if (idealw < winwidth) {
// Height controls size...
if (idealh == winheight) {
idealmw = cvstat.scrnwidth;
*x = lround((double)idealw / idealmw);
*y = lround((double)idealh / idealmh);
return;
}
}
else {
// Width controls size...
if (idealw == winwidth) {
idealmh = cvstat.scrnheight;
*x = lround((double)idealw / idealmw);
*y = lround((double)idealh / idealmh);
return;
}
}
*x = 1;
*y = 1;
aspect_fix(&winwidth, &winheight, cvstat.aspect_width, cvstat.aspect_height);
aspect_reverse(&winwidth, &winheight, cvstat.scrnwidth, cvstat.scrnheight, cvstat.aspect_width, cvstat.aspect_height);
*x = winwidth / cvstat.scrnwidth;
*y = winheight / cvstat.scrnheight;
if (*x < 1 || *x > 14)
*x = 1;
if (*y < 1 || *y > 14)
*y = 1;
}
static int sdl_init_mode(int mode)
......@@ -410,8 +372,9 @@ static int sdl_init_mode(int mode)
pthread_mutex_lock(&vstatlock);
oldcols = cvstat.cols;
bitmap_drv_init_mode(mode, &bitmap_width, &bitmap_height);
vstat.winwidth = ((double)cvstat.winwidth / (cvstat.scrnwidth)) * (vstat.scrnwidth);
vstat.winheight = ((double)cvstat.winheight / (cvstat.scrnheight * cvstat.vmultiplier)) * (vstat.scrnheight * vstat.vmultiplier);
vstat.winwidth = lround((double)cvstat.winwidth / cvstat.scrnwidth * vstat.scrnwidth);
vstat.winheight = lround((double)cvstat.winheight / cvstat.scrnheight * vstat.scrnheight);
aspect_correct(&vstat.winwidth, &cvstat.winheight, cvstat.aspect_width, cvstat.aspect_height);
if (oldcols != vstat.cols) {
if (oldcols == 0) {
if (ciolib_initial_window_width > 0)
......@@ -434,8 +397,6 @@ static int sdl_init_mode(int mode)
vstat.winwidth = vstat.scrnwidth;
if (vstat.winheight < vstat.scrnheight)
vstat.winheight = vstat.scrnheight;
if(vstat.vmultiplier < 1)
vstat.vmultiplier = 1;
cvstat = vstat;
internal_scaling = window_can_scale_internally(vstat.winwidth, vstat.winheight);
......@@ -622,9 +583,11 @@ static void setup_surfaces_locked(void)
{
int flags=0;
SDL_Event ev;
int charwidth, charheight, cols, rows, vmultiplier;
int charwidth, charheight, cols, rows;
SDL_Texture *newtexture;
int idealw;
int idealh;
int idealmw;
int idealmh;
if(fullscreen)
......@@ -641,19 +604,25 @@ static void setup_surfaces_locked(void)
charheight = cvstat.charheight;
cols = cvstat.cols;
rows = cvstat.rows;
vmultiplier = cvstat.vmultiplier;
idealh = lround((long double)cvstat.winwidth * cvstat.scale_denominator / cvstat.scale_numerator * cvstat.scrnheight / cvstat.scrnwidth);
idealmh = lround((long double)cvstat.scrnwidth * cvstat.scale_denominator / cvstat.scale_numerator * cvstat.scrnheight / cvstat.scrnwidth);
internal_scaling = true;
sdl.SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0");
idealmw = cvstat.scrnwidth;
idealmh = cvstat.scrnheight;
aspect_correct(&idealmw, &idealmh, cvstat.aspect_width, cvstat.aspect_height);
idealw = cvstat.winwidth;
idealh = cvstat.winheight;
aspect_fix(&idealw, &idealh, cvstat.aspect_width, cvstat.aspect_height);
internal_scaling = window_can_scale_internally(idealw, idealh);
sdl.SetHint(SDL_HINT_RENDER_SCALE_QUALITY, internal_scaling ? "0" : "2");
if (win == NULL) {
// SDL2: This is slow sometimes... not sure why.
if (sdl.CreateWindowAndRenderer(cvstat.winwidth, idealh, flags, &win, &renderer) == 0) {
if (sdl.CreateWindowAndRenderer(cvstat.winwidth, cvstat.winheight, flags, &win, &renderer) == 0) {
sdl.RenderClear(renderer);
newtexture = sdl.CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, cvstat.winwidth, idealh);
if (internal_scaling)
newtexture = sdl.CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, idealw, idealh);
else
newtexture = sdl.CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, cvstat.scrnwidth, cvstat.scrnheight);
if (texture)
if (texture)
sdl.DestroyTexture(texture);
texture = newtexture;
}
......@@ -663,15 +632,18 @@ static void setup_surfaces_locked(void)
}
}
else {
sdl.SetWindowMinimumSize(win, cvstat.scrnwidth, idealmh);
sdl.SetWindowSize(win, cvstat.winwidth, idealh);
newtexture = sdl.CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, cvstat.winwidth, idealh);
sdl.SetWindowMinimumSize(win, idealmw, idealmh);
sdl.SetWindowSize(win, idealw, idealh);
if (internal_scaling)
newtexture = sdl.CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, idealw, idealh);
else
newtexture = sdl.CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, cvstat.scrnwidth, cvstat.scrnheight);
sdl.RenderClear(renderer);
if (texture)
sdl.DestroyTexture(texture);
texture = newtexture;
}
sdl.SetWindowMinimumSize(win, cvstat.scrnwidth, idealmh);
sdl.SetWindowMinimumSize(win, idealmw, idealmh);
if(win!=NULL) {
bitmap_drv_request_pixels();
......@@ -900,37 +872,50 @@ void sdl_video_event_thread(void *data)
// Don't allow ALT-DIR to change size when maximized...
if ((sdl.GetWindowFlags(win) & SDL_WINDOW_MAXIMIZED) == 0) {
bool wc;
pthread_mutex_lock(&vstatlock);
w = cvstat.winwidth;
h = cvstat.winheight;
aspect_fix(&w, &h, cvstat.aspect_width, cvstat.aspect_height);
if (cvstat.aspect_width == 0 || cvstat.aspect_height == 0)
wc = true;
else
wc = lround((double)(h * cvstat.aspect_width) / cvstat.aspect_height * cvstat.scrnwidth / cvstat.scrnheight) > w;
switch(ev.key.keysym.sym) {
case SDLK_LEFT:
if (w % (cvstat.scrnwidth)) {
w = w - w % cvstat.scrnwidth;
if (wc) {
if (w % (cvstat.scrnwidth)) {
w = w - w % cvstat.scrnwidth;
}
else {
w -= cvstat.scrnwidth;
if (w < cvstat.scrnwidth)
w = cvstat.scrnwidth;
}
}
else {
w -= cvstat.scrnwidth;
if (w < cvstat.scrnwidth)
w = cvstat.scrnwidth;
if (h % (cvstat.scrnheight)) {
h = h - h % cvstat.scrnheight;
}
else {
h -= cvstat.scrnheight;
if (h < cvstat.scrnheight)
h = cvstat.scrnheight;
}
}
break;
case SDLK_RIGHT:
w = (w - w % cvstat.scrnwidth) + cvstat.scrnwidth;
break;
case SDLK_UP:
if (h % (cvstat.scrnheight * cvstat.vmultiplier)) {
h = h - h % (cvstat.scrnheight * cvstat.vmultiplier);
}
else {
h -= (cvstat.scrnheight * cvstat.vmultiplier);
if (h < (cvstat.scrnheight * cvstat.vmultiplier))
h = cvstat.scrnheight * cvstat.vmultiplier;
}
break;
case SDLK_DOWN:
h = (h - h % (cvstat.scrnheight * cvstat.vmultiplier)) + (cvstat.scrnheight * cvstat.vmultiplier);
if (wc)
w = (w - w % cvstat.scrnwidth) + cvstat.scrnwidth;
else
h = (h - h % cvstat.scrnheight) + cvstat.scrnheight;
break;
}
if (wc)
h = INT_MAX;
else
w = INT_MAX;
aspect_fix(&w, &h, cvstat.aspect_width, cvstat.aspect_height);
if (w > 16384 || h > 16384)
beep();
else {
......@@ -1063,8 +1048,13 @@ void sdl_video_event_thread(void *data)
if (strcmp(newh, sdl.GetHint(SDL_HINT_RENDER_SCALE_QUALITY))) {
SDL_Texture *newtexture;
sdl.SetHint(SDL_HINT_RENDER_SCALE_QUALITY, newh);
if (internal_scaling)
newtexture = sdl.CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, cvstat.winwidth, cvstat.winheight);
if (internal_scaling) {
int idealw, idealh;
idealw = cvstat.winwidth;
idealh = cvstat.winheight;
aspect_fix(&idealw, &idealh, cvstat.aspect_width, cvstat.aspect_height);
newtexture = sdl.CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, idealw, idealh);
}
else
newtexture = sdl.CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, cvstat.scrnwidth, cvstat.scrnheight);
sdl.RenderClear(renderer);
......@@ -1118,7 +1108,7 @@ void sdl_video_event_thread(void *data)
int xscale, yscale;
internal_scaling_factors(cvstat.winwidth, cvstat.winheight, &xscale, &yscale);
gb = do_scale(list, xscale, yscale,
(double)cvstat.scale_numerator / cvstat.scale_denominator);
cvstat.aspect_width, cvstat.aspect_height);
src.x = 0;
src.y = 0;
src.w = gb->w;
......@@ -1142,8 +1132,8 @@ void sdl_video_event_thread(void *data)
memcpy(pixels, gb->data, gb->w * ch * sizeof(gb->data[0]));
}
sdl.UnlockTexture(texture);
dst.x = 0;
dst.y = 0;
dst.x = (cvstat.winwidth - gb->w) / 2;
dst.y = (cvstat.winheight - gb->h) / 2;
dst.w = gb->w;
dst.h = gb->h;
release_buffer(gb);
......@@ -1172,21 +1162,12 @@ void sdl_video_event_thread(void *data)
memcpy(pixels, list->data, list->rect.width * ch * sizeof(list->data[0]));
}
sdl.UnlockTexture(texture);
dst.x = 0;
dst.y = 0;
dst.w = cvstat.winwidth;
dst.h = cvstat.winheight;
// Get correct aspect ratio for dst...
idealw = lround((long double)dst.h * cvstat.scale_numerator / cvstat.scale_denominator * cvstat.scrnwidth / cvstat.scrnheight);
idealh = lround((long double)dst.w * cvstat.scale_denominator / cvstat.scale_numerator * cvstat.scrnheight / cvstat.scrnwidth);
if (idealw < cvstat.winwidth) {
dst.x = (cvstat.winwidth - idealw) / 2;
dst.w = idealw;
}
else if(idealh < cvstat.winheight) {
dst.y = (cvstat.winheight - idealh) / 2;
dst.h = idealh;
}
aspect_fix(&dst.w, &dst.h, cvstat.aspect_width, cvstat.aspect_height);
dst.x = (cvstat.winwidth - dst.w) / 2;
dst.y = (cvstat.winheight - dst.h) / 2;
}
sdl.RenderCopy(renderer, texture, &src, &dst);
}
......
......@@ -11,114 +11,118 @@
// TODO: Pretty much all the 1:1 aspect ratios are wrong...
struct video_params vparams[] = {
/* BW 40x25 */
{BW40, GREYSCALE_PALETTE, 40, 25, 14, 15, 16, 8, 1, 7, 0, 833, 1000, 320, 200},
{BW40, GREYSCALE_PALETTE, 40, 25, 14, 15, 16, 8, 7, 0, 4, 3, 320, 200},
/* CO 40x25 */
{C40, COLOUR_PALETTE, 40, 25, 14, 15, 16, 8, 1, 7, 0, 833, 1000, 320, 200},
{C40, COLOUR_PALETTE, 40, 25, 14, 15, 16, 8, 7, 0, 4, 3, 320, 200},
/* BW 80x25 */
{BW80, GREYSCALE_PALETTE, 80, 25, 14, 15, 16, 8, 1, 7, 0, 833, 1000, 640, 400},
{BW80, GREYSCALE_PALETTE, 80, 25, 14, 15, 16, 8, 7, 0, 4, 3, 640, 400},
/* CO 80x25 */
{C80, COLOUR_PALETTE, 80, 25, 14, 15, 16, 8, 1, 7, 0, 833, 1000, 640, 400},
{C80, COLOUR_PALETTE, 80, 25, 14, 15, 16, 8, 7, 0, 4, 3, 640, 400},
/* MONO */
{MONO, 0, 80, 25, 14, 15, 16, 8, 1, 7, 0, 833, 1000, 640, 400},
{MONO, 0, 80, 25, 14, 15, 16, 8, 7, 0, 4, 3, 640, 400},
/* CO 40x14 */
{C40X14, COLOUR_PALETTE, 40, 14, 14, 15, 16, 8, 1, 7, 0, 933, 1000, 320, 224},
{C40X14, COLOUR_PALETTE, 40, 14, 14, 15, 16, 8, 7, 0, 4, 3, 320, 224},
/* CO 40x21 */
{C40X21, COLOUR_PALETTE, 40, 21, 14, 15, 16, 8, 1, 7, 0,1400, 1000, 320, 336},
{C40X21, COLOUR_PALETTE, 40, 21, 14, 15, 16, 8, 7, 0, 4, 3, 320, 336},
/* CO 40x28 */
{C40X28, COLOUR_PALETTE, 40, 28, 12, 13, 14, 8, 1, 7, 0,1633, 1000, 320, 392},
{C40X28, COLOUR_PALETTE, 40, 28, 12, 13, 14, 8, 7, 0, 4, 3, 320, 392},
/* CO 40x43 */
{C40X43, COLOUR_PALETTE, 40, 43, 7, 7, 8, 8, 1, 7, 0,3225, 1000, 320, 774},
{C40X43, COLOUR_PALETTE, 40, 43, 7, 7, 8, 8, 7, 0, 4, 3, 320, 774},
/* CO 40x50 */
{C40X50, COLOUR_PALETTE, 40, 50, 7, 7, 8, 8, 1, 7, 0,1666, 1000, 320, 400},
{C40X50, COLOUR_PALETTE, 40, 50, 7, 7, 8, 8, 7, 0, 4, 3, 320, 400},
/* CO 40x60 */
{C40X60, COLOUR_PALETTE, 40, 60, 7, 7, 8, 8, 1, 7, 0, 2, 1, 320, 480},
{C40X60, COLOUR_PALETTE, 40, 60, 7, 7, 8, 8, 7, 0, 4, 3, 320, 480},
/* CO 80x14 */
{C80X14, COLOUR_PALETTE, 80, 14, 14, 15, 16, 8, 1, 7, 0, 466, 1000, 640, 224},
{C80X14, COLOUR_PALETTE, 80, 14, 14, 15, 16, 8, 7, 0, 4, 3, 640, 224},
/* CO 80x21 */
{C80X21, COLOUR_PALETTE, 80, 21, 14, 15, 16, 8, 1, 7, 0, 698, 1000, 640, 336},
{C80X21, COLOUR_PALETTE, 80, 21, 14, 15, 16, 8, 7, 0, 4, 3, 640, 336},
/* CO 80x28 */
{C80X28, COLOUR_PALETTE, 80, 28, 12, 13, 14, 8, 1, 7, 0, 817, 1000, 640, 392},
{C80X28, COLOUR_PALETTE, 80, 28, 12, 13, 14, 8, 7, 0, 4, 3, 640, 392},
/* CO 80x30 */
{C80X30, COLOUR_PALETTE, 80, 30, 14, 15, 16, 8, 1, 7, 0, 1, 1, 640, 480},
{C80X30, COLOUR_PALETTE, 80, 30, 14, 15, 16, 8, 7, 0, 4, 3, 640, 480},
/* CO 80x43 */
{C80X43, COLOUR_PALETTE, 80, 43, 7, 7, 8, 8, 1, 7, 0, 729, 1000, 640, 350},
{C80X43, COLOUR_PALETTE, 80, 43, 7, 7, 8, 8, 7, 0, 4, 3, 640, 350},
/* EGA 80x25 */
{EGA80X25, COLOUR_PALETTE, 80, 25, 12, 13, 14, 8, 1, 7, 0, 729, 1000, 640, 350},
{EGA80X25, COLOUR_PALETTE, 80, 25, 12, 13, 14, 8, 7, 0, 4, 3, 640, 350},
/* CO 80x50 */
{C80X50, COLOUR_PALETTE, 80, 50, 7, 7, 8, 8, 1, 7, 0, 833, 1000, 640, 400},
{C80X50, COLOUR_PALETTE, 80, 50, 7, 7, 8, 8, 7, 0, 4, 3, 640, 400},
/* CO 80x60 */
{C80X60, COLOUR_PALETTE, 80, 60, 7, 7, 8, 8, 1, 7, 0, 1, 1, 640, 480},
{C80X60, COLOUR_PALETTE, 80, 60, 7, 7, 8, 8, 7, 0, 4, 3, 640, 480},
/* B 40x14 */
{BW40X14, GREYSCALE_PALETTE, 40, 14, 14, 15, 16, 8, 1, 7, 0, 933, 1000, 320, 224},
{BW40X14, GREYSCALE_PALETTE, 40, 14, 14, 15, 16, 8, 7, 0, 4, 3, 320, 224},
/* BW 40x21 */
{BW40X21, GREYSCALE_PALETTE, 40, 21, 14, 15, 16, 8, 1, 7, 0, 14, 10, 320, 336},
{BW40X21, GREYSCALE_PALETTE, 40, 21, 14, 15, 16, 8, 7, 0, 4, 3, 320, 336},
/* BW 40x28 */
{BW40X28, GREYSCALE_PALETTE, 40, 28, 12, 13, 14, 8, 1, 7, 0,1633, 1000, 320, 392},
{BW40X28, GREYSCALE_PALETTE, 40, 28, 12, 13, 14, 8, 7, 0, 4, 3, 320, 392},
/* BW 40x43 */
{BW40X43, GREYSCALE_PALETTE, 40, 43, 7, 7, 14, 8, 1, 7, 0,1458, 1000, 320, 350},
{BW40X43, GREYSCALE_PALETTE, 40, 43, 7, 7, 14, 8, 7, 0, 4, 3, 320, 350},
/* BW 40x50 */
{BW40X50, GREYSCALE_PALETTE, 40, 50, 7, 7, 8, 8, 1, 7, 0,1667, 1000, 320, 400},
{BW40X50, GREYSCALE_PALETTE, 40, 50, 7, 7, 8, 8, 7, 0, 4, 3, 320, 400},
/* BW 40x60 */
{BW40X60, GREYSCALE_PALETTE, 40, 60, 7, 7, 8, 8, 1, 7, 0, 2, 1, 320, 480},
{BW40X60, GREYSCALE_PALETTE, 40, 60, 7, 7, 8, 8, 7, 0, 4, 3, 320, 480},
/* BW 80x14 */
{BW80X14, GREYSCALE_PALETTE, 80, 14, 14, 15, 16, 8, 1, 7, 0, 467, 1000, 640, 224},
{BW80X14, GREYSCALE_PALETTE, 80, 14, 14, 15, 16, 8, 7, 0, 4, 3, 640, 224},
/* BW 80x21 */
{BW80X21, GREYSCALE_PALETTE, 80, 21, 14, 15, 16, 8, 1, 7, 0, 7, 10, 640, 336},
{BW80X21, GREYSCALE_PALETTE, 80, 21, 14, 15, 16, 8, 7, 0, 4, 3, 640, 336},
/* BW 80x28 */
{BW80X28, GREYSCALE_PALETTE, 80, 28, 12, 13, 14, 8, 1, 7, 0, 817, 1000, 640, 392},
{BW80X28, GREYSCALE_PALETTE, 80, 28, 12, 13, 14, 8, 7, 0, 4, 3, 640, 392},
/* BW 80x43 */
{BW80X43, GREYSCALE_PALETTE, 80, 43, 7, 7, 14, 8, 1, 7, 0, 729, 1000, 640, 350},
{BW80X43, GREYSCALE_PALETTE, 80, 43, 7, 7, 14, 8, 7, 0, 4, 3, 640, 350},
/* BW 80x50 */
{BW80X50, GREYSCALE_PALETTE, 80, 50, 7, 7, 8, 8, 1, 7, 0, 833, 1000, 640, 400},
{BW80X50, GREYSCALE_PALETTE, 80, 50, 7, 7, 8, 8, 7, 0, 4, 3, 640, 400},
/* BW 80x60 */
{BW80X60, GREYSCALE_PALETTE, 80, 60, 7, 7, 8, 8, 1, 7, 0, 1, 1, 640, 480},
{BW80X60, GREYSCALE_PALETTE, 80, 60, 7, 7, 8, 8, 7, 0, 4, 3, 640, 480},
/* MONO 80x14 */
{MONO14, MONO_PALETTE, 80, 14, 14, 15, 16, 8, 1, 7, 0, 467, 1000, 640, 224},
{MONO14, MONO_PALETTE, 80, 14, 14, 15, 16, 8, 7, 0, 4, 3, 640, 224},
/* MONO 80x21 */
{MONO21, MONO_PALETTE, 80, 21, 14, 15, 16, 8, 1, 7, 0, 7, 10, 640, 336},
{MONO21, MONO_PALETTE, 80, 21, 14, 15, 16, 8, 7, 0, 4, 3, 640, 336},
/* MONO 80x28 */
{MONO28, MONO_PALETTE, 80, 28, 12, 13, 14, 8, 1, 7, 0, 817, 1000, 640, 392},
{MONO28, MONO_PALETTE, 80, 28, 12, 13, 14, 8, 7, 0, 4, 3, 640, 392},
/* MONO 80x43 */
{MONO43, MONO_PALETTE, 80, 43, 7, 7, 14, 8, 1, 7, 0, 729, 1000, 640, 350},
{MONO43, MONO_PALETTE, 80, 43, 7, 7, 14, 8, 7, 0, 4, 3, 640, 350},
/* MONO 80x50 */
{MONO50, MONO_PALETTE, 80, 50, 7, 7, 8, 8, 1, 7, 0, 833, 1000, 640, 400},
{MONO50, MONO_PALETTE, 80, 50, 7, 7, 8, 8, 7, 0, 4, 3, 640, 400},
/* MONO 80x60 */
{MONO60, MONO_PALETTE, 80, 60, 7, 7, 8, 8, 1, 7, 0, 1, 1, 640, 480},