[Solved] Best way of drawing text in graphics mode

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
User avatar
Octacone
Member
Member
Posts: 1138
Joined: Fri Aug 07, 2015 6:13 am

[Solved] Best way of drawing text in graphics mode

Post by Octacone »

Hello.

I need some help with drawing text to the screen. At the moment I have a function that can draw one individual character, and it works very well.
I use this to draw one character:

Code: Select all

InternalDrawCharacter(Point position, int characterWidth, int characterArraySize, Color drawColor, int* character);
Now I need a method that would recognize my Input like for example DrawText((Point) {0, 0}, "Abc123Test", ColorRed); and use that function above to draw text at given position. Now I am facing some issues. I have exactly 100 characters. That means 100 different cases inside my switch statement. I don't think that function it is efficient enough. I tried using switch statement but I was getting overlapping issues (if one of the characters inside a string was not the same...etc). I just need a simple but efficient method to recognize my input from my method, draw wanted character, + calculate spacing between characters (character width + 1) and if I type \n I just need to add 16 to my Y.

Let's just begin with:

Code: Select all

void DrawText(string text, Color colorText, Point position)
{
    for(int i = 0; i < stringLength(text); i++)
    {
        DrawCharacterMethodIneed(position, color, text[i]);
    }
}
I hope you all understand what I want.
Last edited by Octacone on Sat Aug 20, 2016 1:11 am, edited 1 time in total.
OS: Basic OS
About: 32 Bit Monolithic Kernel Written in C++ and Assembly, Custom FAT 32 Bootloader
User avatar
BrightLight
Member
Member
Posts: 901
Joined: Sat Dec 27, 2014 9:11 am
Location: Maadi, Cairo, Egypt
Contact:

Re: Best way of drawing text in graphics mode

Post by BrightLight »

If you have a function that draws a character and you need a function to draw a string, that's really easy.
Psuedo-code might look like:

Code: Select all

void draw_string(char* string, int x, int y)
{
	while(string[0] != 0)
	{
		draw_char(string[0], x, y);	// void draw_char(char character, int x, int y);
		x += character_width;
		if(x >= screen_width)
		{
			x = 0;
			y += character_height;
		}
		string++;	// move on
	}
}
That should give you the basic idea. I think you can implement carriage returns and new lines from here. ;)
You know your OS is advanced when you stop using the Intel programming guide as a reference.
User avatar
iansjack
Member
Member
Posts: 4706
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

Re: Best way of drawing text in graphics mode

Post by iansjack »

I don't understand why you need a switch statement within your DrawText function. Surely you just pass the character from your string to the character drawing function?
User avatar
Octacone
Member
Member
Posts: 1138
Joined: Fri Aug 07, 2015 6:13 am

Re: Best way of drawing text in graphics mode

Post by Octacone »

omarrx024 wrote:If you have a function that draws a character and you need a function to draw a string, that's really easy.
Psuedo-code might look like:

Code: Select all

void draw_string(char* string, int x, int y)
{
	while(string[0] != 0)
	{
		draw_char(string[0], x, y);	// void draw_char(char character, int x, int y);
		x += character_width;
		if(x >= screen_width)
		{
			x = 0;
			y += character_height;
		}
		string++;	// move on
	}
}
That should give you the basic idea. I think you can implement carriage returns and new lines from here. ;)
iansjack wrote:I don't understand why you need a switch statement within your DrawText function. Surely you just pass the character from your string to the character drawing function?
How is the function going to know which character to draw?
My characters are bitmap arrays. --->>InternalDrawCharacter is a function that draws that array. You need to pass the character you want to draw + array size + array width.
OS: Basic OS
About: 32 Bit Monolithic Kernel Written in C++ and Assembly, Custom FAT 32 Bootloader
User avatar
BrightLight
Member
Member
Posts: 901
Joined: Sat Dec 27, 2014 9:11 am
Location: Maadi, Cairo, Egypt
Contact:

Re: Best way of drawing text in graphics mode

Post by BrightLight »

Wrong, you have an array of 256 characters (that's how the BIOS font is, and you said in another thread you were using the BIOS's font), each character 8*16, 1 bit per pixel. Each character is therefore 16 bytes in size, and you have 256 characters, numbered from 0 to 255, thus a 4096 byte buffer. The raw font data for a specific character should be (character * 16) + font_data, where font_data is the contiguous buffer of 4096 bytes. Dear god, you are losing all performance by using switch statements; just use an array.
You know your OS is advanced when you stop using the Intel programming guide as a reference.
User avatar
Octacone
Member
Member
Posts: 1138
Joined: Fri Aug 07, 2015 6:13 am

Re: Best way of drawing text in graphics mode

Post by Octacone »

omarrx024 wrote:Wrong, you have an array of 256 characters (that's how the BIOS font is, and you said in another thread you were using the BIOS's font), each character 8*16, 1 bit per pixel. Each character is therefore 16 bytes in size, and you have 256 characters, numbered from 0 to 255, thus a 4096 byte buffer. The raw font data for a specific character should be (character * 16) + font_data, where font_data is the contiguous buffer of 4096 bytes. Dear god, you are losing all performance by using switch statements; just use an array.
Wrong, I don't use BIOS font. I just ported it into bitmap arrays. Every array is different, that I how I made it. Here is an example:

