Document indexed image visualizer#47
Conversation
Co-Authored-By: paxcut <53811119+paxcut@users.noreply.github.com>
|
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. |
|
How would one construct an array with 4-bit indices, since the smallest integer type in pattern language is u8? |
|
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. |
Also add an example of visualizing an image with 4-bit indices array Co-Authored-By: paxcut <53811119+paxcut@users.noreply.github.com>
|
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? |
|
You have fully explained all the new available functionality very nicely. Thank you! |
|
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. |
|
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>
|
Please give example how to initialize local lookup table. Something like this approach not work |
|
I found the solution. Just use u32 instead of RGBA8 for palette and it work's. |
|
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. |
|
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. |
|
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 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)]]
; |
|
@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. |
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 In one of my other 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
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.) |
|
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. |
All credits go to Paxcut for his answer here: WerWolv/ImHex#2499
A step towards: #45