Getting started with C programming - More -
Drawing text
There's 3 approaches to drawing text:
- Use a bitmap font, which is just an image with letters on it, and then draw small parts of that image one letter at a time. This is the easiest method and very commonly used in videogames, it's also easy to make custom fonts this way. The biggest downside is that you can't resize this font easily without issues, for example it's fine to scale it to 200% or 300%, but becomes ugly if you try to scale it to 80% or 150%.
Example code for text rendering with bitmap font, click here.
int font_image_width = 256; int font_image_height = 256; int columns = 16; int rows = 16; int cell_width = font_image_width / columns; int cell_height = font_image_height / rows; char* str = "Hello world!"; int length = strlen(str); unsigned char* bytes = (unsigned char*)str; int x = 0; int y = 0; for (int i=0; i<length; i++) { if (bytes[i] == '\n') { // If encountered a new line character, move to a new line. x = 0; y += cell_height; } else { // The glyphs are organized according to ASCII values, so the value of each character in the string is also it's position in an array of glyphs. The position can then be converted to x/y by dividing with the grid width (columns). int cell_x = bytes[i] % columns; int cell_y = bytes[i] / columns; int glyph_x = cell_x * cell_width; int glyph_y = cell_y * cell_height; // Draw the glyph. draw_cropped(x, y, font_image, glyph_x, glyph_y, cell_width, cell_height); x += cell_width; } }
// There's a problem with the code above, which is that a letter like 'M' is usually much more wide than a letter like 'i'. For less ugly looking text, you should create an array with a different glyph_width value for each glyph. You can write the widths manually, or you could generate them automatically by reading the pixel values. You might want to consider just pre-calculating the glyph location entirely, you'll probably need pre-calculated positions anyway if you want to use a non-bitmap font later. Here's an example of how to do that. int font_image_width = 256; int font_image_height = 256; const int columns = 16; const int rows = 16; int cell_width = font_image_width / columns; int cell_height = font_image_height / rows; typedef struct { int x; int y; int width; int height; } Glyph; Glyph glyphs [rows*columns]; for (int i=0; i<rows*columns; i++) { int cell_x = i / columns; int cell_y = i % columns; glyphs[i].x = cell_x * cell_width; glyphs[i].y = cell_y * cell_height; glyphs[i].width = cell_width; glyphs[i].height = cell_height; // Start from the right edge of the glyph, and move to the left. for (int x=glyph_x+cell_width-1; x>=glyph_x; x--) { bool stop = false; // Do the entire height of the glyph. for (int y=glyph_x; y<glyph_x+cell_height; y++) { // y*width+x is the "algorithm" for getting a specific pixel from an image. Pixel px = font_image_pixels[y*font_image_width+x]; if (px.alpha != 0) { stop = true; break; } } if (stop) break; // Did not find any opaque pixels, crop out this column of pixels. glyphs[i].width --; } }
// Now you can just get the glyph instead of calculating the positions. Here's the draw loop again, now using the glyphs array. char* str = "Hello world!"; int length = strlen(str); unsigned char* bytes = (unsigned char*)str; int x = 0; int y = 0; for (int i=0; i<length; i++) { if (bytes[i] == '\n') { x = 0; y += cell_height; } else { Glyph glyph = glyphs[bytes[i]]; draw_cropped(x, y, font_image, glyph.x, glyph.y, glyph.width, glyph.height); x += glyph.width; } }
- Load a font file like .ttf and render the vector shapes into pixels, this is extremely difficult without a library like stb_truetype.
- Create a signed distance field font image and use shaders on the GPU to render it. An SDF font looks like a blurry bitmap font, there's a clever trick to render these blurry characters as sharp text at any size. You could think of this as an advanced combination of the 2 above techniques, it is also often used in videogames. There's a few variations of SDF font rendering, more advanced ones use multiple colors.
Unfortunately I don't know of any pre-made text rendering libraries, it's not too hard to make one yourself using one of the methods above, but it might be tedious if you don't care about that kind of thing.
Rendering methods
There's 2 main ways to put graphics onto the screen: write pixels into a canvas and tell the OS to show it on your window, or use a graphics API (which basically does the same but more efficiently on the GPU and hidden from you) with shaders. Rendering libraries usually use a graphics API.
- SDL which I've used in these examples is a very old (it's still being updated) and widely used library with lots of different features including graphics. I don't even know all of it's features.
- Raylib is a graphics framework that seems to be rising in popularity lately due to it's simplicity and easy drawing features. It has some limitations, for example you can't open multiple windows, and it uses a lot of common function/variable names that you might have wanted to use yourself (this is only a problem in C because you can't wrap a library into any kind of "namespace"). It seems to be specifically designed for videogames rather than generic applications, and kind of forces you into it's own way of doing things rather letting you mold it into your ways.
- The most powerful thing you can do is learn to use a graphics API directly. Interacting with GPUs and writing shaders can be very exciting, but it's also more complicated than the other options because there's a lot of uninteresting busywork (like registering a bunch of objects and attributes) involved when using any of these APIs.
- DirectX or Direct3D is a graphics API made by Microsoft for Windows, there's many versions of it. You might have to use other Microsoft's tools like Visual Studio if you want to use DirectX, I haven't used it so I'm not sure.
- OpenGL is probably the most popular graphics API, it works on Windows and Linux and maybe some other places too, but is pretty outdated and has some technical limitations, and is "deprecated" in favor of Vulkan. Most of OpenGL's features have to be tediously loaded manually, usually people use a library like GLEW to do it, but it's also not too hard to do yourself if you know how it works.
- Vulkan is a newer "successor" to OpenGL, it's a lot more capable, but also SIGNIFICANTLY harder to use. Vulkan is mostly used by people who develop engines and libraries since they have more time and reason to delve into it. Indie game developers tend to still use OpenGL because it's much more approachable and easy to use, but some do use Vulkan.
- I believe Metal is what Mac uses. I don't have any knowledge about Mac things, but I've heard it's similar to Vulkan.
Images/audio without libraries
Reading a file format like PNG requires you to learn the PNG format which is quite extensive, and possibly learn compression algorithms and implement them yourself. It might be useful for learning more programming skills, but otherwise I think it's a waste of time because you can't really do anything with it. The goal is to read a format that someone else designed so there isn't a lot of room for creativity.
If you want to load an image without libraries, BMP is your friend. There's a few different versions and settings in them, but as long as you don't need full-featured BMP support, they're very easy to read because the pixel data is just in there up for taking. You may need to swap some colors around but that's it.
The process goes something like this: Check what BMP version is used. Read integers for width, height and position of the pixel data, all of them are at pre-defined positions in the file. Skip to the pixel data and read the pixels. Depending on version, there may also be pixel masks that determine how the colors in the pixels are ordered (e.g. RGBA vs BGRA). You can read a BMP in less than 10 lines of code if you don't care about validating everything or supporting the different versions or uncommon settings.
If you want to load a sound without libraries, WAV is your friend. WAV is similar to BMP except for audio: there's some settings that may complicate it, but usually the sound data is just sitting in there so all you need to do is figure out where it's at and then read it.
The proces of reading WAV is slightly more complicated, you need to loop through "chunks", each containing different kind of information, until you find the chunk that has the audio data. You can just skip the chunk types that you don't care about, but there's another chunk that has information about the audio data which you'll want, like how many channels it has (usually 2, for left and right) and how fast you're supposed to play the sound data (Hz). Reading a basic WAV file takes less than 150 lines of code.
The downside of BMP and WAV is that they are not compressed, so the filesize is HUGE compared to compressed formats. There's also an upside though, which is that they're much faster to load into your program since your CPU doesn't have to spend any time uncompressing them.