ntrop

2D binary entropy visualization inspired by ..cantor.dust..
git clone https://git.sinitax.com/sinitax/ntrop
Log | Files | Refs | sfeed.txt

commit 041d3dd55e0d7d5a0f01a15b1bcfc3e63a70b661
Author: Louis Burda <quent.burda@gmail.com>
Date:   Sun, 12 Feb 2023 04:28:49 +0100

Initial prototype with raylib

Diffstat:
A.gitignore | 2++
AMakefile | 11+++++++++++
Antrop.c | 337+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 350 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,2 @@ +ntrop +.gdb_history diff --git a/Makefile b/Makefile @@ -0,0 +1,11 @@ +CFLAGS = -Wunused-variable -Wunused-function +LDLIBS = -lraylib -lm + +all: ntrop + +clean: + rm -f ntrop + +ntrop: ntrop.c + +.PHONY: all clean diff --git a/ntrop.c b/ntrop.c @@ -0,0 +1,337 @@ +#include "raylib.h" + +#include <err.h> +#include <math.h> +#include <string.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> + +#define CHUNK_SIZE 4096 +#define ZOOM_MAX 64 + +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) > (b) ? (b) : (a)) + +const char *file_path; +uint8_t *file_data; +size_t data_len; +size_t data_width; +size_t data_height; + +Color *value_colors; +Color *entropy_colors; + +int entropy_ctx; + +double zoom; +double zoom_x, zoom_y; + +int window_width; +int window_height; +char *window_title; +bool show_pos; +bool show_value; + +int mouse_x, mouse_y; + +int drag_mouse_x, drag_mouse_y; +double drag_zoom_x, drag_zoom_y; +bool drag; + +char fmtbuf[256]; + +void +usage(void) +{ + printf("Usage: ntrop FILE\n"); + exit(0); +} + +uint8_t * +readfile(const char *path, size_t *len) +{ + FILE *file; + char *chunk; + uint8_t *data; + size_t nread; + size_t cap; + + chunk = malloc(CHUNK_SIZE); + if (!chunk) err(1, "malloc"); + + file = fopen(file_path, "r"); + if (!file) err(1, "fopen"); + + cap = 16 * 1024; + data = malloc(cap); + if (!data) err(1, "malloc"); + + *len = 0; + while ((nread = fread(chunk, 1, CHUNK_SIZE, file))) { + if (*len + nread > cap) { + cap *= 2; + data = realloc(data, cap); + if (!data) err(1, "realloc"); + } + memcpy(data + *len, chunk, nread); + *len += nread; + } + + fclose(file); + + return data; +} + +void +init_value_colors(Color *data_colors) +{ + size_t pos; + Color c; + + c.a = 255; + for (pos = 0; pos < data_len; pos++) { + c.r = file_data[pos]; + c.g = file_data[pos]; + c.b = file_data[pos]; + data_colors[pos] = c; + } +} + +void +init_entropy_colors(Color *data_colors) +{ + ssize_t i, k, pos, uniq; + uint8_t *vals; + size_t *counts; + size_t ctx_count; + double entropy; + Color c; + + vals = malloc(entropy_ctx * 2 + 1); + if (!vals) err(1, "malloc"); + + counts = malloc(sizeof(size_t) * (entropy_ctx * 2 + 1)); + if (!counts) err(1, "malloc"); + + c.a = 255; + for (pos = 0; pos < data_len; pos++) { + uniq = 0; + ctx_count = entropy_ctx * 2 + 1; + for (i = -entropy_ctx; i <= entropy_ctx; i++) { + if (pos + i < 0 || pos + i > data_len) { + ctx_count--; + continue; + } + for (k = 0; k < uniq; k++) { + if (vals[k] == file_data[pos + i]) { + counts[k] += 1; + break; + } + } + if (k == uniq) { + counts[uniq] = 1; + vals[uniq] = file_data[pos + i]; + uniq += 1; + } + } + entropy = 0; + for (k = 0; k < uniq; k++) { + entropy += - 1.F * counts[k] / ctx_count + * log2(1.F * counts[k] / ctx_count); + } + entropy /= - 1.F * log2(1.F / ctx_count); + entropy *= 255; + c.r = entropy; + c.g = entropy; + c.b = entropy; + data_colors[pos] = c; + } + + free(vals); + free(counts); +} + +void +vis(void) +{ + Color *data_colors; + size_t pos, len; + double mouse_move; + size_t data_x, data_y; + size_t data_pos; + size_t x, y; + + data_colors = value_colors; + show_value = true; + + window_title = malloc(1024); + if (!window_title) err(1, "malloc"); + snprintf(window_title, 1024, "ntrop %s", file_path); + + SetConfigFlags(FLAG_WINDOW_RESIZABLE); + SetTargetFPS(60); + + SetTraceLogLevel(LOG_NONE); + + window_width = 800; + window_height = 600; + InitWindow(window_width, window_height, window_title); + + data_width = window_width / 2; + data_height = data_len / data_width + !!(data_len % data_width); + zoom_x = 0; + zoom_y = 0; + zoom = 1; + + show_pos = false; + while (!WindowShouldClose()) { + if (IsKeyPressed(KEY_Q)) + break; + + if (IsWindowResized()) { + window_width = GetScreenWidth(); + window_height = GetScreenHeight(); + data_width = MIN(window_width / 2, 400); + data_height = data_len / data_width + !!(data_len % data_width); + zoom_x = (data_width - window_width / zoom) / 2.F; + zoom_y = (data_height - window_height / zoom) / 2.F; + } + + mouse_x = MIN(MAX(0, GetMouseX()), window_width); + mouse_y = MIN(MAX(0, GetMouseY()), window_height); + + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { + drag_mouse_x = mouse_x; + drag_mouse_y = mouse_y; + drag_zoom_x = zoom_x; + drag_zoom_y = zoom_y; + drag = true; + } + if (IsMouseButtonDown(MOUSE_BUTTON_LEFT)) { + zoom_x = drag_zoom_x - (mouse_x - drag_mouse_x) * 1.F / zoom; + zoom_y = drag_zoom_y - (mouse_y - drag_mouse_y) * 1.F / zoom; + } else { + drag = false; + } + + mouse_move = GetMouseWheelMove(); + if (mouse_move > 0 && zoom < ZOOM_MAX) { + zoom_x += (1.F * window_width / zoom / 2) + * (1.F * mouse_x / window_width); + zoom_y += (1.F * window_height / zoom / 2) + * (1.F * mouse_y / window_height); + zoom *= 2; + } else if (mouse_move < 0 && zoom > 1) { + zoom_x -= (1.F * window_width / zoom) + * (1.F * mouse_x / window_width); + zoom_y -= (1.F * window_height / zoom) + * (1.F * mouse_y / window_height); + zoom /= 2; + } + + show_pos ^= IsKeyPressed(KEY_T); + + if (IsKeyDown(KEY_LEFT)) + zoom_x -= 20.F / zoom; + else if (IsKeyDown(KEY_RIGHT)) + zoom_x += 20.F / zoom; + + if (IsKeyDown(KEY_UP)) + zoom_y -= 20.F / zoom; + else if (IsKeyDown(KEY_DOWN)) + zoom_y += 20.F / zoom; + + if (IsKeyPressed(KEY_P)) { + show_value = !show_value; + if (show_value) + data_colors = value_colors; + else + data_colors = entropy_colors; + } + + BeginDrawing(); + + ClearBackground(BLACK); + + for (y = 0; y < window_height; y++) { + data_y = (size_t) (zoom_y + y / zoom); + if (data_y < 0 || data_y >= data_height) + continue; + + for (x = 0; x < window_width; x++) { + data_x = (size_t) (zoom_x + x / zoom); + if (data_x < 0 || data_x >= data_width) + continue; + + data_pos = data_y * data_width + data_x; + if (data_pos < data_len) + DrawPixel(x, y, data_colors[data_pos]); + } + } + + if (show_pos) { + data_x = zoom_x + mouse_x / zoom; + data_y = zoom_y + mouse_y / zoom; + if (data_x >= 0 && data_x < data_width + && data_y >= 0 && data_y < data_height) { + pos = data_y * data_width + data_x; + snprintf(fmtbuf, sizeof(fmtbuf), "0x%08lx", pos); + len = MeasureText(fmtbuf, 20); + DrawRectangle(window_width - len - 6, 0, + len + 6, 19, WHITE); + DrawText(fmtbuf, window_width - len - 3, + 0, 20, BLACK); + } + } + + EndDrawing(); + } + + CloseWindow(); + free(window_title); +} + +int +main(int argc, const char **argv) +{ + const char **arg; + + file_path = NULL; + entropy_ctx = 1; + for (arg = argv + 1; *arg; arg++) { + if (!strcmp("-c", *arg)) { + arg += 1; + if (!arg) usage(); + entropy_ctx = atoi(*arg); + if (entropy_ctx <= 0) usage(); + } else if (!file_path) { + file_path = *arg; + } else { + usage(); + } + } + + if (!file_path) + usage(); + + if (!strcmp(file_path, "-")) + file_path = "/dev/stdin"; + + file_data = readfile(file_path, &data_len); + + value_colors = malloc(sizeof(Color) * data_len); + if (!value_colors) err(1, "malloc"); + init_value_colors(value_colors); + + entropy_colors = malloc(sizeof(Color) * data_len); + if (!entropy_colors) err(1, "malloc"); + init_entropy_colors(entropy_colors); + + vis(); + + free(value_colors); + free(entropy_colors); + + free(file_data); +}