Creating a bitmap - SnowyMouse/invader GitHub Wiki

This guide will tell you how to create a bitmap tag using invader-bitmap.

Contents:

Making the color plate

A color plate is a bitmap file used as input to make a bitmap tag. This is the recommended way to make your bitmap, as this not only gives you control over sequences, but even if you don't plan on taking advantage of sequences, it isn't ambiguous what kind of input you're giving. This is because, while in some cases, you can use regular bitmaps as input, it isn't generally recommended, as some bitmaps may be mistakenly interpreted as a color plate.

Here is an example color plate:

Example color plate

Choosing an image editor

To create a color plate, you will need a raster image editor. Traditionally, Adobe Photoshop has been the main tool people have used in the past for editing Halo bitmaps, but there are free and open source options such as Krita or GIMP which will work just as well.

Please note that this tutorial will not teach you how to use an image editor. If you want to learn how to use Photoshop, Krita, or GIMP, there are many tutorials on the Internet.

Setting up the color plate

A color plate is a bitmap that contains all bitmaps to be put into the image editor. Therefore, you should create a bitmap that can fit all of your bitmaps in it.

If there is no valid color plate key, then the whole color plate is treated as a single bitmap.

Color plate key

A color plate can optionally have a color plate key. This is required for bitmap tags that have multiple bitmaps or sequences in it. A key can have up to three reserved colors:

  • Transparency - This is used for spacing out individual pixels
  • Sequence divider - This is used for separating bitmap sequences
  • Spacing - This is used for changing the horizontal registration point of a bitmap

Note: When checking if a pixel is one of the three reserved colors, the alpha channel is ignored. For example, if you use #0000FF as a transparency color, any pixels with #0000FF as the color will be treated as transparency regardless of their alpha channel or the color plate key's alpha channel.

Example colors

These colors are specific to the color plate, itself, and will be replaced with 100% transparent black if used in a bitmap. Therefore, you should pick three colors you do not intend to use in any bitmap. You do not need to specify all three colors (simply leave them as the same color as transparency), but there are a few requirements for the key to be valid:

  • All pixels after the spacing color must be the same as transparency
  • Spacing, if set, must not be the same color as the sequence divider
  • If sequence divider is not set, then transparency must be #0000FF

The color plate is read slightly differently if you do not set a sequence divider color:

  • Spacing will be set to #00FFFF regardless of if you try to set it to anything else
    • invader-bitmap will error if you try to set spacing to anything besides #00FFFF if sequence divider is not set, as this may produce unintended results if it simply ignored it like what tool.exe does
  • The transparency color will be treated as the sequence divider color

Note: All of your bitmaps must be a power-of-two width and height (e.g. 32x256) unless the bitmap type is set to Sprites (generates sprite sheets instead of bitmap sequences) or Interface Bitmaps (generates no mipmaps).

If you want to use a sequence divider, you can optionally fill the second row of the color plate with the sequence divider color. Then, when you want to make another bitmap sequence, create another horizontal row in between the sequences. If you aren't using a sequence divider, you can create sequences with horizontal rows of transparency color, instead.

Note: The sequence divider color is treated as transparency if the first pixel on a row is not the color. So, this color can be used for creating visual guides for editing purposes.

Colors used for spacing can be used to horizontally offset, while relative placement of the bitmap from the sequence divider vertically offsets the registration point. Registration point does not affect textures used for shading objects or level geometry, but it can affect HUD elements and particles.

Note: If you want a bitmap to be centered horizontally and vertically, do not use spacing colors and make sure the bitmap is vertically centered between the sequence borders. If the sequence borders are an even number of pixels, this is simple.

invader-bitmap supports lossless formats such as TIFF, PNG, and TGA. Saving as a PNG or TGA presents the least hassles, as libTIFF may have issues with certain TIFFs saved by image editors.

Note: To keep your tags organized, we recommend putting bitmaps inside of a folder called "bitmaps" unless the bitmap is used for lightmaps. For example, data/invader_tutorial/bitmaps/example_color_plate.png

Creating the bitmap tag

Now that you have your color plate, you will need to make your bitmap tag. You can either create it in a tag editor such as invader-edit-qt or with invader-bitmap. If the bitmap tag doesn't exist, you will need to supply a type. You may also want to supply a format, but this is optional.