Code: Select all

int Character_S_Width = 7;
int Character_S_ArraySize = 70;
int Character_S[] =
{
	0, 1, 1, 1, 1, 1, 0,
	1, 1, 0, 0, 0, 1, 1,
	1, 1, 0, 0, 0, 1, 1,
	0, 1, 1, 0, 0, 0, 0,
	0, 0, 1, 1, 1, 0, 0,
	0, 0, 0, 0, 1, 1, 0,
	0, 0, 0, 0, 0, 1, 1,
	1, 1, 0, 0, 0, 1, 1,
	1, 1, 0, 0, 0, 1, 1,
	0, 1, 1, 1, 1, 1, 0
};

int Character_UpperDot_Width = 2;
int Character_UpperDot_ArraySize = 20;
int Character_UpperDot[] =
{
	1, 1,  
	1, 1, 
	0, 0,  
	0, 0,   
	0, 0, 
	0, 0, 
	0, 0,
	0, 0, 
	0, 0, 
	0, 0
};
OS: Basic OS
About: 32 Bit Monolithic Kernel Written in C++ and Assembly, Custom FAT 32 Bootloader
Octocontrabass
Member
Member
Posts: 5587
Joined: Mon Mar 25, 2013 7:01 pm

Re: Best way of drawing text in graphics mode

Post by Octocontrabass »

octacone wrote: Wrong, I don't use BIOS font. I just ported it into bitmap arrays. Every array is different, that I how I made it. Here is an example:

Code: Select all

