VGA Fonts
This article is written like a tutorial. Please edit it to have more information and documentation instead of example code and step by step instructions.
So you know how to display characters in text mode, and now you want to do it in graphics mode. It's not complicated, but definitely more complex than writing an ASCII code at a specific offset in memory. You'll have to do it pixel by pixel.
But how do you know what to draw? It's stored in data matrix called bitmap fonts.
Decoding of bitmap fonts
How is a character stored in memory? It's quite simple, 0 encodes background, 1 encodes foreground color. VGA fonts are always 8 bits wide so that each byte contains exactly one row. For letter 'A' in the typical 8x16 font it would be (in binary):
00000000b byte 0 00000000b byte 1 00000000b byte 2 00010000b byte 3 00111000b byte 4 01101100b byte 5 11000110b byte 6 11000110b byte 7 11111110b byte 8 11000110b byte 9 11000110b byte 10 11000110b byte 11 11000110b byte 12 00000000b byte 13 00000000b byte 14 00000000b byte 15
The full bitmap contains bitmaps for every character, thus it's 256*16 bytes, 4096 bytes long. If you want to get the bitmap for a specific character, you have to multiply the ASCII code by 16 (number of rows in a character), add the offset of your bitmap and you're ready to go.
A very simple file format to store these is PC Screen Font, used by the Linux Console. It stores fonts the way described above with a small header. Another solution is the Scalable Screen Font format which comes with an extremely small, free rendering ANSI C library.
By reading bits you can display a symbol on the screen:
#include <gfx.h>
// Video mode information (width, height, etc...)
extern video_mode_t GFX_VIDEO_MODE;
/**
* @note gfx_draw_pixel(x, y, r, g, b) - Procedure for drawing one pixel
* @warning Bits in glyphs must be in the MSB format!
* @param x X of the point on the screen where we will display the symbol
* @param y Y of the point on the screen where we will display the symbol
* @param glyph_data Glyph (array of bytes/words/...)
* @param width_bits Glyph width in bits
* @param height_bits Glyph height in bits
* @param frg_r Red glyph color component
* @param frg_g Green glyph color component
* @param frg_b Blue glyph color component
* @param target_width Target width of the glyph on the screen (the glyph can be stretched/compressed)
* @param target_height Target height of the glyph on the screen
* @param bkg_r Red background color component
* @param bkg_g Green background color component
* @param bkg_b Blue background color component
* @param fill_bkg Determines whether the background under the glyph will be painted over
*/
void gfx_draw_glyph(
int x, int y,
const void* glyph_data,
size_t width_bits, size_t height_bits,
uint8_t frg_r, uint8_t frg_g, uint8_t frg_b,
size_t target_width, size_t target_height,
uint8_t bkg_r, uint8_t bkg_g, uint8_t bkg_b,
bool fill_bkg
) {
const video_mode_t* vm = &GFX_VIDEO_MODE;
if (target_width == 0 || target_height == 0) return;
// src_x = floor(dst_x * src_w / target_width)
// src_y = floor(dst_y * src_h / target_height)
size_t stride = (width_bits + 7) / 8;
if (fill_bkg) {
for (size_t dy = 0; dy < target_height; ++dy) {
size_t src_y = (dy * height_bits) / target_height;
const uint8_t* row = glyph_data + src_y * stride;
for (size_t dx = 0; dx < target_width; ++dx) {
size_t src_x = (dx * width_bits) / target_width;
size_t byte_index = src_x >> 3;
uint8_t mask = 0x80 >> (src_x & 7); // MSB(!)
if (row[byte_index] & mask) {
int px = x + dx;
int py = y + dy;
if ((size_t)px < vm->width && (size_t)py < vm->height) gfx_draw_pixel(px, py, frg_r, frg_g, frg_b);
}
else {
int px = x + dx;
int py = y + dy;
if ((size_t)px < vm->width && (size_t)py < vm->height) gfx_draw_pixel(px, py, bkg_r, bkg_g, bkg_b);
}
}
}
}
else {
for (size_t dy = 0; dy < target_height; ++dy) {
size_t src_y = (dy * height_bits) / target_height;
const uint8_t* row = glyph_data + src_y * stride;
for (size_t dx = 0; dx < target_width; ++dx) {
size_t src_x = (dx * width_bits) / target_width;
size_t byte_index = src_x >> 3;
uint8_t mask = 0x80 >> (src_x & 7); // MSB(!)
if (row[byte_index] & mask) {
int px = x + dx;
int py = y + dy;
if ((size_t)px < vm->width && (size_t)py < vm->height) gfx_draw_pixel(px, py, frg_r, frg_g, frg_b);
}
}
}
}
}
Note: this procedure will display a glyph whose bits are written in MSB format. If your glyph(s) are written in LSB format, then you need to replace 0x80 >> (src_x & 7) with 1 << (src_x & 7)
Now we can define a function for each glyph resolution, for example:
#include <gfx.h>
typedef struct _glyph8x8_t {
int index; // character code
uint8_t data[8]; // glyph(8x8) lines
} glyph8x8_t;
extern const glyph8x8_t GFX_GLYPHS8X8[];
extern const size_t GFX_NUM_GLYPHS8X8;
const glyph8x8_t GLYPH_UNKNOWN8X8 = { .index = 0, .data = {
0b00111100,
0b11001110,
0b11000111,
0b00000111,
0b00001110,
0b00000000,
0b00111000,
0b00111000
}};
void gfx_draw_glyph8x8(
int c,
int x, int y,
uint8_t frg_r, uint8_t frg_g, uint8_t frg_b,
size_t target_width, size_t target_height,
uint8_t bkg_r, uint8_t bkg_g, uint8_t bkg_b,
bool fill_bkg
) {
const glyph8x8_t* glyph = 0;
for (size_t i = 0; i < GFX_NUM_GLYPHS8X8; i++) {
if (GFX_GLYPHS8X8[i].index == c) {
glyph = (const glyph8x8_t*)&GFX_GLYPHS8X8[i];
break;
}
}
if (!glyph) glyph = &GLYPH_UNKNOWN8X8;
gfx_draw_glyph(
x, y,
&glyph->data,
8, // glyph width in bits
8, // glyph height in bits
frg_r, frg_g, frg_b,
target_width, target_height,
bkg_r, bkg_g, bkg_b,
fill_bkg
);
}
How to get fonts?
There're several ways. You can have it in a file on your filesystem. You can hardcode it in an array. But sometimes 4k is so much that you cannot afford, and reading a file is not an option (like in a boot loader), in which case you'll have to read the one used by the card (to display text mode characters) from VGA RAM.
Store it in an array
Easiest way, but increases your code by 4k. There are several sources that provide the entire font in binary or source format so you do not need to manually write it out.
Example:
#include <stdint.h>
typedef struct _glyph8x8_t {
int index; // character code
uint8_t data[8]; // glyph(8x8) lines
} glyph8x8_t;
glyph8x8_t GFX_GLYPHS8X8[] = {
{ 0, {
0b01111110,
0b10000001,
0b10011101,
0b10100001,
0b10100001,
0b10011101,
0b10000001,
0b01111110,
}},
/*
...
*/
{ 'A', {
0b00111000,
0b01101100,
0b11000110,
0b11111110,
0b11000110,
0b11000110,
0b11000110,
0b00000000,
}},
{ 'B', {
0b11111100,
0b01100110,
0b01100110,
0b01111100,
0b01100110,
0b01100110,
0b11111100,
0b00000000,
}},
{ 'C', {
0b00111100,
0b01100110,
0b11000000,
0b11000000,
0b11000000,
0b01100110,
0b00111100,
0b00000000,
}},
/*
...
*/
};
const size_t GFX_NUM_GLYPHS8X8 = sizeof(GFX_GLYPHS8X8) / sizeof(GFX_GLYPHS8X8[0]);
Store it in a file
Most modular way. You can use different fonts if you like. Downside you'll need a working filesystem implementation. As for the file format, I'd suggest the aforementioned PC Screen Font (.psf/.psfu) or Scalable Screen Font (.sfn).
Get the copy stored in the VGA BIOS
It's a standard BIOS call (no need to check it's persistence). If you're still in real mode, it's quite easy to use.
;in: es:di=4k buffer
;out: buffer filled with font
push ds
push es
;ask BIOS to return VGA bitmap fonts
mov ax, 1130h
mov bh, 6
int 10h
;copy charmap
push es
pop ds
pop es
mov si, bp
mov cx, 256*16/4
rep movsd
pop ds
Get from VGA RAM directly
Maybe you're already in protected mode, so cannot access BIOS functions. In this case you can still get the bitmap by programming VGA registers. Be careful that the VGA always reserves space for 8x32 fonts so you will need to trim off the bottom 16 bytes of each character during the copy:
;in: edi=4k buffer
;out: buffer filled with font
;clear even/odd mode
mov dx, 03ceh
mov ax, 5
out dx, ax
;map VGA memory to 0A0000h
mov ax, 0406h
out dx, ax
;set bitplane 2
mov dx, 03c4h
mov ax, 0402h
out dx, ax
;clear even/odd mode (the other way, don't ask why)
mov ax, 0604h
out dx, ax
;copy charmap
mov esi, 0A0000h
mov ecx, 256
;copy 16 bytes to bitmap
@@: movsd
movsd
movsd
movsd
;skip another 16 bytes
add esi, 16
loop @b
;restore VGA state to normal operation
mov ax, 0302h
out dx, ax
mov ax, 0204h
out dx, ax
mov dx, 03ceh
mov ax, 1005h
out dx, ax
mov ax, 0E06h
out dx, ax
It worth mentioning that it has to be done before you switch to VBE graphics mode, because VGA registers are usually not accessible afterwards. This means you won't be able to map the VGA card's font memory to screen memory, and you will read only garbage.
Set VGA fonts
If you're still in text mode and want the VGA card to draw different glyphs, you can set the VGA font. It's worthless in graphics mode (because characters are displayed by your code there, not by the card), I only wrote this section for completeness. Modifying the font bitmaps in VGA RAM isn't hard if you read carefully what's written so far. I'll left it to you as a homework.
Set fonts via BIOS
Hint: check Ralph Brown Interrupt list Int 10/AX=1110h.
Set fonts directly
Hint: use the same code as above, but swap source and destination for "movsd".
Displaying a character
And finally we came to the point where we can display a character. I'll assume you have a putpixel procedure ready. We have to draw 8x16 pixels, one for every bit in the bitmap.
//this is the bitmap font you've loaded
unsigned char *font;
void drawchar(unsigned char c, int x, int y, int fgcolor, int bgcolor)
{
int cx,cy;
int mask[8]={1,2,4,8,16,32,64,128};
unsigned char *glyph=font+(int)c*16;
for(cy=0;cy<16;cy++){
for(cx=0;cx<8;cx++){
putpixel(glyph[cy]&mask[cx]?fgcolor:bgcolor,x+cx,y+cy-12);
}
}
}
The arguments are straightforward. You may wonder why to subtract 12 from y. It's for the baseline: you specify y coordinate as the bottom of the character, not counting the "piggy tail" in a glyph that goes down (like in "p","g","q" etc.). I other words it's the most bottom row of letter "A" that has a bit set.
Although it's mostly useful to erase the screen under the glyph, in some cases it could be bad (eg.: writing on a shiny gradiented button). So here's a slightly modificated version, that uses a transparent background.
//this is the bitmap font you've loaded
unsigned char *font;
void drawchar_transparent(unsigned char c, int x, int y, int fgcolor)
{
int cx,cy;
int mask[8]={1,2,4,8,16,32,64,128};
unsigned char *glyph=font+(int)c*16;
for(cy=0;cy<16;cy++){
for(cx=0;cx<8;cx++){
if(glyph[cy]&mask[cx]) putpixel(fgcolor,x+cx,y+cy-12);
}
}
}
As you can see, we have only foreground color this time, and the putpixel call has a condition: only invoked if the according bit in the bitmap is set.
Of course the code above will be excruciatingly slow (mostly due to doing one pixel at a time, and repeatedly recalculating the address for each pixel within the "putpixel()" function). For much better performance, the code above can be optimised to use boolean operations and a "mask lookup table" instead. For example (for an 8-bpp mode):
//this is the bitmap font you've loaded
unsigned char *font;
void drawchar_8BPP(unsigned char c, int x, int y, int fgcolor, int bgcolor)
{
void *dest;
uint32_t *dest32;
unsigned char *src;
int row;
uint32_t fgcolor32;
uint32_t bgcolor32;
fgcolor32 = fgcolor | (fgcolor << 8) | (fgcolor << 16) | (fgcolor << 24);
bgcolor32 = bgcolor | (bgcolor << 8) | (bgcolor << 16) | (bgcolor << 24);
src = font + c * 16;
dest = videoBuffer + y * bytes_per_line + x;
for(row = 0; row < 16; row++) {
if(*src != 0) {
mask_low = mask_table[*src][0];
mask_high = mask_table[*src][1];
dest32 = dest;
dest32[0] = (bgcolor32 & ~mask_low) | (fgcolor32 & mask_low);
dest32[1] = (bgcolor32 & ~mask_high) | (fgcolor32 & mask_high);
}
src++;
dest += bytes_per_line;
}
}
void drawchar_transparent_8BPP(unsigned char c, int x, int y, int fgcolor)
{
void *dest;
uint32_t *dest32;
unsigned char *src;
int row;
uint32_t fgcolor32;
fgcolor32 = fgcolor | (fgcolor << 8) | (fgcolor << 16) | (fgcolor << 24);
src = font + c * 16;
dest = videoBuffer + y * bytes_per_line + x;
for(row = 0; row < 16; row++) {
if(*src != 0) {
mask_low = mask_table[*src][0];
mask_high = mask_table[*src][1];
dest32 = dest;
dest32[0] = (dest[0] & ~mask_low) | (fgcolor32 & mask_low);
dest32[1] = (dest[1] & ~mask_high) | (fgcolor32 & mask_high);
}
src++;
dest += bytes_per_line;
}
}
In this case the address in display memory is only calculated once (rather than up to 128 times) and 8 pixels are done in parallel (which removes the inner loop completely).
The main downside for this approach is that you need a different function for each "bits per pixel", except that 15-bpp and 16-bpp can use the same code. For worst case (32-bpp) the lookup table costs 8 KiB. The lookup table for 32-bpp can be re-used for 24-bpp, and for 4-bpp no lookup table is needed at all. To support all standard bit depths that VBE is capable of; this gives a total of 5 versions of each "draw character" function (4-bpp, 8-bpp, 15-bpp and 16-bpp, 24-bpp, 32-bpp) and 3 lookup tables (8-bpp, 15-bpp and 16-bpp, 24-bpp and 32-bpp) which cost a combined total of 14 KiB of data if you use static tables (rather than dynamically generating the desired lookup table if/when needed).
Example of pixel drawing procedures for modes with different color depths:
- Indexed RGB (8bpp, mode #13h 320x200, color palette)
- RGB 555 (15bpp)
- RGB 565 (16 bpp)
- RGB 888 (24 bpp)
- ARGB 8888 (32 bpp,
A- reserved)
#include <gfx.h>
extern video_mode_t GFX_VIDEO_MODE;
extern uint8_t* GFX_BUFFER; // framebuffer
const color_t GFX_PALETTE_I8[256]; // color palette
typedef struct _color_t {
uint8_t r;
uint8_t g;
uint8_t b;
} color_t;
static inline uint32_t color_scale_component(uint8_t color_component, uint8_t bits) {
if (bits == 8) return color_component;
return (color_component * 255 + ((1 << bits) - 1) / 2) / ((1 << bits) - 1);
}
uint32_t color_scale_rgb(
uint8_t r,
uint8_t g,
uint8_t b,
uint8_t bits_red,
uint8_t shift_red,
uint8_t bits_green,
uint8_t shift_green,
uint8_t bits_blue,
uint8_t shift_blue
) {
uint32_t scaled_r = color_scale_component(r, bits_red) << shift_red;
uint32_t scaled_g = color_scale_component(g, bits_green) << shift_green;
uint32_t scaled_b = color_scale_component(b, bits_blue) << shift_blue;
return scaled_r | scaled_g | scaled_b;
}
size_t color_find_nearset_indexed(
uint8_t r, uint8_t g, uint8_t b,
const color_t* palette,
size_t num_colors_in_palette
) {
size_t best_distance2 = (size_t)-1;
size_t best_color_index = 0;
for (size_t i = 0; i < num_colors_in_palette; i++) {
int r_distance = r - palette[i].r;
int g_distance = g - palette[i].g;
int b_distance = b - palette[i].b;
size_t distance2 = r_distance * r_distance + g_distance * g_distance + b_distance * b_distance;
if (distance2 <= best_distance2) {
best_distance2 = distance2;
best_color_index = i;
}
}
return best_color_index;
}
/**
* Only for indexed video mode with 8 bits per pixel
*/
void gfx_draw_pixel_i8(int x, int y, uint8_t r, uint8_t g, uint8_t b) {
const video_mode_t* vm = &GFX_VIDEO_MODE;
if (x < 0 || y < 0 || x >= (int)vm->width || y >= (int)vm->height) return;
const size_t offset = y * vm->pitch + x;
GFX_BUFFER[offset] = (uint8_t)color_find_nearset_indexed(
r, g, b,
GFX_PALETTE_I8,
256
);
}
/**
* Only for non-index video mode with 15 or 16 bits per pixel (RGB)
*/
void gfx_draw_pixel555_565(int x, int y, uint8_t r, uint8_t g, uint8_t b) {
const video_mode_t* vm = &GFX_VIDEO_MODE;
if (x < 0 || y < 0 || x >= (int)vm->width || y >= (int)vm->height) return;
uint32_t pixel = color_scale_rgb(
r, g, b,
vm->bits_red, vm->shift_red,
vm->bits_green, vm->shift_green,
vm->bits_blue, vm->shift_blue
);
const size_t offset = y * (vm->pitch >> 1) + x;
((uint16_t*)GFX_BUFFER)[offset] = (uint16_t)pixel;
}
/**
* Only for non-index video mode with 24 bits per pixel (RGB)
*/
void gfx_draw_pixel888(int x, int y, uint8_t r, uint8_t g, uint8_t b) {
const video_mode_t* vm = &GFX_VIDEO_MODE;
if (x < 0 || y < 0 || x >= (int)vm->width || y >= (int)vm->height) return;
uint32_t pixel = color_scale_rgb(
r, g, b,
vm->bits_red, vm->shift_red,
vm->bits_green, vm->shift_green,
vm->bits_blue, vm->shift_blue
);
const size_t offset = y * vm->pitch + (x << 1) + x;
GFX_BUFFER[offset] = pixel & 0xff;
GFX_BUFFER[offset + 1] = (pixel >> 8) & 0xff;
GFX_BUFFER[offset + 2] = (pixel >> 16) & 0xff;
}
/**
* Only for non-index video mode with 32 bits per pixel (ARGB, A - reserved)
*/
void gfx_draw_pixel8888(int x, int y, uint8_t r, uint8_t g, uint8_t b) {
const video_mode_t* vm = &GFX_VIDEO_MODE;
if (x < 0 || y < 0 || x >= (int)vm->width || y >= (int)vm->height) return;
const size_t offset = y * vm->pitch + (x << 2);
GFX_BUFFER[offset] = b;
GFX_BUFFER[offset + 1] = g;
GFX_BUFFER[offset + 2] = r;
GFX_BUFFER[offset + 3] = 0;
}
// mode #13h color palette
const color_t GFX_PALETTE_I8[256] = {
{0, 0, 0},{0, 0, 141},{0, 141, 0},{0, 141, 141},
{141, 0, 0},{141, 0, 141},{141, 70, 0},{141, 141, 141},
{70, 70, 70},{70, 70, 211},{70, 211, 70},{70, 211, 211},
{211, 70, 70},{211, 70, 211},{211, 211, 70},{211, 211, 211},
{0, 0, 0},{13, 13, 13},{26, 26, 26},{44, 44, 44},
{57, 57, 57},{70, 70, 70},{84, 84, 84},{97, 97, 97},
{114, 114, 114},{127, 127, 127},{141, 141, 141},{154, 154, 154},
{167, 167, 167},{185, 185, 185},{198, 198, 198},{211, 211, 211},
{0, 0, 211},{54, 0, 211},{108, 0, 211},{157, 0, 211},
{211, 0, 211},{211, 0, 157},{211, 0, 108},{211, 0, 54},
{211, 0, 0},{211, 54, 0},{211, 108, 0},{211, 157, 0},
{211, 211, 0},{157, 211, 0},{108, 211, 0},{54, 211, 0},
{0, 211, 0},{0, 211, 54},{0, 211, 108},{0, 211, 157},
{0, 211, 211},{0, 157, 211},{0, 108, 211},{0, 54, 211},
{108, 108, 211},{131, 108, 211},{157, 108, 211},{185, 108, 211},
{211, 108, 211},{211, 108, 185},{211, 108, 157},{211, 108, 131},
{211, 108, 108},{211, 131, 108},{211, 157, 108},{211, 185, 108},
{211, 211, 108},{185, 211, 108},{157, 211, 108},{131, 211, 108},
{108, 211, 108},{108, 211, 131},{108, 211, 157},{108, 211, 185},
{108, 211, 211},{108, 185, 211},{108, 157, 211},{108, 131, 211},
{154, 154, 211},{167, 154, 211},{185, 154, 211},{198, 154, 211},
{211, 154, 211},{211, 154, 198},{211, 154, 185},{211, 154, 167},
{211, 154, 154},{211, 167, 154},{211, 185, 154},{211, 198, 154},
{211, 211, 154},{198, 211, 154},{185, 211, 154},{167, 211, 154},
{154, 211, 154},{154, 211, 167},{154, 211, 185},{154, 211, 198},
{154, 211, 211},{154, 198, 211},{154, 185, 211},{154, 167, 211},
{0, 0, 94},{23, 0, 94},{47, 0, 94},{70, 0, 94},{94, 0, 94},{94, 0, 70},{94, 0, 47},{94, 0, 23},
{94, 0, 0},{94, 23, 0},{94, 47, 0},{94, 70, 0},{94, 94, 0},{70, 94, 0},{47, 94, 0},{23, 94, 0},
{0, 94, 0},{0, 94, 23},{0, 94, 47},{0, 94, 70},{0, 94, 94},{0, 70, 94},{0, 47, 94},{0, 23, 94},
{47, 47, 94},{57, 47, 94},{70, 47, 94},{80, 47, 94},{94, 47, 94},{94, 47, 80},{94, 47, 70},{94, 47, 57},
{94, 47, 47},{94, 57, 47},{94, 70, 47},{94, 80, 47},{94, 94, 47},{80, 94, 47},{70, 94, 47},{57, 94, 47},
{47, 94, 47},{47, 94, 57},{47, 94, 70},{47, 94, 80},{47, 94, 94},{47, 80, 94},{47, 70, 94},{47, 57, 94},
{67, 67, 94},{74, 67, 94},{80, 67, 94},{87, 67, 94},{94, 67, 94},{94, 67, 87},{94, 67, 80},{94, 67, 74},
{94, 67, 67},{94, 74, 67},{94, 80, 67},{94, 87, 67},{94, 94, 67},{87, 94, 67},{80, 94, 67},{74, 94, 67},
{67, 94, 67},{67, 94, 74},{67, 94, 80},{67, 94, 87},{67, 94, 94},{67, 87, 94},{67, 80, 94},{67, 74, 94},
{0, 0, 54},{13, 0, 54},{26, 0, 54},{41, 0, 54},{54, 0, 54},{54, 0, 41},{54, 0, 26},{54, 0, 13},
{54, 0, 0},{54, 13, 0},{54, 26, 0},{54, 41, 0},{54, 54, 0},{41, 54, 0},{26, 54, 0},{13, 54, 0},
{0, 54, 0},{0, 54, 13},{0, 54, 26},{0, 54, 41},{0, 54, 54},{0, 41, 54},{0, 26, 54},{0, 13, 54},
{26, 26, 54},{33, 26, 54},{41, 26, 54},{47, 26, 54},{54, 26, 54},{54, 26, 47},{54, 26, 41},{54, 26, 33},
{54, 26, 26},{54, 33, 26},{54, 41, 26},{54, 47, 26},{54, 54, 26},{47, 54, 26},{41, 54, 26},{33, 54, 26},
{26, 54, 26},{26, 54, 33},{26, 54, 41},{26, 54, 47},{26, 54, 54},{26, 47, 54},{26, 41, 54},{26, 33, 54},
{37, 37, 54},{41, 37, 54},{44, 37, 54},{50, 37, 54},{54, 37, 54},{54, 37, 50},{54, 37, 44},{54, 37, 41},
{54, 37, 37},{54, 41, 37},{54, 44, 37},{54, 50, 37},{54, 54, 37},{50, 54, 37},{44, 54, 37},{41, 54, 37},
{37, 54, 37},{37, 54, 41},{37, 54, 44},{37, 54, 50},{37, 54, 54},{37, 50, 54},{37, 44, 54},{37, 41, 54},
{0, 0, 0},{0, 0, 0},{0, 0, 0},{0, 0, 0},{0, 0, 0},{0, 0, 0},{0, 0, 0},{0, 0, 0},
};
Now you can implement functions for reading and copying pixels. Like that:
#include <gfx.h>
extern video_mode_t GFX_VIDEO_MODE;
extern uint8_t* GFX_BUFFER;
void gfx_copy_rectangle555_565(int src_x, int src_y, int dst_x, int dst_y, size_t width, size_t height) {
const video_mode_t* vm = &GFX_VIDEO_MODE;
if (src_x < 0) {
width += src_x;
src_x = 0;
}
if (src_y < 0) {
height += src_y;
src_y = 0;
}
if (dst_x < 0) {
width += dst_x;
src_x -= dst_x;
dst_x = 0;
}
if (dst_y < 0) {
height += dst_y;
src_y -= dst_y;
dst_y = 0;
}
if (src_x + width > vm->width) width = vm->width - src_x;
if (dst_x + width > vm->width) width = vm->width - dst_x;
if (src_y + height > vm->height) height = vm->height - src_y;
if (dst_y + height > vm->height) height = vm->height - dst_y;
if (!width || !height) return;
int row_start, row_end, row_step;
if (dst_y > src_y) {
row_start = height - 1;
row_end = -1;
row_step = -1;
} else {
row_start = 0;
row_end = height;
row_step = 1;
}
for (int row = row_start; row != row_end; row += row_step) {
uint16_t* src_line = (uint16_t*)(GFX_BUFFER + (src_y + row) * vm->pitch) + src_x;
uint16_t* dst_line = (uint16_t*)(GFX_BUFFER + (dst_y + row) * vm->pitch) + dst_x;
for (size_t col = 0; col < width; ++col) dst_line[col] = src_line[col];
}
}
// ...
/**
* Only for non-index video mode with 24 bits per pixel (RGB)
*/
void gfx_read_pixel888(int x, int y, uint8_t* r, uint8_t* g, uint8_t* b) {
const video_mode_t* vm = &GFX_VIDEO_MODE;
uint8_t cr;
uint8_t cg;
uint8_t cb;
if (x < 0 || y < 0 || x >= (int)vm->width || y >= (int)vm->height) cr = cg = cb = 0;
else {
const size_t offset = y * vm->pitch + (x << 1) + x;
cb = GFX_BUFFER[offset];
cg = GFX_BUFFER[offset + 1];
cr = GFX_BUFFER[offset + 2];
}
if (r) *r = cr;
if (g) *g = cg;
if (b) *b = cb;
}
// ...
Then, in order not to suffer with so many functions, when initializing the screen/graphics driver, you can define the desired function as a pointer, for example:
#include <gfx.h>
video_mode_t GFX_VIDEO_MODE;
uint8_t* GFX_BUFFER;
extern void gfx_draw_pixel_i8(int x, int y, uint8_t r, uint8_t g, uint8_t b);
extern void gfx_draw_pixel555_565(int x, int y, uint8_t r, uint8_t g, uint8_t b);
extern void gfx_draw_pixel888(int x, int y, uint8_t r, uint8_t g, uint8_t b);
extern void gfx_draw_pixel8888(int x, int y, uint8_t r, uint8_t g, uint8_t b);
extern void gfx_draw_pixel_stub(int x, int y, uint8_t r, uint8_t g, uint8_t b);
extern void gfx_read_pixel_i8(int x, int y, uint8_t* r, uint8_t* g, uint8_t* b);
extern void gfx_read_pixel555_565(int x, int y, uint8_t* r, uint8_t* g, uint8_t* b);
extern void gfx_read_pixel888(int x, int y, uint8_t* r, uint8_t* g, uint8_t* b);
extern void gfx_read_pixel8888(int x, int y, uint8_t* r, uint8_t* g, uint8_t* b);
extern void gfx_read_pixel_stub(int x, int y, uint8_t* r, uint8_t* g, uint8_t* b);
extern void gfx_copy_rectangle_i8(int src_x, int src_y, int dst_x, int dst_y, size_t width, size_t height);
extern void gfx_copy_rectangle555_565(int src_x, int src_y, int dst_x, int dst_y, size_t width, size_t height);
extern void gfx_copy_rectangle888(int src_x, int src_y, int dst_x, int dst_y, size_t width, size_t height);
extern void gfx_copy_rectangle8888(int src_x, int src_y, int dst_x, int dst_y, size_t width, size_t height);
extern void gfx_copy_rectangle_stub(int src_x, int src_y, int dst_x, int dst_y, size_t width, size_t height);
typedef void (*pgfx_draw_pixel)(int x, int y, uint8_t r, uint8_t g, uint8_t b);
typedef void (*pgfx_read_pixel)(int x, int y, uint8_t* r, uint8_t* g, uint8_t* b);
typedef void (*pgfx_copy_rectangle)(int src_x, int src_y, int dst_x, int dst_y, size_t width, size_t height);
pgfx_draw_pixel gfx_draw_pixel = NULL;
pgfx_read_pixel gfx_read_pixel = NULL;
pgfx_copy_rectangle gfx_copy_rectangle = NULL;
void gfx_init(video_mode_t* video_mode) {
GFX_VIDEO_MODE = *video_mode;
GFX_BUFFER = (uint8_t*)GFX_VIDEO_MODE.framebuffer;
const video_mode_t* vm = &GFX_VIDEO_MODE;
if (vm->depth == 8) {
gfx_draw_pixel = gfx_draw_pixel_i8;
gfx_read_pixel = gfx_read_pixel_i8;
gfx_copy_rectangle = gfx_copy_rectangle_i8;
}
else if (vm->depth == 15 || vm->depth == 16) {
gfx_draw_pixel = gfx_draw_pixel555_565;
gfx_read_pixel = gfx_read_pixel555_565;
gfx_copy_rectangle = gfx_copy_rectangle555_565;
}
else if (vm->depth == 24) {
gfx_draw_pixel = gfx_draw_pixel888;
gfx_read_pixel = gfx_read_pixel888;
gfx_copy_rectangle = gfx_copy_rectangle888;
}
else if (vm->depth == 32) {
gfx_draw_pixel = gfx_draw_pixel8888;
gfx_read_pixel = gfx_read_pixel8888;
gfx_copy_rectangle = gfx_copy_rectangle8888;
}
else {
/*
*_stub functions do nothing, they are needed
as placeholders if we have not defined a known video mode
*/
gfx_draw_pixel = gfx_draw_pixel_stub;
gfx_read_pixel = gfx_read_pixel_stub;
gfx_copy_rectangle = gfx_copy_rectangle_stub;
}
}
Now you can simply use the gfx_draw_pixel pointer as a function (same for gfx_read_pixel, gfx_copy_rectangle)
See Also
- VGA Hardware - if you want to do it on your own
- PC Screen Font - wiki has a tutorial on how to display them
- Scalable Screen Font - comes with a small, free ANSI C rendering library
- TrueType Fonts - a very complex vector font format, partially proprietary
External Links
- UNI-VGA - A free Unicode VGA font (.bdf)
- bdf2c - .bdf font to C source converter.
- LzPsfEditor - "lazy" psf font editor (font can be saved as PSF1/PSF2(?), C array with/without character indexes)
- LzPsfEditor/FontExamples - .psf fonts from /usr/share/consolefonts (PSF1)