Bitmap types

When creating a bitmap that doesn't exist, you will need to choose a bitmap type. You can do this in invader-bitmap with -T <type>.

Type Notes
2d-textures Basic texture; must be power-of-two
3d-textures Like 2D texture, but bitmap sequences are treated as depth
interface-bitmaps Like 2D texture, but no mipmaps or power-of-two
cube-maps Like 2D texture, but six textures form a face (see Cube maps)
sprites Like 2D texture, but no power-of-two requirement & sprite sheets are generated (see Sprites)

Bitmap formats

In some cases, when creating a new bitmap tag, you may want to choose a format. By default, 32-bit color will be used, so this step is optional in most cases.

Uncompressed bitmap formats

These formats are uncompressed and use explicit (not interpolated) RGB and/or alpha values. In many cases, this results in higher quality bitmaps than using any of the block compressed formats, but it comes with a file size tradeoff.

The exact storage format used will depend on the bitmap:

  • If 32-bit is specified and all pixels have 100% alpha, then X8R8G8B8 is used. Otherwise, A8R8G8B8 is used. This means that using alpha does not come at any cost of color depth.
  • If 16-bit is specified and all pixels have 100% alpha, then R5G6B5 is used. If pixels have either 0% or 100% alpha, A1R5G5B5 is used. Otherwise, A4R4G4B4 is used. This means that using alpha comes at the cost of color depth.
Format Storage Bits/px Alpha Red Green Blue Notes
32-bit A8R8G8B8 32 8-bit 8-bit 8-bit 8-bit
32-bit X8R8G8B8 32 8-bit 8-bit 8-bit 100% alpha (opaque)
16-bit R5G6B5 16 5-bit 6-bit 5-bit 100% alpha (opaque)
16-bit A1R5G5B5 16 1-bit 5-bit 5-bit 5-bit
16-bit A4R4G4B4 16 4-bit 4-bit 4-bit 4-bit

32-bit color and monochrome (with monochrome input) have zero quality loss. Unfortunately, Halo PC does not support monochrome, and it'd easily be the best choice for monochrome textures like HUD masks. So, the best format with zero loss in quality is 32-bit color. Obviously, this results in a larger file size, with a 1024x1024 bitmap taking up ~5.3 MiB and a 2048x2048 bitmap taking up ~21.3 MiB, with mipmaps included for both. Most bitmaps aren't going to be this big, so using 32-bit bitmaps for every single bitmap in a map file will probably only result in your map being around 200-400 MiB depending on the size of the map.

16-bit color works well on some textures without any noticeable impact, such as noise maps and some simple textures, but it is subject to banding due to a lack of color depth. The color depth also gets significantly worse if you use an alpha channel and even worse if you use more than 1-bit alpha. Dithering can be used to mitigate color banding, resulting in some textures looking even better than DXT thanks to no block artifacting.

Block-compressed bitmap formats

These formats utilize block compression. Basically, each bitmap is separated into 4x4 blocks, and each block has two 16-bit (R5G6B5) colors which are interpolated at runtime. This provides massive space savings but at a significant loss in quality, but there may be less color banding than explicit 16-bit color.

There is no difference in RGB quality between dxt1, dxt3, or dxt5. The only difference is in how alpha is handled. Therefore, if dxt3 or dxt5 is specified and a bitmap does not have transparency, then dxt1 will automatically be used to keep the size as small as possible.

Format Bits/px Alpha Notes
DXT1 4 Can optionally use 1-bit alpha
DXT3 8 4-bit explicit Better for shapes like HUDs
DXT5 8 4-bit interpolated Better for alpha gradients like clouds

dxt1 is very lossy compression, and the image will lose finer details as a result and may have block artifacting even with the best dxt1 compressors - issues that even 16-bit often does not have. As such, it's quite bad on HUDs, detail maps, bumpmaps, and multipurpose maps, and, in our opinion, literally anything else. dxt3 and dxt5 use dxt1 for color, only differing in how alpha works. Alpha is explicit on dxt3, thus dxt3 works better for things that need definite shape such as HUD backgrounds. It will result in banding on gradients, however. dxt5 compresses alpha to be interpolated like the color, so it works better for more things than dxt3.

