Now Reading
Rendering prefer it’s 1996 – Bitmap fonts and DOS

Rendering prefer it’s 1996 – Bitmap fonts and DOS

2023-01-15 12:56:43


This display screen has burned itself into my retina.

To observe alongside this weblog submit with operating code, be sure you’ve put in the prerequisites. Then:

git clone https://github.com/badlogic/r96
cd r96
git checkout 04-dos-nostalgia
./instruments/download-tools.sh
code .

Last time we realized about loading photos and blitting. That was over 3 weeks in the past, making me miss my goal of posting one collection entry per week. However there is a purpose for it! I used to be somewhat busy in these two weeks.

After utilizing Hopper to generate management stream graphs to debate efficiency optimization, I acquired just a little sick of the workflow and constructed my very own assembly CFG viewer. Simply paste some x86 or ARM meeting generated by MSVC, Clang, or GCC into the left panel, and think about the management stream graph of every perform on the proper. I additionally made it a re-usable NPM package. Going ahead, I can embed these fancy CFGs straight.

Then I drifted off into yet one more rabbit gap. Spurred by a imply touch upon Reddit about how the r96 code would not even run in DOS, I made the code of the collection run in DOS.

First, I constructed a DOS backend for MiniFB. Then, I forked an old GDB version which is able to remotely debugging 32-bit protected mode DOS applications as produced by DJGPP, the GCC fork I take advantage of to construct C/C++ DOS applications. I additionally forked DOSBox-x to repair it up so my forked GDB can truly connect with DOS applications by way of the serial port/TCP emulation.

Lastly, I took the hardly useful GDB stub that comes with DJGPP, rewrote it and added a ton of performance to it, so I can now debug DOS applications operating in DOSBox-x from the comforts of Visible Studio Code.

All of that work culminated in a VS Code extension, which helps you to go from 0 to debugging a easy DOS mode 13h demo app in VS code in about 80 seconds:

With all of that out of my system, I constructed some shell scripts that may enable you set up (virtually) all of the instruments to compile, run, and debug the r96 undertaking for desktop, internet, and DOS. And I added some VS Code magic so you may comfortably begin debugging periods on every platform.

And to spherical all of it off, I cleaned up the Git repo, so every weblog submit maps to precisely one commit. And I rewrote the primary 3 weblog posts within the collection. So yeah.

I can now fortunately proceed writing the collection. Promise. Until I am going to add Android and iOS help sooner or later. I at present do not feel that particular masochism piling up within me.

At this time, we’re taking a look at DOS help, after which load and draw some bitmap fonts.

Demo: Howdy DOS

Alright, go get the newest and biggest from the r96 repository. Comply with the README.md to put in the instruments, together with the brand new DOS instruments. The README.md may even get you in control on find out how to construct and debug the whole lot in VS Code or on the command line. Or, in order for you an in depth run-down of the undertaking and its construct and IDE help, learn the first entry of the series.

To rejoice DOS help, I’ve added a brand new demo known as 12_hello_dos.c:

#embody <MiniFB.h>
#embody <stdio.h>
#embody "r96/r96.h"
#embody "stdlib.h"
#embody <math.h>

#outline GDB_IMPLEMENTATION
#embody "dos/gdbstub.h"

#outline num_grunts 100

typedef struct grunt {
	int x, y, vx, vy;
} grunt;

int major(void) {
	gdb_start();
	r96_image picture;
	if (!r96_image_init_from_file(&picture, "property/grunt.png")) {
		printf("Could not load file 'property/grunt.png'n");
		return -1;
	}

	r96_image output;
	r96_image_init(&output, 320, 240);
	struct mfb_window *window = mfb_open("12_hello_dos", output.width, output.peak);

	grunt grunts[num_grunts];
	for (int i = 0; i < num_grunts; i++) {
		grunt *grunt = &grunts[i];
		grunt->x = rand() % 320;
		grunt->y = rand() % 200;
		grunt->vx = 1;
		grunt->vy = 1;
	}
	do {
		r96_clear_with_color(&output, 0xff222222);
		for (int i = 0; i < num_grunts; i++) {
			grunt *grunt = &grunts[i];
			if (grunt->x < 0) {
				grunt->x = 0;
				grunt->vx = -grunt->vx;
			}
			if (grunt->x > 320 - 64) {
				grunt->x = 320 - 64;
				grunt->vx = -grunt->vx;
			}
			if (grunt->y < 0) {
				grunt->y = 0;
				grunt->vy = -grunt->vy;
			}
			if (grunt->y > 240 - 64) {
				grunt->y = 240 - 64;
				grunt->vy = -grunt->vy;
			}
			grunt->x += grunt->vx;
			grunt->y += grunt->vy;
			r96_blit_keyed(&output, &picture, grunt->x, grunt->y, 0x00000000);
		}
		if (mfb_update_ex(window, output.pixels, output.width, output.peak) != STATE_OK) break;
		gdb_checkpoint();
	} whereas (mfb_wait_sync(window));

	r96_image_dispose(&picture);
	r96_image_dispose(&output);
	return 0;
}

That is our first animated demo!

The demo attracts 100 shifting grunts, that bounce off of the display screen boundaries. Every grunt is saved in a easy grunt struct, which in flip shops the grunt’s place (x, y) and velocity on the x- and y-axis (vx, vy) in pixels per body. Throughout initialization, we give every grunt a random place throughout the display screen boundaries and set its velocity on every axis to 1 (traces 29-35).

What’s a body you could ask? A body may be many issues, however in our case, a body is solely one iteration of the primary loop of your program (traces 36-62). In every body, we verify whether or not every grunt continues to be contained in the display screen boundaries. If a grunt is exterior the display screen boundaries on the x- or y-axis (or each), we transfer them again contained in the bounds and negate their velocity on the axis they left the display screen on.

E.g. a grunt shifting to the proper (vx = 1), leaving the display screen on the x-axis (x > 320 - 64), can be moved again contained in the display screen boundaries (x = 320 - 64), and its velocity on the x-axis will develop into -1. Beginning within the subsequent body, the grunt will then transfer to the left, till it exits the display screen boundaries on the left facet of the display screen. The identical occurs on the y-axis.

As soon as all of the checks are full, we add the grunt’s velocity to its place. Every body, the grunt’s place thus modifications by vx pixels on the x-axis, and vy pixels on the y-axis. Therefore why vx and vy are given as pixels per body.

Notice: This can be a very primary type of explicit Euler integration. It is a lot much less scary than it sounds! Go study your fundamentals.

Now, there’s one massive downside with this kind of shifting objects: it is determined by the pace of execution.

We name mfb_wait_sync(), which waits for a vertical refresh, successfully limiting the variety of frames per second to the display screen refresh charge, so 60Hz, 90Hz, 120Hz, or no matter different wonky display screen refresh charge the show has.