int Character_S_Width = 7;
int Character_S_ArraySize = 70;
int Character_S[] =
{
...
Instead of a single int for character width, why not an array?

Instead of a single int for "array size", why not an array? (For that matter, why not character height?)

Instead of a single array for each character's data, why not an array of pointers to each character array?
User avatar
Octacone
Member
Member
Posts: 1138
Joined: Fri Aug 07, 2015 6:13 am

Re: Best way of drawing text in graphics mode

Post by Octacone »

Octocontrabass wrote:
octacone wrote: Wrong, I don't use BIOS font. I just ported it into bitmap arrays. Every array is different, that I how I made it. Here is an example:

Code: Select all

int Character_S_Width = 7;
int Character_S_ArraySize = 70;
int Character_S[] =
{
...
Instead of a single int for character width, why not an array?

Instead of a single int for "array size", why not an array? (For that matter, why not character height?)

Instead of a single array for each character's data, why not an array of pointers to each character array?
I don't need height since it is not required by my internal function.
What do you mean by that? Why to make an array for each character width/array size?
OS: Basic OS
About: 32 Bit Monolithic Kernel Written in C++ and Assembly, Custom FAT 32 Bootloader
onlyonemac
Member
Member
Posts: 1146
Joined: Sat Mar 01, 2014 2:59 pm

Re: Best way of drawing text in graphics mode

Post by onlyonemac »

octacone wrote:Now I am facing some issues. I have exactly 100 characters. That means 100 different cases inside my switch statement.
Firstly, you will need 256 characters. It doesn't matter if some of those are unused, or if you're treating them differently (e.g. '\n' which will make a newline, rather than printing character 10). Now that you've got 256 characters, directly mapped to the ASCII code, you can do away with the switch statement; create an array of 256 character bitmaps, then just plot the bitmap at the index equal to the ASCII value of the character that you want to draw. So something like this:

Code: Select all

typedef uint8_t[8] character_bitmap; // for an 8x8 character; change as appropriate for your font

character_bitmap font[256];

draw_character(char character, int x, int y)
{
    character_bitmap bitmap = font[character];
    draw_bitmap(bitmap, x, y); // however you're plotting the bitmap
}
When you start writing an OS you do the minimum possible to get the x86 processor in a usable state, then you try to get as far away from it as possible.

Syntax checkup:
Wrong: OS's, IRQ's, zero'ing
Right: OSes, IRQs, zeroing
User avatar
Octacone
Member
Member
Posts: 1138
Joined: Fri Aug 07, 2015 6:13 am

Re: Best way of drawing text in graphics mode

Post by Octacone »

onlyonemac wrote:
octacone wrote:Now I am facing some issues. I have exactly 100 characters. That means 100 different cases inside my switch statement.
Firstly, you will need 256 characters. It doesn't matter if some of those are unused, or if you're treating them differently (e.g. '\n' which will make a newline, rather than printing character 10). Now that you've got 256 characters, directly mapped to the ASCII code, you can do away with the switch statement; create an array of 256 character bitmaps, then just plot the bitmap at the index equal to the ASCII value of the character that you want to draw. So something like this:

Code: Select all

typedef uint8_t[8] character_bitmap; // for an 8x8 character; change as appropriate for your font

character_bitmap font[256];

draw_character(char character, int x, int y)
{
    character_bitmap bitmap = font[character];
    draw_bitmap(bitmap, x, y); // however you're plotting the bitmap
}
I only have 100 characters and I do not need more. So I just need to create 156 empty character with random names? :|
So I need to assign ASCII values to every each one of my 100 characters? I can not do 8x8 or anything like that, my arrays are all different. I also need to pass Array size information and character width information to my internal method in order to draw a character.
OS: Basic OS
About: 32 Bit Monolithic Kernel Written in C++ and Assembly, Custom FAT 32 Bootloader
Octocontrabass
Member
Member
Posts: 5587
Joined: Mon Mar 25, 2013 7:01 pm

Re: Best way of drawing text in graphics mode

Post by Octocontrabass »

It's all about using the right tool for the job. A switch statement requires hundreds of lines of code.

Code: Select all

switch( c )
{
	case 'S':
		InternalDrawCharacter( position, Character_S_Width, Character_S_ArraySize, drawColor, Character_S );
		break;
	// repeat 99 times
}
If you use an array, it's trivial.

Code: Select all

InternalDrawCharacter( position, Character_Width[c], Character_ArraySize[c], drawColor, Character[c] );
You would then define those three arrays like this: (note that this is just an example, you must modify it to be in ASCII order)

Code: Select all

const int Character_Width[128] =
{
	2, 4, 6, 8, ...
}
const int Character_ArraySize[128] =
{
	16, 32, 48, 64, ...
}
const int * const Character[128] =
{
	Character_empty, Character_A, Character_B, ...
}

Keep in mind that this isn't flexible enough for proper internationalization, where you'll need true Unicode support, bidirectional support, and somewhat more complex mapping between code points and glyphs.
User avatar
Octacone
Member
Member
Posts: 1138
Joined: Fri Aug 07, 2015 6:13 am

Re: Best way of drawing text in graphics mode

Post by Octacone »

Octocontrabass wrote:It's all about using the right tool for the job. A switch statement requires hundreds of lines of code.

Code: Select all

switch( c )
{
	case 'S':
		InternalDrawCharacter( position, Character_S_Width, Character_S_ArraySize, drawColor, Character_S );
		break;
	// repeat 99 times
}
If you use an array, it's trivial.

Code: Select all

InternalDrawCharacter( position, Character_Width[c], Character_ArraySize[c], drawColor, Character[c] );
You would then define those three arrays like this: (note that this is just an example, you must modify it to be in ASCII order)

Code: Select all

const int Character_Width[128] =
{
	2, 4, 6, 8, ...
}
const int Character_ArraySize[128] =
{
	16, 32, 48, 64, ...
}
const int * const Character[128] =
{
	Character_empty, Character_A, Character_B, ...
}

Keep in mind that this isn't flexible enough for proper internationalization, where you'll need true Unicode support, bidirectional support, and somewhat more complex mapping between code points and glyphs.
Read this about 11 times. I finally get it. So if I try this I am not going to get any type of errors because of using not static variables inside a struct?
OS: Basic OS
About: 32 Bit Monolithic Kernel Written in C++ and Assembly, Custom FAT 32 Bootloader
User avatar
Roman
Member
Member
Posts: 568
Joined: Thu Mar 27, 2014 3:57 am
Location: Moscow, Russia
Contact:

Re: Best way of drawing text in graphics mode

Post by Roman »

I do it this way:
1. The code sets the "best" graphics mode available, it also saves the pixel format.
2. The PSF (a simple format supported by the Linux kernel) font parser generates the font cache, which is in my case a hash map, where the key is the UTF-8 sequence, and the value is the glyph. A glyph is just a fixed size array of pixels.
3. Upon printing a character the code gets the glyph and copies it to the frame buffer.

Note, that it is not the best way to do these things. Unicode is much more complex, vector fonts are also superior to bitmap ones. But it's enough for my purposes at the moment.
"If you don't fail at least 90 percent of the time, you're not aiming high enough."
- Alan Kay
Octocontrabass
Member
Member
Posts: 5587
Joined: Mon Mar 25, 2013 7:01 pm

Re: Best way of drawing text in graphics mode

Post by Octocontrabass »

octacone wrote:So if I try this I am not going to get any type of errors because of using not static variables inside a struct?
There are no structs here.
User avatar
Octacone
Member
Member
Posts: 1138
Joined: Fri Aug 07, 2015 6:13 am

Re: Best way of drawing text in graphics mode

Post by Octacone »

Roman wrote:I do it this way:
1. The code sets the "best" graphics mode available, it also saves the pixel format.
2. The PSF (a simple format supported by the Linux kernel) font parser generates the font cache, which is in my case a hash map, where the key is the UTF-8 sequence, and the value is the glyph. A glyph is just a fixed size array of pixels.
3. Upon printing a character the code gets the glyph and copies it to the frame buffer.

Note, that it is not the best way to do these things. Unicode is much more complex, vector fonts are also superior to bitmap ones. But it's enough for my purposes at the moment.
You are using one very sophisticated method. You can even generate the font cache. I already have my own way of plotting the characters themselves.
OS: Basic OS
About: 32 Bit Monolithic Kernel Written in C++ and Assembly, Custom FAT 32 Bootloader
Post Reply