That being said, it provides a large space savings over 32-bit color and may be useful for creating maps for the Xbox due to the strict file size and memory limits.

More bitmap formats

These formats only work on the Xbox version of Halo: Combat Evolved and the MCC version of Halo: Combat Evolved Anniversary. If generating a monochrome bitmap with a monochrome bitmap as input, then there will be no loss in quality, allowing for space savings over 32-bit or sometimes even 16-bit.

Format Storage Bits/px Alpha RGB Notes
monochrome A8Y8 16 8-bit 8-bit
monochrome AY8 8 8-bit (8-bit) luminosity = alpha
monochrome A8 8 8-bit 100% luminosity (white)
monochrome Y8 8 8-bit 100% alpha (opaque)
palettized P8 8 Indexed Indexed bump compression only

Which bitmap format should I use?

This is simple:

  • If you only intend to support MCC, use auto (-F auto). This will give you the best, lossless version of your bitmap.
  • If you also (or only) intend to support Halo PC, use 32-bit color (-F 32-bit).
  • If you only intend to support the Xbox version:
    • Use DXT1 for textures that don't have alpha or 1-bit alpha.
    • Use DXT3 for things that have definite shapes that need to be preserved in alpha, and DXT5 for things like alpha gradients (-F dxt1, -F dxt3, and -F dxt5).
    • Use monochrome for most monochrome textures. While DXT1 can provide a 50% size reduction over Y8 and DXT3/DXT5 can provide a 50% size reduction over A8Y8, DXT3/DXT5 provide no savings over A8/AY8, and for most textures, the quality loss is not worth the size reduction.
    • If you are generating a height or vector map on Xbox, never use DXT as it has block artifacting that can end up being extremely visible. Instead, use height map compression (P8) on 32-bit color.

Generating the bitmap tag

You can generate your bitmap tag using invader-bitmap. If the bitmap tag exists, any settings used in that will be re-used in invader-bitmap unless overridden with a command-line argument.

For example, if your bitmap is at invader_tutorial/bitmaps/example_color_plate, you can use this command to generate the bitmap tag:

invader-bitmap invader_tutorial/bitmaps/example_color_plate

By default, invader-bitmap will create a 32-bit 2D texture with the default usage. You can see the other options by using the -h argument.

If everything was done correctly, you will see output such as this:

Found 5 bitmaps:
    Bitmap #0: 64x64, 6 mipmaps, X8R8G8B8 (32-bit color) - 0.021 MiB
    Bitmap #1: 64x64, 6 mipmaps, X8R8G8B8 (32-bit color) - 0.021 MiB
    Bitmap #2: 128x64, 7 mipmaps, X8R8G8B8 (32-bit color) - 0.042 MiB
    Bitmap #3: 128x64, 7 mipmaps, X8R8G8B8 (32-bit color) - 0.042 MiB
    Bitmap #4: 32x64, 6 mipmaps, X8R8G8B8 (32-bit color) - 0.010 MiB
Total: 0.135 MiB

Previewing the bitmap tag

You should preview your bitmap to make sure everything looks correct.

In invader-edit-qt, you can navigate to your bitmap. Alternatively, you can quickly open your bitmap tag in invader-edit-qt from the command line like this:

invader-edit-qt invader_tutorial/bitmaps/example_color_plate.bitmap

Once you've opened your bitmap tag, click "Preview bitmap" at the top of the window and you will see your bitmaps.

Other bitmap types

The above guide applies to basic 2D textures, sprites, interface bitmaps, and 3D textures. However, there are other types of bitmaps you may want to create.

Cube maps

Cube maps are textures used for environment mapping (e.g. fake reflections). A cube map is composed of six 2D textures. You can input cube maps by supplying a color plate with the cube map as a sequence or an unrolled cube map bitmap. For example, these two input bitmaps will result in the same cube map:

Example color plate cube map

Example unrolled cube map bitmap

Sprites

Sprites are non-power of two bitmaps that are baked onto a power-of-two sprite sheet. This has a few advantages:

  • Hardware that has issues with non-power-of-two textures can use sprites.
  • Power-of-two sprite sheets can have mipmaps, thus they can be used with texture filtering. However, they may only have up to two mipmaps.
  • Because multiple sprites are in a sprite sheet (which is all one texture), fewer textures have to be loaded in the GPU.