On a 60Hz display screen a grunt will thus transfer 60 pixels per second, on a 120Hz it can transfer 120 pixels.

For a recreation, that is not nice: totally different gamers will expertise the sport at totally different speeds, relying on their {hardware}. We’ll look into this difficulty in a future collection entry.

Notice: Many elderly DOS video games truly did have this downside: they’d not bear in mind how a lot time has handed because the final body, however as an alternative replace recreation object positions at a set charge every body. There is a purpose Wikipedia has an entry on the infamous PC turbo button.

This is the little demo on the net:

And right here it’s operating in DOSBox-x, telling DOSBox-x to go full pace.

DOSBox-x on my system syncs to 60Hz in windowed mode, whereas Chrome runs the online demo on the full 120Hz of my show. Within the video above, there may be some smearing and artifacts. That is principally because of the MP4 encoding and would not appear to be that when truly operating the demo in DOSBox-x in your system.

Is the DOSBox-x efficiency indicative of efficiency on outdated programs? No. DOSBox-x goes full pace, which is approach quicker than what my outdated 486 might do. Nonetheless, you may modify the emulation pace by way of the DOSBox-x menu CPU > Emulated CPU pace. Within the following video, I’ve set the emulated CPU pace to be equal to a 486DX2 with 66Mhz:

Whereas that is extra correct, it is nonetheless not fairly the identical as actual {hardware}. To get a extra correct sense of how this system would carry out on an actual 486, we will use 86Box. 86Box is as cycle correct emulator for numerous outdated x86 programs.

Appears to be like like DOSBox-x is not far off with its emulation. So why is it so gradual?

Notice: Organising digital machine photos for 86Box is a bit horrible. I’ve created 2 photos you may obtain, a 486 picture and a Pentium picture, pre-installed with MS-DOS 6.22, a mouse driver, and a CD-ROM driver. You possibly can run them by way of 86box -c 486/86box.conf and 86box -c pentium/86box.conf. The photographs additionally embody QBasic 1.1. And NIBBLES.BAS and GORILLA.BAS. Simply saying.

Why is it so gradual on a 486?

The MiniFB DOS backend units up a video mode with both 24-bit or 32-bit colour depth by way of VESA. MiniFB assumes 32-bit colour depth, so now we have to abide by that and go VESA.

This works fairly properly from Pentium class machines onwards, if the (emulated) video card helps VESA. This is the demo on Pentium class {hardware} in 86Box:

A 486 could help 24-bit and 32-bit colour depth video modes, relying on the graphics card. Mine did. Nonetheless, that does not imply the system is quick sufficient to truly take care of that quantity of information. A humdrum 486 would have reminiscence throughput someplace within the vary of 10-25MB/s. You learn that proper.

In our demo above, we render to a 320×240 output r96_image. The decision to r96_clear_with_color() has to the touch 0.3MB price of pixels. Rendering a single grunt means studying 64x64x4 bytes from the grunt picture and writing them to a 64x64x4 bytes massive area within the output r96_image. For 100 grunts, that is studying 1.6MB and writing 1.6MB. Lastly, the output r96_image is transferred to the VESA linear buffer, a reminiscence mapped area from which the graphics card will learn what it ought to output to the show. That is one other 320x240x4 bytes, or 0.3MB. Every body we thus contact 0.3 + 1.6 + 1.6 + 0.3 = 3.8MB of reminiscence. And whereas this straightforward evaluation would not account for reminiscence caches, it does align with what we expertise when operating the demo on a (emulated) 486. We do certainly solely get one thing like 3-5 frames per second, which is 11.4-19MB of information pushed by the demo per second.

That is one of many causes just about all older DOS video games concentrating on 386 or 486 would use mode 13h or derivatives like Mode X. Each of those video modes use 8 bits to encode a pixel’s colour. However as an alternative of straight encoding the colour’s pink, inexperienced and blue part, the 8-bit worth is an index right into a palette with a complete of 256 colours. That cuts down on reminiscence and bandwidth wants significantly.

If we went mode 13h in our demo, we would go from 3.8MB to 0.95MB of information per body. That interprets to 12-20 frames per second, which continues to be not nice, however usually playable sufficient. That is in regards to the body charge I acquired when enjoying MicroProse’s Formula One Grand Prix on my 486.

So what is the answer? Draw much less every body! DOOM and Quake relied on numerous strategies like binary space partitioning to keep away from drawing issues which might be invisible or occluded. Drawing much less means touching much less reminiscence. Take into account that 100 grunts are about 5.3 screens price of pixels. That is a whole lot of overdraw.

Sure, we might most likely squeeze a whole lot of cycles out of the blitting capabilities if we handcrafted some 32-bit x86 meeting. However DJGPP truly does a fairly good job at producing quick machine code. And I do not wish to drop down into meeting land.

Notice: trendy {hardware} will not prevent from these points both typically. When NVIDIA despatched me a prototype Tegra board within the early 2010s, I quickly came upon that you can solely render about 2 full-screen alpha blended rectangles by way of OpenGL ES earlier than the frame-rate takes a heavy hit.

Tour: DOS debugging help

Once we debug the demo on the desktop, the debugger will spawn the demo course of and use system APIs to cease, resume, examine, and in any other case manipulate the method.

For DOS purposes operating in DOSBox-x or on an actual machine, we would not have the luxurious of a debugger. As a substitute, we use a bit of code known as GDB stub that we combine in our program. This is how that works in 12_hello_dos.c.

Of observe are 3 items of code within the demo above, which do nothing on any platform aside from DOS. In traces 7-8 now we have:

#outline GDB_IMPLEMENTATION
#embody "dos/gdbstub.h"

This pulls in my GDB stub implementation for DJGPP/DOS, which is a single header file library.

The stub’s job is it to speak with the debugger over the serial port, and inform it when this system has stopped resulting from a breakpoint, or segfault, or different purpose. The stub then waits for instructions from the debugger to execute, like setting breakpoints, inspecting reminiscence and CPU registers, stepping, persevering with, and so forth.

This GDB stub kind of debugging is a cooperative debugging strategy. The stub must be built-in with this system itself. This explains the opposite two GDB associated traces of code within the demo.

The gdb_start() perform is known as initially of major(). It waits for the debugger to attach on the serial port. When the debugger tells the stub to proceed execution of this system, the stub stops speaking with the debugger in the meanwhile, and provides again management to this system.

The stub then waits for a system degree sign to be raised, like a breakpoint or segfault, for which the stub has registered handlers. If such a sign occurs, the stub takes over management from this system once more, tells the debugger about this system being stopped, and waits for debugger instructions to execute.

The ultimate GDB associated line is gdb_checkpoint() in line 61. It’s positioned on the finish of our major loop. That is required so the stub can verify if the debugger requested to interrupt this system, wherein case the stub will take management of this system once more and discuss to the debugger.

The GDB stub expects all communication to occur by way of serial port COM1. Some emulators and digital machines, like DOSBox-x or VirtualBox, can expose the emulated serial port as a TCP port to applications on the host OS. That is what’s occurring once we debug a demo in DOSBox-x. DOSBox-x exposes the serial port on TCP port 5123, to which GDB connects by way of TCP. DOSBox-x will then translate TCP packages to writes to the serial port, which the GDB stub reads from COM1. If the GDB stub writes to COM1, then DOSBox-x will ahead the information by way of TCP to GDB.

In concept, the GDB stub also needs to work on real-hardware. Sadly, I would not have my 486 anymore, nor a serial cable or a serial port on my MacBook.

If you wish to debug any of the demos in DOS, you may have so as to add the three items of GDB stub associated code to the demo’s sources as outlined above. Solely the 12_hello_dos.c demo is at present set-up for DOS debugging. Since our code is cross-platform, there will not be a have to debug in DOS so much although.

Notice: when debugging the demos compiled for DOS, we’ll be utilizing DOSBox-x as an alternative of 86Box. Two causes: getting information into and out of 86Box could be very annoying. And there’s no serial port over TCP emulation in 86Box, so the debugger could not even join. It must be potential to hook the debugger up with a program operating in MS-DOS or FreeDOS in VirtualBox although.

Bitmap fonts

Rendering textual content as of late is de facto, actually arduous. Once we go zooming round paperwork or internet pages by way of mouse wheel or contact zoom, we count on textual content to scale seamlessly and keep crisp. If we wish to get fancy, we add kerning and hinting to the combo.

It will get even more durable when non-latin scripts like arabic script or CJK script have to get placed on a display screen. Now it’s important to take care of (extra) ligatures, blended left-to-right and right-to-left layouting, and numerous different complexities.

And to high all of it off, what you get out of a font file is normally a vector illustration of not a personality, however a glyph, which generally is a character, or part of a personality, and oh my, that is all very sophisticated.

Fortunately, there are numerous libraries that may assist us draw textual content. For translating a textual content string to a set of glyphs, or shaping because it’s normally known as, you need to use HarfBuzz. If you wish to rasterize these glyphs, that are normally given in vector type, you need to use FreeType. In case you wish to use your GPU to do most of that, you need to use Slug. Your working system normally additionally comes with APIs to draw text.

We aren’t going to do any of that although. We’ll be going considerably old skool and draw inspiration from VGA text mode fonts, however with a 2022 spirit (aka being wasteful).

Earlier than we will have a look at font pixels, we have to speak about how textual content is saved within the tubes of our computerers.

Character encodings

Textual content consists of characters. Once we retailer textual content digitally, these characters must be saved as a sequence of (binary) numbers. Once we learn characters from a file to attract them to the display screen, or translate key strokes to characters, we have to map numbers again to characters. Equally, when the C compiler encounters a string literal like const char *textual content = "Howdy world", it can convert the characters within the string to a sequence of numbers that will get embedded within the ultimate executable.

Mapping these sequences of numbers to characters and vice versa is what character encodings are for.

One of many oldest character encodings is ASCII. Every character is encoded in 1 byte. Effectively, truly, ASCII solely makes use of the primary 7-bits, so it encodes a complete of 128 characters. Effectively, that is not fairly true both. Solely 95 of those characters are printable. The opposite 33 “characters” are what’s known as control codes. Notable ones are t or 9, which signifies a tab, and n or 10, the road feed. See, it is already sophisticated!

Listed below are all of the printable characters and non-printable management codes contained in ASCII with their (hexa-)decimal codes.

