Skip to content

Document indexed image visualizer#47

Merged
WerWolv merged 3 commits intoWerWolv:masterfrom
C3pa:Document-indexed-image-visualizer
Dec 11, 2025
Merged

Document indexed image visualizer#47
WerWolv merged 3 commits intoWerWolv:masterfrom
C3pa:Document-indexed-image-visualizer

Conversation

@C3pa
Copy link
Copy Markdown
Contributor

@C3pa C3pa commented Nov 16, 2025

All credits go to Paxcut for his answer here: WerWolv/ImHex#2499

A step towards: #45

Co-Authored-By: paxcut <53811119+paxcut@users.noreply.github.com>
@paxcut
Copy link
Copy Markdown
Contributor

paxcut commented Nov 17, 2025

Thanks you for adding this. Perhaps it should also be mentioned that besides the example that uses 8 bit indexes (256 colors), they can also be 4 (16 colors) or 16 bits (65536 colors) and that the lut has to always be 32 bit colors (if other colors size is used it can be transformed using transform attribute). I can try to cook some example that includes one of these variations for completeness sake.

@C3pa
Copy link
Copy Markdown
Contributor Author

C3pa commented Nov 17, 2025

How would one construct an array with 4-bit indices, since the smallest integer type in pattern language is u8?

@paxcut
Copy link
Copy Markdown
Contributor

paxcut commented Nov 19, 2025

The image is NxM indices. For example if you have a 16x32 image and the image is 16x16 bytes then each index must be 4 bits. Like I mentioned above, I could try to write a complete simple example of some very small input file that shows how to deal with the parts that may need some explaining like 4 bit indices and colors that are not rgba8.

To answer your question directly it is not necessary to create an array of 4 bit elements. As long as the size is the expected number of bytes you can use and type of integer array that contain said number of bytes.
Of course nothing stops you from defining a bitfield of 4 bits and creating an array of them that can also be used.

Also add an example of visualizing an image with 4-bit indices array

Co-Authored-By: paxcut <53811119+paxcut@users.noreply.github.com>
@C3pa
Copy link
Copy Markdown
Contributor Author

C3pa commented Nov 19, 2025

I added another example with 4-bit indices to better illustrate your point. Also, I mentioned that clut has to be in RGBA format. Do you have any other concerns?

@paxcut
Copy link
Copy Markdown
Contributor

paxcut commented Nov 19, 2025

You have fully explained all the new available functionality very nicely. Thank you!

@paxcut
Copy link
Copy Markdown
Contributor

paxcut commented Nov 19, 2025

maybe a minor point. Reading the explanation it talks about using transform attribute and then it says see example below but none of the examples show how to use the attribute.

@paxcut
Copy link
Copy Markdown
Contributor

paxcut commented Nov 19, 2025

In case you wanted to add some code to showcase using the transform attribute this is how it was used in a ps2 format to convert rgba5551 to rgba8

bitfield RGBA5551 {
    r : 5;
    g : 5;
    b : 5;
    a : 1;
}[[sealed,transform("to_u32"),format("as_u32")]];

fn uq(auto v) { //unquantize
    return u32(float(v)*255f/31f);
};

fn to_u32(auto v) {
    return uq(v.r)+(uq(v.g)<<8)+(uq(v.b)<<16)+0xFF000000;
};

fn as_u32(auto v) {
    return std::format("{:#x}",v);
};

struct Image{
   // all the other stuff
    RGBA5551 colors[W*H];
}[[visualization]];

Also make the indentation consistent with the other examples.

Co-Authored-By: paxcut <53811119+paxcut@users.noreply.github.com>
@fersatgit
Copy link
Copy Markdown

fersatgit commented Nov 26, 2025

Please give example how to initialize local lookup table.

Something like

const type::RGBA pal[16]={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};

this approach not work

u32 _pal[16]={0xFF000000,0xFFAA0000,0xFF00AA00,0xFFAAAA00,0xFF0055AA,0xFFAAAAFF,0xFF55AAFF,0xFFAAAAAA,0xFF555555,0xFFFF5555,0xFF55FF55,0xFFFFFF55,0xFF5555FF,0xFFFF55FF,0xFF55FFFF,0xFFFFFFFF};
type::RGBA8 pal[16] @ addressof(_pal);

@fersatgit
Copy link
Copy Markdown

I found the solution. Just use u32 instead of RGBA8 for palette and it work's.

@paxcut
Copy link
Copy Markdown
Contributor

paxcut commented Nov 26, 2025

You have found a way to circumvent a bug in ImHex that fails to use local variables that are non-custom types inside functions. Using a built-in type may work now but it is not guaranteed to work always. Instead the only way that will always work is to create a section where you create the data and place variables there. Not only that will always work, but you will be able to use any non-custom type like rgba8 or any other. Local variables are not meant to be used as sources for patterns so if your input file doesn't have the data you need, you should use a section to create it manually.

@paxcut
Copy link
Copy Markdown
Contributor

paxcut commented Nov 26, 2025

I see now that the example using rgba5551 was added. This is now perfect from my point of view. Anybody with write access to this repository can now merge it if they have no objections.

@tinktinp
Copy link
Copy Markdown

Hi. I didn't see this until today.

I made some patterns for old Midway games (mainly Mortal Kombat), and made use of the feature.

https://github.com/tinktinp/hexpat/blob/main/includes/img.hexpat

Since I didn't have these docs, I ended up doing things in a different and probably worse way, but I thought I'd share my approach.

I don't know that anything in there is a "good example", but if it is, or even if you just need an example of what not to do, feel free to use it.

I don't think I've used a transformer before, and it didn't occur to me to use one for this, so I created a section instead.

    fn paletteToRgba32(auto name, ref auto inData, u32 len) {
        std::mem::Section palSection = std::mem::create_section(name);
        std::mem::set_section_size(palSection, len * 4);

        u32 sectionData[len] @ 0x0 in palSection;

        u8 a = 0xff;
        for (u8 i = 0, i < len, i = i + 1) {
            u16 d = inData[i];
            u16 r = 8 * ((d >> 10) & 0x1f);
            u16 g = 8 * ((d >> 5) & 0x1f);
            u16 b = 8 * ((d >> 0) & 0x1f);
            sectionData[i] = (a << 24) | (r << 0) | (g << 8) | (b << 16);
        }
        sectionData[0] = sectionData[0] & ~0xFF000000; // zero alpha for index 0
        return palSection;
    };

Because the file format has a section of palettes and a section of images (pixel data), I also had a lot of trouble finding a place from which I had access to both the pixels and the palettes. To me it would make more sense to put the visualizer on the u8 data[dataSize] @ imageDataOffset; or the containing ImgHeader struct.

But I couldn't figure out how to get at the palettes from there, so I ended up creating a dummy struct just to put the annotation on:

    struct ImgWithPaletteVisualize {
        u32 i = std::core::array_index();
 
        u32 width = (parent.imgHeaders[i].xSize + 3) & ~0x3;
        u32 height = parent.imgHeaders[i].ySize;
        u8 data[width * height] @ addressof(parent.imgHeaders[i].data);
        u32 paletteIndex = parent.imgHeaders[i].palette - 3;
        auto paletteSection = parent.paletteHeaders[paletteIndex].rgba32Section;
        u32 numberOfColors = parent.paletteHeaders[paletteIndex].numberOfColors;

        u32 palette[numberOfColors] @  0x0 in paletteSection [[export]];
    }
    [[sealed, hex::visualize("bitmap", data, width, height, palette)]]
    ;

@paxcut
Copy link
Copy Markdown
Contributor

paxcut commented Nov 28, 2025

@tinktinp Using a transform is preferable to using a section because it is faster and uses less memory but it is true that most colormap tables are too small to care for that. I will also argue that using a transform makes the code simpler and easier to read. you also end using only one place to store all the values instead of having some data here and other there. I also wanted to point out that the method used to convert 5 bit colors to 8 bit colors fails to produce full colors in the 32 bit version.

converting 5 bits to 8 bits can be thought of as going from 2^5 (2 raise to the power of 5) or 32 to 2^8 or 256 and 256 divided by 32 is 8 so as done in your code you can simply multiply the 5 bit number extracted from the 5551 color by 8 to get the 8 bit equivalent, but that's not exactly what is needed.