These are generally used for HUD elements, HUD meters, detail objects, and particles.

Budgeting

You can control the maximum size and number of bitmaps by tweaking the sprite budget parameters. It may require some manual work, but it may be useful for saving more space. However, you may run the risk of having sprite sequences not being fully contained in one bitmap which can potentially cause performance or graphical issues, or it may even cause the bitmap to not generate at all.

Setting budget count to 0 will ensure that all sprites in a sprite sequence are on the same sheet, often times putting everything on the same sprite sheet.

Budgeting on:

$ invader-bitmap ui/hud/bitmaps/combined/hud_reticles -R -C 64 -B 64
Found 15 bitmaps:
    Bitmap #0: 64x64, 0 mipmaps, AY8 (8-bit monochrome) - 0.004 MiB
    Bitmap #1: 64x64, 0 mipmaps, AY8 (8-bit monochrome) - 0.004 MiB
    Bitmap #2: 64x64, 0 mipmaps, AY8 (8-bit monochrome) - 0.004 MiB
    Bitmap #3: 64x64, 0 mipmaps, AY8 (8-bit monochrome) - 0.004 MiB
    Bitmap #4: 64x64, 0 mipmaps, AY8 (8-bit monochrome) - 0.004 MiB
    Bitmap #5: 64x64, 0 mipmaps, AY8 (8-bit monochrome) - 0.004 MiB
    Bitmap #6: 64x64, 0 mipmaps, AY8 (8-bit monochrome) - 0.004 MiB
    Bitmap #7: 64x64, 0 mipmaps, AY8 (8-bit monochrome) - 0.004 MiB
    Bitmap #8: 64x64, 0 mipmaps, AY8 (8-bit monochrome) - 0.004 MiB
    Bitmap #9: 64x64, 0 mipmaps, AY8 (8-bit monochrome) - 0.004 MiB
    Bitmap #10: 64x64, 0 mipmaps, AY8 (8-bit monochrome) - 0.004 MiB
    Bitmap #11: 64x64, 0 mipmaps, AY8 (8-bit monochrome) - 0.004 MiB
    Bitmap #12: 64x64, 0 mipmaps, AY8 (8-bit monochrome) - 0.004 MiB
    Bitmap #13: 64x64, 0 mipmaps, AY8 (8-bit monochrome) - 0.004 MiB
    Bitmap #14: 64x64, 0 mipmaps, A8Y8 (16-bit monochrome) - 0.008 MiB
Total: 0.062 MiB

Budgeting off:

$ invader-bitmap ui/hud/bitmaps/combined/hud_reticles -R -C 0
Found 1 bitmap:
    Bitmap #0: 512x256, 0 mipmaps, A8Y8 (16-bit monochrome) - 0.250 MiB
Total: 0.250 MiB

In this case, over 192 KiB was saved by creating 14 extra bitmaps instead of fitting all of the reticles in one large bitmap.

Spacing and margins

Spacing is determined by two things:

  • Mipmap count
  • Sprite placement

If there is only one mipmap, then spacing is set to 1 pixel on the edge. Otherwise, spacing is set to 4 pixels on the edge. However, if a sprite is on a sprite sheet by itself, then it may not have any spacing applied to it. This may affect some HUD elements, and as inconsistent as it is since you may not have any direct control over if a sprite ends up in a sprite sheet by itself, this behavior is required for stock Bungie HUDs to work properly.

In the future, a flag may be added to disable adding margins, but this is only being considered and is not an option right now, nor is it guaranteed to be one.

Sprite usage

Sprite usage is mainly for specifying how the sprite will be used. In sprite generation, it determines the sprite sheet background.

Value Background Notes
blend add subtract max Black, fully transparent Default; used for most sprites, including HUD elements, particles, and detail objects
multiply min 50% gray, 50% transparent Useful for decals; try this if you see black borders on bullet holes
double multiply White, fully opaque Useful for decals; alpha blends sprite pixels over white instead of replace
⚠️ **GitHub.com Fallback** ⚠️