Skip to content

Commit

Permalink
feat: Added Minimap to Hex Editor
Browse files Browse the repository at this point in the history
  • Loading branch information
WerWolv committed Jan 28, 2024
1 parent 17f769c commit 069544e
Show file tree
Hide file tree
Showing 9 changed files with 221 additions and 34 deletions.
15 changes: 15 additions & 0 deletions lib/libimhex/include/hex/api/content_registry.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -985,11 +985,19 @@ namespace hex {
u16 m_maxCharsPerCell;
};

struct MiniMapVisualizer {
using Callback = std::function<ImColor(const std::vector<u8>&)>;

UnlocalizedString unlocalizedName;
Callback callback;
};

namespace impl {

void addDataVisualizer(std::shared_ptr<DataVisualizer> &&visualizer);

std::vector<std::shared_ptr<DataVisualizer>> &getVisualizers();
std::vector<std::shared_ptr<MiniMapVisualizer>> &getMiniMapVisualizers();

}

Expand All @@ -1010,6 +1018,13 @@ namespace hex {
*/
std::shared_ptr<DataVisualizer> getVisualizerByName(const UnlocalizedString &unlocalizedName);

/**
* @brief Adds a new minimap visualizer
* @param unlocalizedName Unlocalized name of the minimap visualizer
* @param callback The callback that will be called to get the color of a line
*/
void addMiniMapVisualizer(UnlocalizedString unlocalizedName, MiniMapVisualizer::Callback callback);

}

/* Diffing Registry. Allows adding new diffing algorithms */
Expand Down
10 changes: 10 additions & 0 deletions lib/libimhex/source/api/content_registry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,12 @@ namespace hex {
return visualizers;
}

std::vector<std::shared_ptr<MiniMapVisualizer>> &getMiniMapVisualizers() {
static std::vector<std::shared_ptr<MiniMapVisualizer>> visualizers;

return visualizers;
}

}

std::shared_ptr<DataVisualizer> getVisualizerByName(const UnlocalizedString &unlocalizedName) {
Expand All @@ -1012,6 +1018,10 @@ namespace hex {
return nullptr;
}

void addMiniMapVisualizer(UnlocalizedString unlocalizedName, MiniMapVisualizer::Callback callback) {
impl::getMiniMapVisualizers().emplace_back(std::make_shared<MiniMapVisualizer>(std::move(unlocalizedName), std::move(callback)));
}

}