> ascii -d
Dec Hex    Dec Hex    Dec Hex  Dec Hex  Dec Hex  Dec Hex   Dec Hex   Dec Hex
  0 00 NUL  16 10 DLE  32 20    48 30 0  64 40 @  80 50 P   96 60 `  112 70 p
  1 01 SOH  17 11 DC1  33 21 !  49 31 1  65 41 A  81 51 Q   97 61 a  113 71 q
  2 02 STX  18 12 DC2  34 22 "  50 32 2  66 42 B  82 52 R   98 62 b  114 72 r
  3 03 ETX  19 13 DC3  35 23 #  51 33 3  67 43 C  83 53 S   99 63 c  115 73 s
  4 04 EOT  20 14 DC4  36 24 $  52 34 4  68 44 D  84 54 T  100 64 d  116 74 t
  5 05 ENQ  21 15 NAK  37 25 %  53 35 5  69 45 E  85 55 U  101 65 e  117 75 u
  6 06 ACK  22 16 SYN  38 26 &  54 36 6  70 46 F  86 56 V  102 66 f  118 76 v
  7 07 BEL  23 17 ETB  39 27 '  55 37 7  71 47 G  87 57 W  103 67 g  119 77 w
  8 08 BS   24 18 CAN  40 28 (  56 38 8  72 48 H  88 58 X  104 68 h  120 78 x
  9 09 HT   25 19 EM   41 29 )  57 39 9  73 49 I  89 59 Y  105 69 i  121 79 y
 10 0A LF   26 1A SUB  42 2A *  58 3A :  74 4A J  90 5A Z  106 6A j  122 7A z
 11 0B VT   27 1B ESC  43 2B +  59 3B ;  75 4B Okay  91 5B [  107 6B k  123 7B 
 13 0D CR   29 1D GS   45 2D -  61 3D =  77 4D M  93 5D ]  109 6D m  125 7D 
 14 0E SO   30 1E RS   46 2E .  62 3E >  78 4E N  94 5E ^  110 6E n  126 7E ~
 15 0F SI   31 1F US   47 2F /  63 3F ?  79 4F O  95 5F _  111 6F o  127 7F DEL

The codes 0-31 are management codes, together with the t (9) and n (10) codes we mentioned above. Printable characters begin at code 32 ( or area) and go to code 126. The ultimate code 127 is one other management code.

ASCII is brief for “American Commonplace Code for Info Interchange”. Unsurprisingly, the ASCII encoding actually solely accommodates characters utilized in US English, and by coincidence, another western scripts.

Now, I am not ‘merican. And primarily based on my server logs, chances are high good you are not ‘merican both. What about different fancy characters, like ‘ö’ or ‘ê’? Or characters from the arabic or CJK scripts? Effectively, that is much more sophisticated and traditionally entails one thing known as code pages, which was and nonetheless is an utter mess.

The choice to code pages is Unicode. Unicode defines codes (or code factors in Unicode parlance) for nearly 150,000 characters utilized in scripts from all all over the world, together with historic ones. It additionally consists of emojis, for higher or worse. Your dad and mom’ brains have most likely additionally switched to emoji solely instantaneous messaging communication. They usually mentioned computer systems would make us youngsters dumb. Thanks, Unicode.

Unicode has a number of encodings, like UTF-8, UTF-16, and so forth. Fortunately, the world has now principally standardized on UTF-8, for good reasons. UTF-8 is a multi-byte encoding. Relying on the character, we might have 1 to 4 bytes to retailer it.

For our demos, we’ll retailer textual content both in C supply code as literals ala const char *textual content = "Howdy world", or in textual content recordsdata within the property/ folder of the r96 undertaking. Each the C sources and textual content recordsdata can be encoded utilizing UTF-8. The rest could be ache. This implies now we have to take care of UTF-8 when rendering textual content.

However as I mentioned earlier, we don’t wish to go full Unicode textual content rendering, as that’d require us to combine all the flamboyant libraries talked about above. We wish a less complicated answer. Enter Unicode’s first 256 code factors. These code factors are cut up up into 2 blocks.

The primary block from code level 0-127 is known as the Basic Latin Unicode block. The code factors are the very same codes as utilized in ASCII, together with each non-printable management codes (0-31 and 127) and printable characters (32-126). When encoding textual content with UTF-8, the ensuing sequence of bytes is backwards appropriate with ASCII: the primary 128 Unicode code factors get encoded as a single byte in UTF-8.

The second block from code level 128-255 is known as the Latin 1 Supplement block. It accommodates one other set of non-printable management codes (128-159) known as C1 controls, which we will safely ignore for the aim of rendering textual content. The remaining code factors within the block (160-255) embody extra characters utilized in some western scripts. These Unicode code factors are encoded with 2 bytes in UTF-8.

Shock! These first 256 Unicode code factors map straight onto an outdated code web page, specifically, the ISO-8859-1 character set. It’s typically incorrectly known as prolonged ASCII. Listed below are the characters contained within the set.

E.g. ö is encoded as 0xF6 or 246 in decimal. The grey blocks are the management codes.

Alright, we have determined to make use of the primary 2 Unicode blocks spanning code factors 0-255. All our C supply code containing string literals can be saved UTF-8 encoded. And any textual content recordsdata we put into property/ to be learn by our demos may even be UTF-8 encoded. There are two minor problems.

The primary complication is how C compilers deal with string literals. When the compiler encounters one thing like const char *textual content = "Howdy world", it can use a personality encoding to show the literal "Howdy world" right into a sequence of bytes embedded within the executable. Which encoding is chosen, is determined by the compiler. By default, Clang and GCC convert the string literal to UTF-8 and embed the corresponding byte sequence. Clang even assumes that the supply file encoding is UTF-8 and refuses to compile anything. MSVC is … different. Fortunately, we don’t take care of MSVC on this collection. In case you do take care of some purpose, simply ensure that to cross /utf8 as a compiler flag to make sure MSVC embeds string literals as UTF-8 as properly.

The second complication is definitely studying the code factors of a UTF-8 encoded textual content string, whether or not it comes from a C string literal or a UTF-8 encoded file learn from disk. Now we have to take care of the multi-byte nature of the UTF-8 encoding, as code factors above 127 are encoded as two bytes. Fortunately, I’ve taken care of that with the perform r96_next_utf8_character():

uint32_t r96_next_utf8_code_point(const char *information, uint32_t *index, uint32_t finish) {
	static const uint32_t utf8_offsets[6] = {
			0x00000000UL, 0x00003080UL, 0x000E2080UL,
			0x03C82080UL, 0xFA082080UL, 0x82082080UL};

	uint32_t character = 0;
	const unsigned char *bytes = (const unsigned char *) information;
	int num_bytes = 0;
	do {
		character <<= 6;
		character += bytes[(*index)++];
		num_bytes++;
	} whereas (*index != finish && ((bytes[*index]) & 0xC0) == 0x80);
	character -= utf8_offsets[num_bytes - 1];

	return character;
}

This perform takes a sequence of bytes (information) encoding a UTF-8 string, an index into the byte sequence, and the final legitimate index (finish). Each indices are byte offsets, not character offsets!

The perform then reads the following UTF-8 character, which can be 1 to 4 bytes lengthy, and returns its code level. Moreover, it increments the index accordingly, so we all know at what byte offset the following character begins.

Notice: I stole the unique of this perform a few years in the past from … someplace. I can’t bear in mind anymore. I’ve since modified it to my wants. To the unique creator: I am deeply sorry I forgot who you’re.

We will use this perform to iterate all UTF-8 characters in a byte sequence and get their code factors:

const char *utf8_text = "¡ÄÖ$nt";
uint32_t index = 0;
uint32_t finish = strlen(utf8_text);
whereas (index != finish) {
	uint32_t code_point = r96_next_utf8_code_point(utf8_text, &index, finish);
	printf("code level: %i/%xn", code_point, code_point);
}

Which prints the code level of every character in decimal and hexadecimal.

code level: 161/a1
code level: 196/c4
code level: 214/d6
code level: 36/24
code level: 10/a
code level: 9/9

As anticipated. Examine the output to the ISO-8859-1 chart above for validation.

This perform can take care of any legitimate UTF-8 byte sequence and returns code factors as a 32-bit unsigned integer. For our functions, we’re solely taken with code factors 0-255 and can ignore another code factors.

The glyph atlas

Alright, now we have all our encoding bases lined. The following query is: how will we flip a code level like 64 (0x41) into the corresponding glyph picture for the character A from a font, so we will blit it onto the display screen?

To make issues straightforward for us, we’ll outline some limits:

  • We’ll solely render the printable Unicode code factors between 0-255 as described above.
  • We’ll solely use fixed-width or monospaced fonts. Every glyph in such a font has the identical width. We will solely ignore issues like kerning this manner.
  • The font measurement is fastened.

With these limits in place, the essential thought of a glyph atlas goes like this:

  • Choose a monospaced font, like the unique IBM VGA 8×16 font.
  • Use a glyph rendering library like FreeType to load the font and render out a glyph picture for every printable Unicode code level between 0-255.
  • Pack these glyph photos right into a single picture known as the glyph atlas in some order which makes mapping from a code level to the glyph picture coordinates contained in the glyph atlas trivial.

This is an instance of what such a glyph atlas might appear to be.


I’ve super-imposed a pink grid de-marking every glyph’s boundaries. An atlas we will use wouldn’t have that grid on it. The pixels of the glyph are totally opaque white (0xffffffff), whereas the background pixels are clear (0x00000000);

The atlas above accommodates glyph photos from the IBM VGA 8×16 font for the Unicode code factors 0-255. Every glyph is 8×16 pixels in measurement. Every row consists of 16 glyphs. There are 16 rows in complete, so 256 glyphs in complete, one for every code level.

The glyphs within the first row map to code factors 0-15, the glyphs within the second row map to code factors 16-31, and so forth. The primary, second, ninth, and tenth row are empty, as these are the glyphs for non-printable management characters. The opposite rows comprise the glyphs for all printable characters.

In case you evaluate this glyph atlas with the ISO-8859-1 desk above, you may see that they’re equal, besides that the final glyph within the backside proper nook is lacking from the atlas. The IBM VGA 8×16 font merely doesn’t have a glyph for that code level.

So how will we generate this atlas? We do not. At the least we cannot write code for that as a part of this collection. I’ve already written an internet software primarily based on FreeType that does precisely what we’d like. It is known as Mario's (B)it(m)ap (F)ont (G)enerator (I am a a dad, I am allowed to call it like that) and you’ll run it in your browser here.

The software helps you to load a monospaced TrueType font, set the pixel peak of the glyphs you need, and spits out a 16×14 grid of glyph photos for the code factors 32-255. It omits the code factors 0-31 and thus the primary two rows of the atlas as these are non-printable management codes in any case. The above atlas thus turns into this:


We’re nonetheless losing two rows within the center for the second set of management codes. However maintaining them round makes changing code factors to glyph picture coordinates simpler.

We will retailer the generated glyph atlas as a .png file within the property/ folder. I did simply that utilizing the file title assets/ibmvga.png. The generator additionally tells us that every glyph has a measurement of 8×16 pixels. We’ll have to keep in mind that for once we truly draw textual content later. Because the glyph atlas is a plain outdated picture, we will load it by way of r96_image_init_from_file().

We’re virtually able to render a textual content string. We’d like two extra issues:
* With the ability to map a Unicode code level to a area within the glyph atlas picture, the place a area is outlined by its top-left nook x- and y- pixel coordinates within the glyph atlas, and its width and peak in pixels.
* With the ability to not simply blit a complete r96_image to a different, but additionally blit areas of an r96_image to a different r96_image.

Let’s begin with the mapping downside.

Notice: We might put each the atlas and the glyph measurement info into some customized file format. I made a decision that is not price it, so we’ll go together with a .png and a few arduous coded glyph sizes within the code.

Mapping code factors to glyph atlas pixel coordinates

How can we map a code level to the pixel coordinates of the highest left nook of a glyph picture within the atlas?

Earlier than we resolve pixel coordinates for a code level, it is truly simpler to make use of a special coordinate system. Let’s give every glyph within the atlas an x- and y-coordinate.

For our instance glyph atlas within the final part above, every cell represents a glyph picture of measurement 8×16 pixels. Within the diagram, the cell exhibits each the glyph and its code level.

The highest-left glyph picture has coordinate (0, 0) and the bottom-right glyph picture has coordinate (15, 13). We will outline a easy equation that goes from glyph coordinates to code level, identical to we did for pixel coordinates to pixel deal with:

code_point = glyph_x + glyph_y * glyphs_per_row + 32

Why the + 32? As a result of the primary glyph has code level 32 (area). With out it, we would get 0 for glyph_x = 0 and glyph_y = 0.

We will reverse this glyph coordinates to code level mapping as follows:

glyph_x = (code_point - 32) % glyphs_per_row;
glyph_y = (code_point - 32 - glyph_x) / glyphs_per_row;

The % glyphs_per_row mainly strips the glyph_y * glyphs_per_row part from the unique equation above, leaving us with the glyph x-coordinate.

To calculate glyph_y, we will then subtract the simply calculated glyph_x, which provides us the code level of the primary glyph within the row, and divide by glyphs_per_row to reach on the glyph_y coordinate.

See Also

All that is left to get the pixel coordinate of the highest left nook of a glyph is to multiply the glyph coordinates by the glyph pixel width and peak of the font, 8 and 16 within the instance above.

glyph_pixel_x = glyph_x * glyph_width;
glyph_pixel_y = glyph_x * glyph_height;

Blitting areas

Alright, we will generate glyph atlases for the primary 255 Unicode code factors, and we will calculate the pixel coordinates of a glyph picture within the atlas equivalent to a code level. We additionally know the dimensions of every glyph in pixels, as we specified that when producing the glyph atlas.

However now we have another downside: our present blitting capabilities can solely blit a complete r96_image. What we’d like is blitting capabilities that blit only a area from a r96_image. Fortunately, that is trivial, given our current blitting capabilities! This is a blitting perform that blits a area from one r96_image to a different.

void r96_blit_region(r96_image *dst, r96_image *src, int32_t dst_x, int32_t dst_y, int32_t src_x, int32_t src_y, int32_t src_width, int32_t src_height) {
	assert(src_x + src_width - 1 < src->width);
	assert(src_y + src_height - 1 < src->peak);

	int32_t dst_x1 = dst_x;
	int32_t dst_y1 = dst_y;
	int32_t dst_x2 = dst_x + src_width - 1;
	int32_t dst_y2 = dst_y + src_height - 1;
	int32_t src_x1 = src_x;
	int32_t src_y1 = src_y;

	if (dst_x1 >= dst->width) return;
	if (dst_x2 < 0) return;
	if (dst_y1 >= dst->peak) return;
	if (dst_y2 < 0) return;

	if (dst_x1 < 0) {
		src_x1 -= dst_x1;
		dst_x1 = 0;
	}
	if (dst_y1 < 0) {
		src_y1 -= dst_y1;
		dst_y1 = 0;
	}
	if (dst_x2 >= dst->width) dst_x2 = dst->width - 1;
	if (dst_y2 >= dst->peak) dst_y2 = dst->peak - 1;

	int32_t clipped_width = dst_x2 - dst_x1 + 1;
	int32_t dst_next_row = dst->width - clipped_width;
	int32_t src_next_row = src->width - clipped_width;
	uint32_t *dst_pixel = dst->pixels + dst_y1 * dst->width + dst_x1;
	uint32_t *src_pixel = src->pixels + src_y1 * src->width + src_x1;
	for (int32_t y = dst_y1; y <= dst_y2; y++) {
		for (int32_t i = 0; i < clipped_width; i++) {
			*dst_pixel++ = *src_pixel++;
		}
		dst_pixel += dst_next_row;
		src_pixel += src_next_row;
	}
}

That is mainly our outdated r96_blit() perform with extra arguments. We sepcify the vacation spot (dst) and supply (src) picture as earlier than. We additionally specify the coordinates (dst_x, dst_y) at which the supply picture must be blitted within the vacation spot picture. These was once known as x and y. Lastly, we specify the area from the supply picture we wish to blit, given as its top-left nook (src_x, src_y) and width and peak (src_width, src_height).

The implementation itself then solely has three minor modifications in comparison with r96_blit().

The perform begins with two asserts that be sure that the supply area is legitimate. Subsequent, dst_x2 and dst_y2 are calculated utilizing the supply area width and peak as an alternative of the supply picture width and peak. Lastly, src_x1 and src_y1 aren’t initialized to 0, however to src_x and src_y.

That is it! The remaining, together with the clipping, is precisely the identical as r96_blit(). We will already use this perform to blit glyph photos from the glyph atlas. And for some use circumstances, that’d be ok.

Nonetheless, if we solely wish to blit the white pixels of a glyph and ignore it is background pixels, we’d like colour keying.

Simple, simply copy r96_blit_keyed() and apply the identical modifications.

void r96_blit_region_keyed(r96_image *dst, r96_image *src, int32_t dst_x, int32_t dst_y, int32_t src_x, int32_t src_y, int32_t src_width, int32_t src_height, uint32_t color_key) {
	assert(src_x + src_width - 1 < src->width);
	assert(src_y + src_height - 1 < src->peak);

	int32_t dst_x1 = dst_x;
	int32_t dst_y1 = dst_y;
	int32_t dst_x2 = dst_x + src_width - 1;
	int32_t dst_y2 = dst_y + src_height - 1;
	int32_t src_x1 = src_x;
	int32_t src_y1 = src_y;

	if (dst_x1 >= dst->width) return;
	if (dst_x2 < 0) return;
	if (dst_y1 >= dst->peak) return;
	if (dst_y2 < 0) return;

	if (dst_x1 < 0) {
		src_x1 -= dst_x1;
		dst_x1 = 0;
	}
	if (dst_y1 < 0) {
		src_y1 -= dst_y1;
		dst_y1 = 0;
	}
	if (dst_x2 >= dst->width) dst_x2 = dst->width - 1;
	if (dst_y2 >= dst->peak) dst_y2 = dst->peak - 1;

	int32_t clipped_width = dst_x2 - dst_x1 + 1;
	int32_t dst_next_row = dst->width - clipped_width;
	int32_t src_next_row = src->width - clipped_width;
	uint32_t *dst_pixel = dst->pixels + dst_y1 * dst->width + dst_x1;
	uint32_t *src_pixel = src->pixels + src_y1 * src->width + src_x1;
	for (dst_y = dst_y1; dst_y <= dst_y2; dst_y++) {
		for (int32_t i = 0; i < clipped_width; i++) {
			uint32_t src_color = *src_pixel;
			uint32_t dst_color = *dst_pixel;
			*dst_pixel = src_color != color_key ? src_color : dst_color;
			src_pixel++;
			dst_pixel++;
		}
		dst_pixel += dst_next_row;
		src_pixel += src_next_row;
	}
}

However we will do even higher. No textual content rendering engine is full with out help for coloured textual content! As is stands, we will solely draw white textual content, as that is the colour the glyph atlas generator spits out. On-top of colour keying, we will additionally apply what’s normally often known as tinting.

We’ll implement tinting within the easiest potential approach: multiply the pink, inexperienced, and blue colour part of the supply pixel with the pink, inexperienced, and blue colour part of the required tinting colour. That results of the multiplication is then normalized again to the 0-255 vary for every part by dividing by 255. This successfully mixes the 2 colours.

tinted_red = ((source_red * tint_red) >> 8) & 0xff;
tinted_green = ((source_green * tint_green) >> 8) & 0xff;
tinted_blue = ((source_blue * tint_blue) >> 8) & 0xff;

Notice: for the case of tinting glyphs photos as generated by the generator, we might simply write the tint colour to the vacation spot if the supply pixel colour would not match the colour key. Nonetheless, this strategy above additionally works for tinting arbitrary supply pixel colours. We’ll see why that is helpful in a later demo.

This is the ultimate area blitting routine, which takes each a colour key and a tinting colour:

void r96_blit_region_keyed_tinted(r96_image *dst, r96_image *src, int32_t dst_x, int32_t dst_y, int32_t src_x, int32_t src_y, int32_t src_width, int32_t src_height, uint32_t color_key, uint32_t tint) {
	assert(src_x + src_width - 1 < src->width);
	assert(src_y + src_height - 1 < src->peak);

	int32_t dst_x1 = dst_x;
	int32_t dst_y1 = dst_y;
	int32_t dst_x2 = dst_x + src_width - 1;
	int32_t dst_y2 = dst_y + src_height - 1;
	int32_t src_x1 = src_x;
	int32_t src_y1 = src_y;

	if (dst_x1 >= dst->width) return;
	if (dst_x2 < 0) return;
	if (dst_y1 >= dst->peak) return;
	if (dst_y2 < 0) return;

	if (dst_x1 < 0) {
		src_x1 -= dst_x1;
		dst_x1 = 0;
	}
	if (dst_y1 < 0) {
		src_y1 -= dst_y1;
		dst_y1 = 0;
	}
	if (dst_x2 >= dst->width) dst_x2 = dst->width - 1;
	if (dst_y2 >= dst->peak) dst_y2 = dst->peak - 1;

	uint32_t tint_r = R96_R(tint);
	uint32_t tint_g = R96_G(tint);
	uint32_t tint_b = R96_B(tint);

	int32_t clipped_width = dst_x2 - dst_x1 + 1;
	int32_t dst_next_row = dst->width - clipped_width;
	int32_t src_next_row = src->width - clipped_width;
	uint32_t *dst_pixel = dst->pixels + dst_y1 * dst->width + dst_x1;
	uint32_t *src_pixel = src->pixels + src_y1 * src->width + src_x1;
	for (dst_y = dst_y1; dst_y <= dst_y2; dst_y++) {
		for (int32_t i = 0; i < clipped_width; i++) {
			uint32_t src_color = *src_pixel;
			uint32_t dst_color = *dst_pixel;
			*dst_pixel = src_color != color_key ? R96_ARGB(
														  R96_A(src_color),
														  ((R96_R(src_color) * tint_r) >> 8) & 0xff,
														  ((R96_G(src_color) * tint_g) >> 8) & 0xff,
														  ((R96_B(src_color) * tint_b) >> 8) & 0xff)
												: dst_color;
			src_pixel++;
			dst_pixel++;
		}
		dst_pixel += dst_next_row;
		src_pixel += src_next_row;
	}
}

Since we have already extensively benchmarked and optimized the unique blitter capabilities, and since these new capabilities solely change some setup code, now we have no have to do one other optimization cross. Whew.

Alright, let’s put the whole lot we realized into just a little demo.

Demo: Blitting areas

On this demo, we’re going to blit the glyphs for the string "Howdy world!" sourced from the glyph atlas in assets/ibmvga.png, which I generated by way of Mario’s BMFG. We’ll apply what we realized and created above, from iterating UTF-8 encoded characters, calculating pixel coordinates for glyphs from code factors, to blitting areas in numerous methods.

This is 13_blit_region.c:

#embody <MiniFB.h>
#embody <stdlib.h>
#embody <string.h>
#embody "r96/r96.h"

int major(void) {
	const int window_width = 320, window_height = 240;
	struct mfb_window *window = mfb_open("13_blit_region", window_width, window_height);
	r96_image output;
	r96_image_init(&output, window_width, window_height);

	r96_image glyph_atlas;
	int32_t glyph_width = 8;
	int32_t glyph_height = 16;
	int32_t glyphs_per_row = 16;
	r96_image_init_from_file(&glyph_atlas, "property/ibmvga.png");

	do {
		r96_clear_with_color(&output, R96_ARGB(0xff, 0x22, 0x22, 0x22));

		const char *textual content = "Howdy world!";
		uint32_t text_length = strlen(textual content);
		uint32_t char_index = 0;
		uint32_t x_offset = 100;
		whereas (char_index < text_length) {
			uint32_t code_point = r96_next_utf8_code_point(textual content, &char_index, text_length);
			int32_t glyph_x = (code_point - 32) % glyphs_per_row;
			int32_t glyph_y = (code_point - 32 - glyph_x) / glyphs_per_row;
			int32_t glyph_pixel_x = glyph_x * glyph_width;
			int32_t glyph_pixel_y = glyph_y * glyph_height;

			r96_blit_region(&output, &glyph_atlas, x_offset, 50, glyph_pixel_x, glyph_pixel_y, glyph_width, glyph_height);
			r96_blit_region_keyed(&output, &glyph_atlas, x_offset, 100, glyph_pixel_x, glyph_pixel_y, glyph_width, glyph_height, 0x0);
			r96_blit_region_keyed_tinted(&output, &glyph_atlas, x_offset, 150, glyph_pixel_x, glyph_pixel_y, glyph_width, glyph_height, 0x0, 0xffff00ff);
			x_offset += glyph_width;
		}

		if (mfb_update_ex(window, output.pixels, window_width, window_height) != STATE_OK) break;
	} whereas (mfb_wait_sync(window));
	return 0;
}

As typical, we begin out by making a window and an output r96_image to which we draw, which will get later drawn to the window.

Subsequent, we outline the properties of our glyph atlas and the glyphs contained there-in, and cargo the glyph atlas picture.

In the primary loop, we clear the output picture, then iterate by way of the characters within the textual content string by way of r96_next_utf8_code_point(). We then calculate the glyph pixel coordinates for the code level within the glyph atlas and use that info to blit the glyph to the display screen 3 times, utilizing the conventional blit, keyed blit, and keyed and tinted blit capabilities.

Take particular observe of x_offset. It specifies at what x-coordinate the following glyph can be blitted within the output picture. As our font is monospaced, we will simply advance the drawing place on the x-axis by glyph_width. All glyphs have the identical width. Variable width fonts are fairly a bit extra advanced to get proper in that regard.

And right here is the online model.

Let’s pack all of this up into re-useable code.

r96_font

Wanting on the final demo, we will virtually see a struct for fonts plop out:

r96_image glyph_atlas;
int32_t glyph_width = 8;
int32_t glyph_height = 16;
int32_t glyphs_per_row = 16;

That is the minimal info we have to retailer for a font to attract textual content with it, which interprets to the next struct:

typedef struct r96_font {
	r96_image glyph_atlas;
	int32_t glyph_width, glyph_height;
	int32_t glyphs_per_row;
	int32_t tab_size;
} r96_font;

We load the glyph_atlas from a picture file. glyph_width and glyph_height are parameters we’ll have to specify when initializing the r96_image font. glyphs_per_row we will truly robotically deduce from the glyph atlas width and the glyph width, decreasing the quantity of parameters we have to specify when initializing a font. tab_size will make sense in a minute! This is r96_font_init():

bool r96_font_init(r96_font *font, const char *path, int32_t glyph_width, int32_t glyph_height) {
	if (!r96_image_init_from_file(&font->glyph_atlas, path)) return false;
	font->glyph_width = glyph_width;
	font->glyph_height = glyph_height;
	font->glyphs_per_row = font->glyph_atlas.width / glyph_width;
	font->tab_size = 3;
	return true;
}

Unremarkable. And the corresponding r96_font_dispose():

void r96_font_dispose(r96_font *font) {
	r96_image_dispose(&font->glyph_atlas);
}

The rendering logic from the final instance may be straight translated to a re-usable perform. However we’ll add two extra options. We’ll interpret n and t and regulate the rendering place for the following glyph accordingly.

void r96_text(r96_image *picture, r96_font *font, const char *textual content, int32_t x, int32_t y, uint32_t tint) {
	int32_t cursor_x = x;
	int32_t cursor_y = y;
	uint32_t text_length = strlen(textual content);
	uint32_t index = 0;
	whereas (index < text_length) {
		uint32_t c = r96_next_utf8_code_point(textual content, &index, text_length);
		if (c == 't') {
			cursor_x += font->tab_size * font->glyph_width;
			proceed;
		}
		if (c == 'n') {
			cursor_x = x;
			cursor_y += font->glyph_height;			
			proceed;
		}
		if (c < 32 || c > 255) {
			cursor_x += font->glyph_width;
			proceed;
		}

		int32_t glyph_index = c - 32;
		int32_t glyph_x = (glyph_index % font->glyphs_per_row);
		int32_t glyph_y = (glyph_index - glyph_x) / font->glyphs_per_row;
		glyph_x *= font->glyph_width;
		glyph_y *= font->glyph_height;

		r96_blit_region_keyed_tinted(picture, &font->glyph_atlas, cursor_x, cursor_y, glyph_x, glyph_y, font->glyph_width, font->glyph_height, 0x0, tint);

		cursor_x += font->glyph_width;
	}
}

The perform takes the picture we wish to render the textual content to, the font to render with, the textual content as a null-terminated UTF-8 string, and the x and y place to start out rendering the primary glyph at within the picture. It is ultimate parameter is the tint colour.

Contained in the perform, we hold monitor of the place to render the following glyph at in cursor_x and cursor_y. We additionally hold monitor of the textual content size in bytes and the byte index from which we’ll learn the following Unicode code level from the textual content.

The loop then iterates over all code factors within the textual content by way of r96_next_utf8_code_point(). In case we encounter t, we advance the cursor place by font->tab_size * font->glyph_width and proceed on to the following glyph. In case of n, we reset cursor_x to the unique x, basically shifting the cursor to the start of the textual content line. We then improve cursor_y by the glyph peak to maneuver it to the following line under. Yay, multi-line rendering!

Earlier than we truly render the glyph for the present code level, we additionally verify that the code level is inside 32-255, so we do not strive to attract a glyph that is not contained in the glyph atlas.

The rest of the perform maps the code level to the glyph within the glyph atlas and makes use of r96_blit_region_keyed_tinted() to attract the glyph to the present cursor place. Lastly, we advance the cursor by the glyph width.

Not counting the area blitting capabilities, the whole textual content rendering code code is about 70 LOC now. Let’s add just a few extra traces of code.

Within the earlier demo, we positioned the glyphs at arduous coded coordinates. If we wished to heart the textual content on the display screen, or apply different alignments, we have to know the width and peak of the textual content, often known as its bounds.

Let’s write just a little perform that calculates precisely that.

void r96_font_get_text_bounds(r96_font *font, const char *textual content, int32_t *width, int32_t *peak) {
	*width = 0;
	*peak = font->glyph_height;
	int32_t current_line_width = 0;
	uint32_t text_length = strlen(textual content);
	uint32_t index = 0;
	whereas (index < text_length) {
		uint32_t c = r96_next_utf8_code_point(textual content, &index, text_length);
		if (c == 't') {
			current_line_width += font->tab_size * font->glyph_width;
			proceed;
		}
		if (c == 'n') {
			*width = current_line_width > *width ? current_line_width : *width;
			*peak += font->glyph_height;
			current_line_width = 0;
			proceed;
		}
		current_line_width += font->glyph_width;
	}
	*width = current_line_width > *width ? current_line_width : *width;
}

The perform takes the font that the textual content can be rendered with, in addition to pointers width and peak to which we write the calculated bounds.

The perform then mirrors components of the rendering logic in r96_text(), calculating the utmost line width, in addition to what number of traces there truly are.

Alright, let’s use all this in just a little demo.

Demo: utilizing r96_font and pals

This is 14_fonts.c, our cute font demo:

#embody <MiniFB.h>
#embody <stdlib.h>
#embody "r96/r96.h"

int major(void) {
	const int window_width = 320, window_height = 240;
	struct mfb_window *window = mfb_open("14_fonts", window_width, window_height);
	r96_image output;
	r96_image_init(&output, window_width, window_height);
	r96_font font;
	r96_font_init(&font, "property/ibmvga.png", 8, 16);

	do {
		r96_clear_with_color(&output, R96_ARGB(0xff, 0x22, 0x22, 0x22));

		const char *textual content = "The fast brown fox jumpsnover the lazy dogn"
						   "¡¢£¤¥¦§¨©ª«¬n"
						   "ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ";

		int32_t text_x, text_y, text_width, text_height;
		r96_font_get_text_bounds(&font, textual content, &text_width, &text_height);
		text_x = window_width / 2 - text_width / 2;
		text_y = window_height / 2 - text_height / 2;

		r96_rect(&output, text_x, text_y, text_width, text_height, R96_ARGB(0xff, 0xff, 0x0, 0xff));
		r96_text(&output, &font, textual content, text_x + 1, text_y + 1, 0x00000000);
		r96_text(&output, &font, textual content, text_x, text_y, 0xffffffff);

		if (mfb_update_ex(window, output.pixels, window_width, window_height) != STATE_OK) break;
	} whereas (mfb_wait_sync(window));
	return 0;
}

We begin off by loading the font in line 11, specifying the glyph atlas picture path, the glyph width, and the glyph peak. r96_font_init masses the glyph atlas picture and units up all of the fields of the font as we noticed earlier.

In the primary loop, we clear the output picture, then outline the textual content we wish to render. The textual content consists of three traces, utilizing characters from the code level vary we help.

The following block of code calculates the bounds of the textual content by way of r96_font_get_text_bounds(), which we use to calculate the textual content’s top-left nook place in such a approach, that the textual content is centered in the midst of the display screen.

Within the ultimate block, we render a background rectangle utilizing the textual content bounds, adopted by rendering the textual content offset by 1 pixel on each axes with a black tint. Lastly, we render the textual content on the calculated place with a white tint. Rendering the textual content twice this manner offers us a easy shadow impact. This is the demo operating on the net.

Nice success.

Demo: enjoyable with fonts

Whereas the unique IBM VGA font is good, it is also a little bit of an outdated, and dare I say boring look.

I’ve added two extra glyph atlases to the property/ folder. The primary one is derived from the superior Tamzen font (property/tamzen.png).


It has a lighter, extra trendy look and is properly suited to show stats, like efficiency counters.

The opposite font was ripped from some outdated demo from the 90ies by Ian Hanschen. He is put up a GitHub repo with a gargantuan quantity of ripped fonts. Most of them would not have attribution. That is the one I picked (property/demofont.png).


Every font is mainly only a glyph atlas. Nonetheless, the atlas structure would not match the one generated by BMFG.

For the font I picked, we see that it solely accommodates glyphs for the primary few code factors. As a substitute of 16 glyphs, it accommodates 20 glyphs per row. Fortunately, r96_init_font() can take care of this by calculating the variety of glyphs per row primarily based on the glyph atlas width and glyph width. The one factor we have to be careful for is to not use any code factors that go above Z in our textual content strings.

This demo would not include a proof. Take into account it to be a puzzle to your mind noggins! Can you determine the way it works? 15_font_fun.c:

#embody <MiniFB.h>
#embody <stdlib.h>
#embody <string.h>
#embody <math.h>
#embody "r96/r96.h"

int major(void) {
	const int window_width = 320, window_height = 240;
	struct mfb_window *window = mfb_open("15_font_fun", window_width, window_height);
	r96_image output;
	r96_image_init(&output, window_width, window_height);
	r96_font font;
	r96_font_init(&font, "https://blinkingrobots.com/wp-content/uploads/2023/01/1673884287_588_Rendering-like-its-1996-Bitmap-fonts-and-DOS.png", 16, 16);
	float counter = 0;
	struct mfb_timer *timer = mfb_timer_create();
	do {
		r96_clear_with_color(&output, R96_ARGB(0xff, 0x22, 0x22, 0x22));

		const char *textual content = "--(2022 DEMO CREW)--";
		int32_t text_x = 0;
		uint32_t text_length = strlen(textual content);
		uint32_t char_index = 0;
		whereas (char_index < text_length) {
			char character[] = {0, 0};
			character[0] = (char) r96_next_utf8_code_point(textual content, &char_index, text_length);
			int32_t text_y = output.peak / 2 - font.glyph_width / 2 + (int32_t) (sinf(counter + char_index / 10.0f) * output.peak / 4);
			r96_text(&output, &font, character, text_x, text_y, 0xffffffff);
			text_x += font.glyph_width;
		}

		counter += M_1_PI * mfb_timer_delta(timer) * 12;
		mfb_timer_reset(timer);

		if (mfb_update_ex(window, output.pixels, window_width, window_height) != STATE_OK) break;
	} whereas (mfb_wait_sync(window));
	return 0;
}

And right here it’s in motion.

Subsequent time on “Mario writes a whole lot of phrases”

Our little code base is shaping as much as be kinda helpful. Subsequent time, we’ll look into drawing traces. Probably with sub-pixel precision. Until I am unable to determine that out.

Talk about this submit on Twitter or Mastodon.



Source Link

What's Your Reaction?
Excited
0
Happy
0
In Love
0
Not Sure
0
Silly
0
View Comments (0)

Leave a Reply

Your email address will not be published.

2022 Blinking Robots.
WordPress by Doejo

Scroll To Top