Skip to content

Write a BMP→array converter for hardcoded 1bpp sprites? #8

@nmlgc

Description

@nmlgc

TH01 pellets are coming up next, and for the first time, we'll have the chance to move hardcoded sprite data from ASM land to C land. This raises the question of when we are going to do this The Right And Moddable Way, by auto-converting actual image files into ASM or C arrays during the build process. This converter would also automatically generate pre-shifted versions of any sprites that need them, without requiring modders to store the 8 versions of those sprites manually.

Such a minor thing is currently not covered by any of the subscriptions, probably to this missing option in the order form. Since it requires no RE skills and also collides with nothing else, it would be the perfect thing to be done by an outside contributor who is merely familiar with C/C++… if we still have any left, that is.
Any backers with outstanding contributions in the backlog might also choose to put their contribution towards that, in case they think that this is more valuable than more reverse-engineering at this point. Otherwise, I'll just hardcode TH01's pellets directly, for the time being.

Since TH05's gaiji are also stored in this hardcoded 1bpp form, I also consider this converter as a prerequisite for fully rebuilding TH05's ZUN.COM, as mentioned in the 2020-02-23 blog post. As it turns out though, the OMF .OBJ output mentioned in that post won't be necessary after all. Most of these sprite declarations lie in the middle of a translation unit anyway, and therefore need to be #included. Same for ASM land.


Some more, unsorted details I had in mind - everything here is still up for discussion though:

  • Ideally, we'd only have to read 1bpp ("monochrome") .BMP files, as saved by MS Paint – any non-1bpp formats are handled by other, non-hardcoded image formats, and are out of scope here. However, Paint is pretty much deprecated, and has a quite nasty rendering bug in 1bpp mode on my system:

    Paint 1bpp sprite bug
    (It's the exact spark sprite sheet from below, but some dots just aren't rendered?)

    It's also been getting more and more difficult to save this format from other tools. Therefore, the converter should also accept all other common uncompressed .BMP bit depths – but, of course, error out whenever any color other than #000000 or #ffffff is found.

  • This should simultaneously compile into a 16- and 32-bit binary, right from the start. The 32-bit build step already requires the ASM arrays to be present, as does the 16-bit build step for C arrays.

  • Yes, technically it wouldn't be necessary to compile this into a 16-bit Real Mode DOS program, since we could already output the C arrays during ReC98's 32-bit build step. However, people are looking forward to the eventual 16-bit-only build process, and I agree that it makes no sense to have the PC-98-only master depend on a 32-bit C++ compiler just for things like these.

  • Therefore, aim for maximum portability right from the start - the converter should compile with anything from Turbo C++ 4.0J up to modern Visual Studio and GCC/Clang compilers, so that future port authors have one less thing to worry about. Now, I could force C89 onto people now, but I really don't see why I should 🙂

    ReC98 itself is going to use the freeware Borland C++ 5.5 (from the infamous freecommandLinetools.exe) as its default 32-bit Windows compiler; I've just added that one to the DevKit. Turbo C++ 4.0J defines the subset of allowed C++ features anyway, and BCC55 still happens to be the most hassle- and bloat-free way to get any sort of 32-bit Windows C++ compiler to people. (Open Watcom unfortunately only comes second here.)

  • Rely on platform.h in the top level of this repo, adding new things there as necessary. For modern, sane compilers, we'd ideally only need to #include <stdint.h> there? I hope.

  • For integration into the future build system, the converter should be written as a library with a C/C++ API.

  • My original plan would have involved parsing sprite dimensions from a C header, recognizing the dots*_t types, as well as macros like PRESHIFT to figure out where to put the pre-shifted data, or *_H to indicate the height. However, that would not only significantly raise the complexity of this, it also can't communicate all necessary information: TH03's in-game score sprites have their dots stored upside down, but we'd obviously like to have them the right way around in the .BMP file. (Not to mention the fact that TH05's zun -G is written completely in ASM and doesn't need a C header anyway.)

  • 💡 And then you notice that having a C++ build system allows you to just #include the sprite declarations from their actual header, and then pass the necessary numbers to the converter via sizeof(). So, the converter is free to only output raw numbers for the array definition. Adding per-dimension initializer lists will catch even more errors at compile-time, and mzdiff will catch the rest 🙂

  • 💡 And then you notice that this doesn't depend on the build system at all. We can simply use the converter's main() function to call the conversion API for every sprite sheet in ReC98, with "hardcoded" paths, and run that from the Makefile. Then, we don't even need to design a command-line frontend.

  • No further dependencies – yes, no master.lib, but also no win32_utf8. The only encoding-sensitive call should be fopen(), so let's just handle Win32 Unicode via an overload for wchar_t.

  • The dots*_t types limit the width of individual sprites to 8, 16, or 32 dots, you don't need to support any other sprite widths. Pre-shifted sprites can even be restricted to 8 dots.

  • Inside the .BMP files, cels can be arranged in any left-to-right, top-to-bottom order. Their individual positions are then derived according to the dimensions of the input file. An example from TH05's gaiji sprite sheet:

    • Sprite size: 16×16
    • Sprite count: 256
    • Input .BMP size: 256×256
    • → Top-left corner of sprite #174 (0xAE) is at position ((0xAE % 16) * 16, ⌊0xAE / 16⌋ * 16) = (224, 160)
  • Preliminary file extensions for the generated files could be .csp and .asp. The future build system mandates that those are different from regular .cpp or .asm, because that's how it selects the compiler. Having distinct extensions also works well with .gitignore.

  • Whoever writes it gets to name it. (Within reason, of course…) 6 letters max, since we need to suffix it with 16 and 32 – if a directory contains both a .COM and a .EXE with the same name, even modern 64-bit Windows runs the .COM binary by default…

  • And, of course, it should output the obligatory // Generated by ??? from ???.bmp, do not modify directly comment in the first line of the file.


Currently, the converter would have to work correctly with the following sprites, and output the same arrays we currently got in the repo:

Game .BMP file C declaration / data layout Note
1 TH01 pellets dots16_t sPELLET[3][PRESHIFT][8];
2/4/5 TH02 pellets dots16_t sPELLET[PRESHIFT][8]; Reusing TH01's bitmap is probably not worth the complication of somehow getting rid of TH01's two decay sprites, which are no longer used in later games?
2/4/5 TH02 sparks dots16_t sSPARKS[PRESHIFT][8][8];
2 TH02 point numerals dots16_t sPOINTNUMS[13][8]; Yes, there's a 13th empty 8×8 sprite at the end. It's actually used in place of the multiplier, if the multiplier would be ×1. :zunpet:
3 TH03 score font dots8_t sSCORE[10][8]; Emitted data needs to be stored upside-down.
4/5 TH04 gray bottom part of pellets dots16_t sPELLET_BOTTOM[PRESHIFT][4]; Bottom gray part of pellets.
4/5 TH04 point numerals dots16_t sPOINTNUMS[12][PRESHIFT][8]; Also not worth reusing the first 10 sprites of TH02's bitmap.
5 TH05 gaiji dots16_t sGAIJI[256][16];
5 TH05 piano label font dots8_t sPIANO_LABEL[9][8];

(Technically, we'd also have to add the piano sprites for TH05's Music Room, but these are stored and used in such a compressed way that it defeats the purpose of storing them as bitmaps.)

Metadata

Metadata

Labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions