feat: Extended bitmap visualizer to handle indexed colormaps (#1901)

### Problem description
Older image format use to store color values in a small lookup table so
that the image could simply store an index to the table. The pattern
language is not designed to handle this sort of operation which makes
this extension necessary to be able to visualize images of this type.

### Implementation description

The changes add an optional parameter to the bitmap visualizer which
holds the color lookup table. If the image contains values that are
outside the range of possible colors then the first color in the map is
chosen. The dimensions of the image can be equal to or smaller than
rows*columns depending on how the indices to the color map are stored.
For example, you can use 4 bit indices which would make the image half
the size (in bytes) but that limits the number of colors to 16. To store
colors in sizes larger than one byte use an array of the appropriate
sized integers.

### Screenshots


![image](https://github.com/user-attachments/assets/d068cb7e-3ff3-450d-8ac2-1bfc6e38043f)
This commit is contained in:
paxcut
2024-09-15 06:16:36 -07:00
committed by GitHub
parent 3c060cc57a
commit 057543da15
2 changed files with 102 additions and 4 deletions

View File

@@ -22,7 +22,7 @@ namespace hex::plugin::visualizers {
ContentRegistry::PatternLanguage::addVisualizer("line_plot", drawLinePlotVisualizer, ParamCount::exactly(1));
ContentRegistry::PatternLanguage::addVisualizer("scatter_plot", drawScatterPlotVisualizer, ParamCount::exactly(2));
ContentRegistry::PatternLanguage::addVisualizer("image", drawImageVisualizer, ParamCount::exactly(1));
ContentRegistry::PatternLanguage::addVisualizer("bitmap", drawBitmapVisualizer, ParamCount::exactly(3));
ContentRegistry::PatternLanguage::addVisualizer("bitmap", drawBitmapVisualizer, ParamCount::between(3, 4));
ContentRegistry::PatternLanguage::addVisualizer("3d", draw3DVisualizer, ParamCount::between(2, 6));
ContentRegistry::PatternLanguage::addVisualizer("sound", drawSoundVisualizer, ParamCount::exactly(3));
ContentRegistry::PatternLanguage::addVisualizer("coordinates", drawCoordinateVisualizer, ParamCount::exactly(2));

View File

@@ -7,6 +7,9 @@
#include <hex/ui/imgui_imhex_extensions.h>
namespace hex::plugin::visualizers {
std::vector<u32> getIndices(pl::ptrn::Pattern *colorTablePattern, u128 width, u128 height);
ImGuiExt::Texture getTexture(pl::ptrn::Pattern *colorTablePattern, std::vector<u32>& indices, u128 width, u128 height);
void drawImageVisualizer(pl::ptrn::Pattern &, bool shouldReset, std::span<const pl::core::Token::Literal> arguments) {
static ImGuiExt::Texture texture;
@@ -40,9 +43,22 @@ namespace hex::plugin::visualizers {
auto pattern = arguments[0].toPattern();
auto width = arguments[1].toUnsigned();
auto height = arguments[2].toUnsigned();
bool hasColorTable = false;
auto data = pattern->getBytes();
texture = ImGuiExt::Texture::fromBitmap(data.data(), data.size(), width, height, ImGuiExt::Texture::Filter::Nearest);
if (arguments.size() == 4) {
auto colorTablePattern = arguments[3].toPattern();
if (colorTablePattern->getSize() > 0) {
auto indices = getIndices(pattern.get(), width, height);
texture = getTexture(colorTablePattern.get(), indices, width, height);
hasColorTable = true;
}
}
if (!hasColorTable) {
auto data = pattern->getBytes();
texture = ImGuiExt::Texture::fromBitmap(data.data(), data.size(), width, height,ImGuiExt::Texture::Filter::Nearest);
}
}
if (texture.isValid())
@@ -50,6 +66,7 @@ namespace hex::plugin::visualizers {
if (ImGui::IsWindowHovered()) {
auto scrollDelta = ImGui::GetIO().MouseWheel;
if (scrollDelta != 0.0F) {
scale += scrollDelta * 0.1F;
scale = std::clamp(scale, 0.1F, 10.0F);
@@ -57,4 +74,85 @@ namespace hex::plugin::visualizers {
}
}
}
template <typename T> ImGuiExt::Texture unmapColors(pl::ptrn::Pattern *colorTablePattern, std::vector<u32>& indices, u128 width, u128 height) {
std::vector<T> colorTable = patternToArray<T>(colorTablePattern);
auto colorCount = colorTable.size();
auto indexCount = indices.size();
std::vector<T> image(indexCount);
for (u32 i = 0; i < indexCount; i++) {
auto index = indices[i];
if (index >= colorCount)
index = 0;
image[i] = colorTable[index];
}
void *tmp = image.data();
ImU8 *data = static_cast<ImU8 *>(tmp);
ImGuiExt::Texture texture = ImGuiExt::Texture::fromBitmap(data, indexCount*sizeof(T), width, height, ImGuiExt::Texture::Filter::Nearest);
return texture;
}
std::vector<u32> getIndices(pl::ptrn::Pattern *pattern, u128 width, u128 height) {
auto indexCount = 2 * width * height / pattern->getSize();
std::vector<u32> indices;
auto *iterable = dynamic_cast<pl::ptrn::IIterable *>(pattern);
if (iterable == nullptr || iterable->getEntryCount() <= 0)
return indices;
auto content = iterable->getEntry(0);
auto byteCount = content->getSize();
if (byteCount >= indexCount && indexCount != 0) {
auto bytePerIndex = byteCount / indexCount;
if (bytePerIndex == 1) {
auto temp = patternToArray<u8>(pattern);
indices = std::vector<u32>(temp.begin(), temp.end());
} else if (bytePerIndex == 2) {
auto temp = patternToArray<u16>(pattern);
indices = std::vector<u32>(temp.begin(), temp.end());
} else if (bytePerIndex == 4) {
auto temp = patternToArray<u32>(pattern);
indices = std::vector<u32>(temp.begin(), temp.end());
}
} else if (indexCount != 0) {
auto indicesPerByte = indexCount / byteCount;
auto temp = patternToArray<u8>(pattern);
if (indicesPerByte == 2) {
for (u32 i = 0; i < temp.size(); i++) {
indices.push_back(temp[i] & 0xF);
indices.push_back((temp[i] >> 4) & 0xF);
}
} else if (indicesPerByte == 4) {
for (u32 i = 0; i < temp.size(); i++) {
indices.push_back(temp[i] & 0x3);
indices.push_back((temp[i] >> 2) & 0x3);
indices.push_back((temp[i] >> 4) & 0x3);
indices.push_back((temp[i] >> 6) & 0x3);
}
}
}
return indices;
}
ImGuiExt::Texture getTexture(pl::ptrn::Pattern *colorTablePattern, std::vector<u32>& indices, u128 width, u128 height) {
ImGuiExt::Texture texture;
auto iterable = dynamic_cast<pl::ptrn::IIterable *>(colorTablePattern);
if (iterable == nullptr || iterable->getEntryCount() <= 0)
return texture;
auto content = iterable->getEntry(0);
auto colorTypeSize = content->getSize()*2;
if (colorTypeSize == 1) {
texture = unmapColors<u8>(colorTablePattern, indices, width, height);
} else if (colorTypeSize == 2) {
texture = unmapColors<u16>(colorTablePattern, indices, width, height);
} else if (colorTypeSize == 4) {
texture = unmapColors<u32>(colorTablePattern, indices, width, height);
}
return texture;
}
}