namespace ContentRegistry::Diffing {
Expand Down
1 change: 1 addition & 0 deletions plugins/builtin/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ add_imhex_plugin(
source/content/workspaces.cpp
source/content/pl_visualizers.cpp
source/content/out_of_box_experience.cpp
source/content/minimap_visualizers.cpp

source/content/data_processor_nodes/basic_nodes.cpp
source/content/data_processor_nodes/control_nodes.cpp
Expand Down
2 changes: 2 additions & 0 deletions plugins/builtin/romfs/lang/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@
"hex.builtin.menu.view.debug": "Show Debugging View",
"hex.builtin.menu.view.demo": "Show ImGui Demo",
"hex.builtin.menu.view.fps": "Display FPS",
"hex.builtin.minimap_visualizer.entropy": "Local Entropy",
"hex.builtin.minimap_visualizer.zeros": "Zeros Count",
"hex.builtin.nodes.arithmetic": "Arithmetic",
"hex.builtin.nodes.arithmetic.add": "Addition",
"hex.builtin.nodes.arithmetic.add.header": "Add",
Expand Down
57 changes: 57 additions & 0 deletions plugins/builtin/source/content/minimap_visualizers.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include <hex/api/content_registry.hpp>

#include <imgui.h>
#include <hex/ui/imgui_imhex_extensions.h>

#include <hex/helpers/utils.hpp>

#include <wolv/utils/string.hpp>


namespace hex::plugin::builtin {

namespace {

ImColor entropyMiniMapVisualizer(const std::vector<u8> &data) {
std::array<u32, 256> frequencies = { 0 };
for (u8 byte : data)
frequencies[byte]++;

double entropy = 0.0;
for (u32 frequency : frequencies) {
if (frequency == 0)
continue;

double probability = static_cast<double>(frequency) / data.size();
entropy -= probability * std::log2(probability);
}

// Calculate color
ImColor color = ImColor::HSV(0.0F, 0.0F, 1.0F);

if (entropy > 0.0) {
double hue = std::clamp(entropy / 8.0, 0.0, 1.0);
color = ImColor::HSV(static_cast<float>(hue) / 0.75F, 0.8F, 1.0F);
}

return color;
}

ImColor zerosMiniMapVisualizer(const std::vector<u8> &data) {
u32 zerosCount = 0;
for (u8 byte : data) {
if (byte == 0x00)
zerosCount++;
}

return ImColor::HSV(0.0F, 0.0F, 1.0F - (double(zerosCount) / data.size()));
}

}

void registerMiniMapVisualizers() {
ContentRegistry::HexEditor::addMiniMapVisualizer("hex.builtin.minimap_visualizer.entropy", entropyMiniMapVisualizer);
ContentRegistry::HexEditor::addMiniMapVisualizer("hex.builtin.minimap_visualizer.zeros", zerosMiniMapVisualizer);
}

}
2 changes: 2 additions & 0 deletions plugins/builtin/source/plugin_builtin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ namespace hex::plugin::builtin {

void registerEventHandlers();
void registerDataVisualizers();
void registerMiniMapVisualizers();
void registerDataInspectorEntries();
void registerToolEntries();
void registerPatternLanguageFunctions();
Expand Down Expand Up @@ -86,6 +87,7 @@ IMHEX_PLUGIN_SETUP("Built-in", "WerWolv", "Default ImHex functionality") {

registerEventHandlers();
registerDataVisualizers();
registerMiniMapVisualizers();
registerDataInspectorEntries();
registerToolEntries();
registerPatternLanguageFunctions();
Expand Down
6 changes: 6 additions & 0 deletions plugins/ui/include/ui/hex_editor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ namespace hex::ui {
void drawEditor(const ImVec2 &size);
void drawFooter(const ImVec2 &size);
void drawTooltip(u64 address, const u8 *data, size_t size) const;
void drawScrollbar(ImVec2 characterSize);
void drawMinimap(ImVec2 characterSize);

void handleSelection(u64 address, u32 bytesPerCell, const u8 *data, bool cellHovered);
std::optional<color_t> applySelectionColor(u64 byteAddress, std::optional<color_t> color);
Expand Down Expand Up @@ -324,11 +326,15 @@ namespace hex::ui {
bool m_shouldUpdateEditingValue = false;
std::vector<u8> m_editingBytes;

std::shared_ptr<ContentRegistry::HexEditor::MiniMapVisualizer> m_miniMapVisualizer;

color_t m_selectionColor = 0x60C08080;
bool m_upperCaseHex = true;
bool m_grayOutZero = true;
bool m_showAscii = true;
bool m_showCustomEncoding = true;
bool m_showMiniMap = false;
int m_miniMapWidth = 5;
bool m_showHumanReadableUnits = true;
u32 m_byteCellPadding = 0, m_characterCellPadding = 0;
bool m_footerCollapsed = true;
Expand Down
2 changes: 2 additions & 0 deletions plugins/ui/romfs/lang/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@
"hex.ui.hex_editor.human_readable_units_footer": "Convert sizes to human-readable units",
"hex.ui.hex_editor.data_size": "Data Size",
"hex.ui.hex_editor.gray_out_zero": "Grey out zeros",
"hex.ui.hex_editor.minimap": "Mini Map",
"hex.ui.hex_editor.minimap.width": "Width",
"hex.ui.hex_editor.no_bytes": "No bytes available",
"hex.ui.hex_editor.page": "Page",
"hex.ui.hex_editor.region": "Region",
Expand Down
160 changes: 126 additions & 34 deletions plugins/ui/source/ui/hex_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ namespace hex::ui {

HexEditor::HexEditor(prv::Provider *provider) : m_provider(provider) {
m_currDataVisualizer = ContentRegistry::HexEditor::getVisualizerByName("hex.builtin.visualizer.hexadecimal.8bit");
m_miniMapVisualizer = ContentRegistry::HexEditor::impl::getMiniMapVisualizers().front();
}

HexEditor::~HexEditor() {
Expand Down Expand Up @@ -150,6 +151,105 @@ namespace hex::ui {
ImGui::PopStyleVar();
}

void HexEditor::drawScrollbar(ImVec2 characterSize) {
ImS64 numRows = m_provider == nullptr ? 0 : (m_provider->getSize() / m_bytesPerRow) + ((m_provider->getSize() % m_bytesPerRow) == 0 ? 0 : 1);

auto window = ImGui::GetCurrentWindowRead();
const auto outerRect = window->Rect();
const auto innerRect = window->InnerRect;
const auto borderSize = window->WindowBorderSize;
const auto scrollbarWidth = ImGui::GetStyle().ScrollbarSize;
const auto bb = ImRect(ImMax(outerRect.Min.x, outerRect.Max.x - borderSize - scrollbarWidth), innerRect.Min.y, outerRect.Max.x, innerRect.Max.y);

constexpr auto roundingCorners = ImDrawFlags_RoundCornersTopRight | ImDrawFlags_RoundCornersBottomRight;
constexpr auto axis = ImGuiAxis_Y;

if (numRows > 0) {
ImGui::PushID("MainScrollBar");
ImGui::ScrollbarEx(
bb,
ImGui::GetWindowScrollbarID(window, axis),
axis,
&m_scrollPosition.get(),
(std::ceil(innerRect.Max.y - innerRect.Min.y) / characterSize.y),
std::nextafterf(numRows + ImGui::GetWindowSize().y / characterSize.y, std::numeric_limits<float>::max()),
roundingCorners);
ImGui::PopID();
}

if (m_showMiniMap && m_miniMapVisualizer != nullptr)
this->drawMinimap(characterSize);

if (ImGui::IsWindowHovered()) {
m_scrollPosition += ImS64(ImGui::GetIO().MouseWheel * -5);
}

if (m_scrollPosition < 0)
m_scrollPosition = 0;
if (m_scrollPosition > (numRows - 1))
m_scrollPosition = numRows - 1;
}

void HexEditor::drawMinimap(ImVec2 characterSize) {
ImS64 numRows = m_provider == nullptr ? 0 : (m_provider->getSize() / m_bytesPerRow) + ((m_provider->getSize() % m_bytesPerRow) == 0 ? 0 : 1);

auto window = ImGui::GetCurrentWindowRead();
const auto outerRect = window->Rect();
const auto innerRect = window->InnerRect;
const auto borderSize = window->WindowBorderSize;
const auto scrollbarWidth = ImGui::GetStyle().ScrollbarSize;
const auto bb = ImRect(ImMax(outerRect.Min.x, outerRect.Max.x - borderSize - scrollbarWidth) - scrollbarWidth * (1 + m_miniMapWidth), innerRect.Min.y, outerRect.Max.x - scrollbarWidth, innerRect.Max.y);

constexpr auto roundingCorners = ImDrawFlags_RoundCornersTopRight | ImDrawFlags_RoundCornersBottomRight;
constexpr auto axis = ImGuiAxis_Y;

constexpr static u64 RowCount = 256;
const auto rowHeight = innerRect.GetSize().y / RowCount;
const auto scrollPos = m_scrollPosition.get();
const auto grabSize = rowHeight * m_visibleRowCount;
const auto grabPos = (RowCount - m_visibleRowCount) * (double(scrollPos) / numRows);

auto drawList = ImGui::GetWindowDrawList();

drawList->ChannelsSplit(2);
drawList->ChannelsSetCurrent(1);
if (numRows > 0) {
ImGui::PushID("MiniMapScrollBar");
ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, grabSize);
ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarRounding, 0);
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrab, ImGui::GetColorU32(ImGuiCol_ScrollbarGrab, 0.4F));
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabActive, ImGui::GetColorU32(ImGuiCol_ScrollbarGrabActive, 0.5F));
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabHovered, ImGui::GetColorU32(ImGuiCol_ScrollbarGrabHovered, 0.5F));
ImGui::ScrollbarEx(
bb,
ImGui::GetWindowScrollbarID(window, axis),
axis,
&m_scrollPosition.get(),
(std::ceil(innerRect.Max.y - innerRect.Min.y) / characterSize.y),
std::nextafterf((numRows - m_visibleRowCount) + ImGui::GetWindowSize().y / characterSize.y, std::numeric_limits<float>::max()),
roundingCorners);
ImGui::PopStyleVar(2);
ImGui::PopStyleColor(3);
ImGui::PopID();
}
drawList->ChannelsSetCurrent(0);

std::vector<u8> rowData(m_bytesPerRow);
const auto drawStart = scrollPos - grabPos;
for (u64 y = drawStart; y < std::min<u64>(drawStart + RowCount, m_provider->getSize() / m_bytesPerRow); y += 1) {
const auto rowStart = bb.Min + ImVec2(0, (y - drawStart) * rowHeight);
const auto rowEnd = rowStart + ImVec2(bb.GetSize().x, rowHeight);

m_provider->read(y * m_bytesPerRow + m_provider->getBaseAddress() + m_provider->getCurrentPageAddress(), rowData.data(), rowData.size());

drawList->AddRectFilled(rowStart, rowEnd, m_miniMapVisualizer->callback(rowData));
}

drawList->ChannelsMerge();
}



void HexEditor::drawCell(u64 address, const u8 *data, size_t size, bool hovered, CellType cellType) {
static DataVisualizerAscii asciiVisualizer;

Expand Down Expand Up @@ -313,39 +413,7 @@ namespace hex::ui {
}

if (ImGui::BeginChild("Hex View", size, ImGuiChildFlags_None, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
// Draw scrollbar
{
const auto window = ImGui::GetCurrentWindowRead();
const auto axis = ImGuiAxis_Y;
ImS64 numRows = m_provider == nullptr ? 0 : (m_provider->getSize() / m_bytesPerRow) + ((m_provider->getSize() % m_bytesPerRow) == 0 ? 0 : 1);

const auto outerRect = window->Rect();
const auto innerRect = window->InnerRect;
const auto borderSize = window->WindowBorderSize;
const auto scrollbarWidth = ImGui::GetStyle().ScrollbarSize;
const auto bb = ImRect(ImMax(outerRect.Min.x, outerRect.Max.x - borderSize - scrollbarWidth), innerRect.Min.y, outerRect.Max.x, innerRect.Max.y);
const auto roundingCorners = ImDrawFlags_RoundCornersTopRight | ImDrawFlags_RoundCornersBottomRight;

if (numRows > 0) {
ImGui::ScrollbarEx(
bb,
ImGui::GetWindowScrollbarID(window, axis),
axis,
&m_scrollPosition.get(),
(std::ceil(innerRect.Max.y - innerRect.Min.y) / CharacterSize.y),
std::nextafterf(numRows + ImGui::GetWindowSize().y / CharacterSize.y, std::numeric_limits<float>::max()),
roundingCorners);
}

if (ImGui::IsWindowHovered()) {
m_scrollPosition += ImS64(ImGui::GetIO().MouseWheel * -5);
}

if (m_scrollPosition < 0)
m_scrollPosition = 0;
if (m_scrollPosition > (numRows - 1))
m_scrollPosition = numRows - 1;
}
this->drawScrollbar(CharacterSize);

ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(0.5, 0));
if (ImGui::BeginTable("##hex", byteColumnCount, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoKeepColumnsVisible, size)) {
Expand Down Expand Up @@ -850,9 +918,33 @@ namespace hex::ui {
// Custom encoding view
ImGui::BeginDisabled(!m_currCustomEncoding.has_value());
ImGuiExt::DimmedIconToggle(ICON_VS_WHITESPACE, &m_showCustomEncoding);
ImGuiExt::InfoTooltip("hex.ui.hex_editor.custom_encoding_view"_lang);
ImGui::EndDisabled();

ImGuiExt::InfoTooltip("hex.ui.hex_editor.custom_encoding_view"_lang);
ImGui::SameLine(0, 1_scaled);

// Minimap
ImGuiExt::DimmedIconToggle(ICON_VS_MAP, &m_showMiniMap);
ImGuiExt::InfoTooltip("hex.ui.hex_editor.minimap"_lang);
if (ImGui::IsItemClicked(ImGuiMouseButton_Right) && m_miniMapVisualizer != nullptr)
ImGui::OpenPopup("MiniMapOptions");

if (ImGui::BeginPopup("MiniMapOptions")) {
ImGui::SliderInt("hex.ui.hex_editor.minimap.width"_lang, &m_miniMapWidth, 1, 25, "%d", ImGuiSliderFlags_AlwaysClamp);

if (ImGui::BeginCombo("##minimap_visualizer", Lang(m_miniMapVisualizer->unlocalizedName))) {

for (const auto &visualizer : ContentRegistry::HexEditor::impl::getMiniMapVisualizers()) {
if (ImGui::Selectable(Lang(visualizer->unlocalizedName))) {
m_miniMapVisualizer = visualizer;
}
}

ImGui::EndCombo();
}

ImGui::EndPopup();
}

ImGui::SameLine();

Expand Down

0 comments on commit 069544e

Please sign in to comment.