diff --git a/GViewCore/include/GView.hpp b/GViewCore/include/GView.hpp index d1821c5f..8330b267 100644 --- a/GViewCore/include/GView.hpp +++ b/GViewCore/include/GView.hpp @@ -771,6 +771,11 @@ namespace Regex }; } // namespace Regex +namespace Entropy +{ + CORE_EXPORT double ShannonEntropy(const BufferView& buffer); +} // namespace Entropy + /* * Object can be: * - a file diff --git a/GViewCore/src/CMakeLists.txt b/GViewCore/src/CMakeLists.txt index 9417550a..d39edd30 100644 --- a/GViewCore/src/CMakeLists.txt +++ b/GViewCore/src/CMakeLists.txt @@ -13,6 +13,7 @@ add_subdirectory(ZIP) add_subdirectory(SQLite3) add_subdirectory(Regex) add_subdirectory(Unpack) +add_subdirectory(Entropy) if(NOT DEFINED CMAKE_TESTING_ENABLED) target_sources(GViewCore PRIVATE main.cpp) diff --git a/GViewCore/src/Entropy/CMakeLists.txt b/GViewCore/src/Entropy/CMakeLists.txt new file mode 100644 index 00000000..398aff76 --- /dev/null +++ b/GViewCore/src/Entropy/CMakeLists.txt @@ -0,0 +1,3 @@ +target_sources(GViewCore PRIVATE + Entropy.cpp +) diff --git a/GViewCore/src/Entropy/Entropy.cpp b/GViewCore/src/Entropy/Entropy.cpp new file mode 100644 index 00000000..5b00e91c --- /dev/null +++ b/GViewCore/src/Entropy/Entropy.cpp @@ -0,0 +1,27 @@ +#include "Internal.hpp" + +namespace GView::Entropy +{ +double ShannonEntropy(const BufferView& buffer) +{ + char frequency[256]{}; + + // Count frequency of each byte in the buffer + for (uint32 i = 0; i < buffer.GetLength(); i++) { + const auto c = buffer[i]; + frequency[c]++; + } + + // Calculate entropy + double entropy = 0.0; + for (const auto& value : frequency) { + if (value == 0) { + continue; + } + double probability = static_cast(value) / buffer.GetLength(); + entropy -= probability * std::log2(probability); + } + + return entropy; // max log2(n) = 8 +} +} // namespace GView::Entropy diff --git a/GenericPlugins/EntropyVisualizer/include/EntropyVisualizer.hpp b/GenericPlugins/EntropyVisualizer/include/EntropyVisualizer.hpp index f96bc6e6..fb46d8cc 100644 --- a/GenericPlugins/EntropyVisualizer/include/EntropyVisualizer.hpp +++ b/GenericPlugins/EntropyVisualizer/include/EntropyVisualizer.hpp @@ -4,5 +4,37 @@ namespace GView::GenericPlugins::EntropyVisualizer { +static const SpecialChars BLOCK_SPECIAL_CHARACTER = SpecialChars::Block75; +static const Color CANVAS_ENTROPY_BACKGROUND = Color::Black; +static const uint32 SHANNON_ENTROPY_MAX_VALUE = 8; +static const uint32 SHANNON_ENTROPY_LEGEND_HEIGHT = 13; +static const std::string_view SHANNON_ENTROPY_OPTION_NAME = "Shannon Entropy"; +static const uint32 MINIMUM_BLOCK_SIZE = 4; +static const uint32 COMBO_BOX_ITEM_SHANNON_ENTROPY = 0; + +class Plugin : public Window +{ + Reference object; + Reference entropyComboBox; + Reference blockSizeComboBox; + Reference canvasEntropy; + Reference canvasLegend; + + uint32 blockSize = MINIMUM_BLOCK_SIZE; + + private: + void ResizeLegendCanvas(); + static Color ShannonEntropyValueToColor(int32 value); + bool InitializeBlocksForCanvas(); + + public: + Plugin(Reference object); + + bool DrawShannonEntropy(); + bool DrawShannonEntropyLegend(); + + virtual void OnAfterResize(int newWidth, int newHeight) override; + bool OnEvent(Reference sender, Event eventType, int controlID) override; +}; } // namespace GView::GenericPlugins::EntropyVisualizer diff --git a/GenericPlugins/EntropyVisualizer/src/EntropyVisualizer.cpp b/GenericPlugins/EntropyVisualizer/src/EntropyVisualizer.cpp index ba93875c..2bb111f6 100644 --- a/GenericPlugins/EntropyVisualizer/src/EntropyVisualizer.cpp +++ b/GenericPlugins/EntropyVisualizer/src/EntropyVisualizer.cpp @@ -2,4 +2,221 @@ namespace GView::GenericPlugins::EntropyVisualizer { +extern "C" { +PLUGIN_EXPORT bool Run(const string_view command, Reference object) +{ + if (command == "EntropyVisualizer") { + auto p = Plugin(object); + p.Show(); + return true; + } + return false; +} + +PLUGIN_EXPORT void UpdateSettings(IniSection sect) +{ + sect["command.EntropyVisualizer"] = Input::Key::Ctrl | Input::Key::F10; +} +} + +Color Plugin::ShannonEntropyValueToColor(int32 value) +{ + switch (value) { + case 0: + return Color::White; + case 1: + return Color::Silver; + case 2: + return Color::Gray; + case 3: + return Color::Olive; + case 4: + return Color::Yellow; + case 5: + return Color::DarkGreen; + case 6: + return Color::DarkRed; + case 7: + return Color::Magenta; + case 8: + return Color::Red; + default: + return Color::Black; + } +} + +Plugin::Plugin(Reference object) : Window("EntropyVisualizer", "d:c,w:95%,h:95%", WindowFlags::FixedPosition) +{ + this->object = object; + { + Factory::Label::Create(this, "Entropy type", "x:1, y:0,w:12,h:1"); + this->entropyComboBox = Factory::ComboBox::Create(this, "x:14, y:0,w:25,h:1", ""); + entropyComboBox->SetHotKey('E'); + entropyComboBox->AddItem(SHANNON_ENTROPY_OPTION_NAME, COMBO_BOX_ITEM_SHANNON_ENTROPY); + // TODO: add the rest + entropyComboBox->SetCurentItemIndex(0); + } + { + Factory::Label::Create(this, "Block size", "x:40,y:0,w:10,h:1"); + this->blockSizeComboBox = Factory::ComboBox::Create(this, "x:52,y:0,w:15,h:1", ""); + blockSizeComboBox->SetHotKey('B'); + } + { + this->canvasEntropy = Factory::CanvasViewer::Create(this, "d:lb,w:90%,h:99%", this->GetWidth(), this->GetHeight(), Controls::ViewerFlags::Border); + auto canvas = this->canvasEntropy->GetCanvas(); + canvas->Resize(this->canvasEntropy->GetWidth(), this->canvasEntropy->GetHeight()); + canvas->SetCursor(0, 0); + } + { + this->canvasLegend = Factory::CanvasViewer::Create(this, "d:tr,w:10%,h:20%", this->GetWidth(), this->GetHeight(), Controls::ViewerFlags::Border); + auto canvas = this->canvasLegend->GetCanvas(); + canvas->Resize(this->canvasLegend->GetWidth(), this->canvasLegend->GetHeight()); + } + + this->InitializeBlocksForCanvas(); + // raise events after all children are initialized + this->entropyComboBox->RaiseEvent(Event::ComboBoxClosed); + + this->canvasEntropy->SetFocus(); +} + +bool Plugin::DrawShannonEntropy() +{ + CHECK(this->canvasEntropy.IsValid(), false, ""); + auto canvas = this->canvasEntropy->GetCanvas(); + + auto& cache = object->GetData(); + const auto size = cache.GetSize(); + const uint32 blocksCount = static_cast(size / this->blockSize + 1); + + uint32 x = 0; + uint32 y = 0; + uint32 maxX = canvas->GetWidth(); + uint32 maxY = std::max(blocksCount / maxX + 1 + 1, canvas->GetHeight()); + const auto color = ColorPair{ Color::White, this->GetConfig()->Window.Background.Normal }; + canvas->Resize(maxX, maxY, 'X', color); + canvas->ClearEntireSurface('X', color); + + for (uint32 i = 0; i < blocksCount; i++) { + auto bf = cache.Get(i * static_cast(this->blockSize), this->blockSize, false); + const auto value = GView::Entropy::ShannonEntropy(bf); + const auto roundedValue = static_cast(std::llround(value)); + const auto fColor = ShannonEntropyValueToColor(roundedValue); + + canvas->WriteSpecialCharacter(x++, y, BLOCK_SPECIAL_CHARACTER, ColorPair{ fColor, CANVAS_ENTROPY_BACKGROUND }); + if (x == maxX) { + x = 0; + y++; + } + } + + return true; +} + +bool Plugin::DrawShannonEntropyLegend() +{ + CHECK(this->canvasLegend.IsValid(), false, ""); + ResizeLegendCanvas(); + + auto canvas = this->canvasLegend->GetCanvas(); + + const auto color = ColorPair{ Color::White, this->GetConfig()->Window.Background.Normal }; + + uint32 x = 0; + uint32 y = 0; + canvas->Clear(' ', color); + + canvas->WriteSingleLineText(x, y++, "Shanon Legend [0-8]", color); + canvas->FillHorizontalLineWithSpecialChar(x, y++, canvas->GetWidth(), SpecialChars::BoxHorizontalSingleLine, color); + x = 0; + + for (uint32 i = 0; i <= SHANNON_ENTROPY_MAX_VALUE; i++) { + canvas->WriteCharacter(x++, y, i + '0', color); + canvas->WriteCharacter(x++, y, ' ', color); + canvas->WriteCharacter(x++, y, '=', color); + canvas->WriteCharacter(x++, y, '>', color); + canvas->WriteCharacter(x++, y, ' ', color); + + while (x < canvas->GetWidth()) { + canvas->WriteSpecialCharacter(x++, y, BLOCK_SPECIAL_CHARACTER, ColorPair{ ShannonEntropyValueToColor(i), CANVAS_ENTROPY_BACKGROUND }); + } + y++; + x = 0; + } + + return true; +} + +void Plugin::OnAfterResize(int newWidth, int newHeight) +{ + ResizeLegendCanvas(); +} + +void Plugin::ResizeLegendCanvas() +{ + CHECKRET(canvasLegend.IsValid(), ""); + CHECKRET(canvasEntropy.IsValid(), ""); + this->canvasLegend->MoveTo(this->canvasLegend->GetX(), this->canvasEntropy->GetY()); + + this->canvasLegend->Resize(this->canvasLegend->GetWidth(), SHANNON_ENTROPY_LEGEND_HEIGHT); + auto canvas = this->canvasLegend->GetCanvas(); + canvas->Resize(this->canvasLegend->GetWidth(), SHANNON_ENTROPY_LEGEND_HEIGHT); +} + +bool Plugin::OnEvent(Reference sender, Event eventType, int controlID) +{ + if (Window::OnEvent(sender, eventType, controlID)) { + return true; + } + + switch (eventType) { + case AppCUI::Controls::Event::ComboBoxSelectedItemChanged: + /* nothing, is to costly computing entropy each time */ + break; + case AppCUI::Controls::Event::ComboBoxClosed: + if (sender == this->entropyComboBox.ToBase()) { + this->DrawShannonEntropy(); + this->DrawShannonEntropyLegend(); + return true; + } + if (sender == this->blockSizeComboBox.ToBase()) { + this->blockSize = static_cast(this->blockSizeComboBox->GetCurrentItemUserData(-1)); + const auto entropy = this->entropyComboBox->GetCurrentItemUserData(-1); + if (entropy == COMBO_BOX_ITEM_SHANNON_ENTROPY) { + this->DrawShannonEntropy(); + this->DrawShannonEntropyLegend(); + } + return true; + } + break; + default: + break; + } + + return false; +} + +bool Plugin::InitializeBlocksForCanvas() +{ + CHECK(this->object.IsValid(), false, ""); + CHECK(this->canvasEntropy.IsValid(), false, ""); + + const auto size = this->object->GetData().GetSize(); + auto canvas = this->canvasEntropy->GetCanvas(); + const auto canvasWidth = canvas->GetWidth(); + const auto canvasHeight = canvas->GetHeight(); + + uint32 blocksRows = 0; + do { + uint32 blocksCount = static_cast(size / this->blockSize); + blocksRows = blocksCount / canvasWidth + 1; + blockSizeComboBox->AddItem(std::to_string(this->blockSize), this->blockSize); + this->blockSize *= 2; + } while (blocksRows > canvasHeight); + this->blockSize /= 2; + + blockSizeComboBox->SetCurentItemIndex(blockSizeComboBox->GetItemsCount() - 1); + + return true; +} } // namespace GView::GenericPlugins::EntropyVisualizer