The maximum number that you can make with 5 bits is 31 (2^5-1) and with 8 bits it is 255 (another way of saying this is that 32 colors go from 0 to 31 and 256 go from 0 to 255). If you had a fully red color with all 5 bits set to 1 then its value in decimal would be of 31. Multiplying it by 8 would create a color of decimal value 248 and not the 255 one would expect from a fully red 8 bits. The difference can be noticeable and this sort of error is known as the quantization error where an error of one becomes much bigger due to scaling. The only way I know of to obtain the correct colors is to use floating point arithmetic and doing the explicit multiplication of 255.0f / 31.0f as it is being done in the transform.

@tinktinp
Copy link
Copy Markdown

@tinktinp Using a transform is preferable to using a section because it is faster and uses less memory but it is true that most colormap tables are too small to care for that. I will also argue that using a transform makes the code simpler and easier to read. you also end using only one place to store all the values instead of having some data here and other there. I also wanted to point out that the method used to convert 5 bit colors to 8 bit colors fails to produce full colors in the 32 bit version.

Fair enough. Like I said, I haven't ever used a transform and so it didn't occur to me to use one here. Hopefully this PR gets merged soon so no one else has to figure it out (badly) as they go.

I agree that transform is cleaner. I'm surprised that it's faster and uses less memory. What would I have needed to know to realize the space and time performance issues with my approach? Might be good to add to the docs (if not already there).

I also like how the example uses a bitfield instead of extracting bits with bit shifting like I did.

In one of my other .hexpat files, https://github.com/tinktinp/hexpat/blob/main/includes/mkt_types.hexpat#L10-L15 which I worked on first, I wasn't quite able figure it out yet. But I did use [[sealed, color(std::format("{0:02X}{1:02X}{2:02X}", r * 8, g * 8, b * 8))]]; to color code the display as the palette, which I thought was pretty neat.

I also couldn't get the built in RGBA type to work correctly for some reason so I made my own. Not sure if that was a problem with it or if I just didn't know what I was doing.

But I bring it up because I notice the example in this PR also creates a new bitfield instead of using std::color. Is there a reason why? And if so, is that something worth documenting?

converting 5 bits to 8 bits can be thought of as going from 2^5 (2 raise to the power of 5) or 32 to 2^8 or 256 and 256 divided by 32 is 8 so as done in your code you can simply multiply the 5 bit number extracted from the 5551 color by 8 to get the 8 bit equivalent, but that's not exactly what is needed.

You know, I had actually realized that (there were some 4bpp palettes I ran into which caused me to think it through). And then I forgot about it, so that's for reminding me!

I had copied that approach from another source, so I was meaning to check with them if there was a reason or not they did it that way. (Your math is certainly more correct, but in my case the palette is for an old arcade game, so it matters more how that game displayed it than how that game should have displayed it.)

I don't see why I need to convert to float to do that though. As long as I multiply first, won't I get the same result with integers? (Unless your conversion back to u32 is doing some rounding instead of truncating the fractional part.)

@paxcut
Copy link
Copy Markdown
Contributor

paxcut commented Nov 28, 2025

Using section to create 32 bit version of the 16 bit lut uses all that extract memory to store the results of the conversions from 16 to 32 bits. Using the transform only uses one calculation channel and the result is not stored anywhere and is sent directly to the visualizer. it is also faster because the way the pattern language is implemented as an interpreted language makes code that relies on loops very slow. In this case it is no big deal because lookup tables are generally speaking very small.

Imhex tries its best to provide enough already made types to show you ways in which the language features can be used in specific examples. All the rgb types are derived from a general rgb template that lets you specify how many bits to assign to ach color channel. You can use the built in one if it is good enough for you or you can write your own with customization made for your usage like the transform that was used to convert it to 32 bits. So the library is design to show you how to write your own types more than a complete set of types that you can use for every occasion.

You are correct that floating point math is not required but do do have to mind the order of evaluations to ensure the correct answer is obtained. Using floating point guarantees the right answer regardless of how you write the expression so it is just convenient.

@WerWolv WerWolv merged commit 435c8e9 into WerWolv:master Dec 11, 2025
1 check passed
@C3pa C3pa deleted the Document-indexed-image-visualizer branch December 12, 2025 18:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants