From 057543da153a1a31a83881fb9b1bfbc7618c4fb3 Mon Sep 17 00:00:00 2001 From: paxcut <53811119+paxcut@users.noreply.github.com> Date: Sun, 15 Sep 2024 06:16:36 -0700 Subject: [PATCH] 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) --- .../source/content/pl_visualizers.cpp | 2 +- .../source/content/pl_visualizers/image.cpp | 104 +++++++++++++++++- 2 files changed, 102 insertions(+), 4 deletions(-) diff --git a/plugins/visualizers/source/content/pl_visualizers.cpp b/plugins/visualizers/source/content/pl_visualizers.cpp index d938979b3..b0d477712 100644 --- a/plugins/visualizers/source/content/pl_visualizers.cpp +++ b/plugins/visualizers/source/content/pl_visualizers.cpp @@ -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)); diff --git a/plugins/visualizers/source/content/pl_visualizers/image.cpp b/plugins/visualizers/source/content/pl_visualizers/image.cpp index 0784ac67e..e6d888ecf 100644 --- a/plugins/visualizers/source/content/pl_visualizers/image.cpp +++ b/plugins/visualizers/source/content/pl_visualizers/image.cpp @@ -7,6 +7,9 @@ #include namespace hex::plugin::visualizers { + std::vector getIndices(pl::ptrn::Pattern *colorTablePattern, u128 width, u128 height); + ImGuiExt::Texture getTexture(pl::ptrn::Pattern *colorTablePattern, std::vector& indices, u128 width, u128 height); + void drawImageVisualizer(pl::ptrn::Pattern &, bool shouldReset, std::span 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 { } } -} \ No newline at end of file + template ImGuiExt::Texture unmapColors(pl::ptrn::Pattern *colorTablePattern, std::vector& indices, u128 width, u128 height) { + std::vector colorTable = patternToArray(colorTablePattern); + auto colorCount = colorTable.size(); + auto indexCount = indices.size(); + std::vector 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(tmp); + ImGuiExt::Texture texture = ImGuiExt::Texture::fromBitmap(data, indexCount*sizeof(T), width, height, ImGuiExt::Texture::Filter::Nearest); + return texture; + } + + std::vector getIndices(pl::ptrn::Pattern *pattern, u128 width, u128 height) { + auto indexCount = 2 * width * height / pattern->getSize(); + std::vector indices; + auto *iterable = dynamic_cast(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(pattern); + indices = std::vector(temp.begin(), temp.end()); + } else if (bytePerIndex == 2) { + auto temp = patternToArray(pattern); + indices = std::vector(temp.begin(), temp.end()); + } else if (bytePerIndex == 4) { + auto temp = patternToArray(pattern); + indices = std::vector(temp.begin(), temp.end()); + } + } else if (indexCount != 0) { + auto indicesPerByte = indexCount / byteCount; + auto temp = patternToArray(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& indices, u128 width, u128 height) { + ImGuiExt::Texture texture; + auto iterable = dynamic_cast(colorTablePattern); + + if (iterable == nullptr || iterable->getEntryCount() <= 0) + return texture; + auto content = iterable->getEntry(0); + auto colorTypeSize = content->getSize()*2; + + if (colorTypeSize == 1) { + texture = unmapColors(colorTablePattern, indices, width, height); + } else if (colorTypeSize == 2) { + texture = unmapColors(colorTablePattern, indices, width, height); + } else if (colorTypeSize == 4) { + texture = unmapColors(colorTablePattern, indices, width, height); + } + return texture; + } +}