Skip to content

Instantly share code, notes, and snippets.

@khang06
Created July 5, 2022 06:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save khang06/74b1293c2ef4592dd1a3285487a1075b to your computer and use it in GitHub Desktop.
Save khang06/74b1293c2ef4592dd1a3285487a1075b to your computer and use it in GitHub Desktop.
Simple atlas generator for Proggy Clean (and other monospace bitmap fonts)
#include <Windows.h>
#include <gdiplus.h>
#include <stdio.h>
#pragma comment(lib,"gdiplus.lib")
using namespace Gdiplus;
// https://docs.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-retrieving-the-class-identifier-for-an-encoder-use
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid) {
UINT num = 0; // number of image encoders
UINT size = 0; // size of the image encoder array in bytes
ImageCodecInfo* pImageCodecInfo = NULL;
GetImageEncodersSize(&num, &size);
if (size == 0)
return -1; // Failure
pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
if (pImageCodecInfo == NULL)
return -1; // Failure
GetImageEncoders(num, size, pImageCodecInfo);
for (UINT j = 0; j < num; ++j)
{
if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0)
{
*pClsid = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j; // Success
}
}
free(pImageCodecInfo);
return -1; // Failure
}
int main() {
// Init GDI+
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
// Calculate how the texture will be generated
constexpr unsigned char_width = 8;
constexpr unsigned char_height = 8;
constexpr unsigned char_underhang = 4;
constexpr unsigned padding = 1;
constexpr unsigned per_row = 16;
constexpr unsigned rows = (128 - 32) / per_row + ((128 - 32) % per_row != 0);
unsigned tex_width = (char_width + padding) * per_row;
unsigned tex_height = (char_height + char_underhang + padding) * rows;
printf("width: %d, height: %d\n", tex_width, tex_height);
// Initialize the GDI objects
SolidBrush brush(Color(255, 255, 255, 255));
Bitmap atlas(tex_width, tex_height, PixelFormat32bppARGB);
Graphics gfx(&atlas);
FontFamily family(L"ProggyCleanTTSZ");
Font font(&family, 16, FontStyleRegular, UnitPixel);
// Render the atlas
gfx.Clear(Color(0, 0, 0, 0));
gfx.SetTextRenderingHint(TextRenderingHintSingleBitPerPixel);
wchar_t buf[2] = {};
for (int y = 0; y < rows; y++) {
for (int x = 0; x < per_row; x++) {
PointF point((char_width + padding) * x - 3.0f, (char_height + char_underhang + padding) * y - 2.0f);
buf[0] = (wchar_t)(y * per_row + x + 32);
gfx.DrawString(buf, -1, &font, point, &brush);
}
}
/*
// Save as a PNG file
CLSID png_clsid;
GetEncoderClsid(L"image/png", &png_clsid);
atlas.Save(L"atlas.png", &png_clsid, NULL);
*/
// Compress the atlas
Rect rect(0, 0, tex_width, tex_height);
BitmapData data;
atlas.LockBits(&rect, ImageLockModeRead, PixelFormat32bppARGB, &data);
unsigned compressed_size = tex_width * tex_height / 8 + (tex_width * tex_height % 8 != 0);
char* compressed = new char[compressed_size];
unsigned bits_left = tex_width * tex_height;
for (unsigned i = 0; i < compressed_size; i++) {
char output = 0;
for (int j = 0; j < min(8, bits_left); j++) {
output <<= 1;
output |= ((char*)data.Scan0)[(i * 8 + j) * 4] != 0;
}
compressed[i] = output;
bits_left -= 8;
}
atlas.UnlockBits(&data);
// Save it
FILE* output = fopen("atlas.bin", "wb");
fwrite(compressed, compressed_size, 1, output);
fclose(output);
printf("Done\n");
delete[] compressed;
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment