ntrop

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

ntrop.c (11911B)


      1#include "raylib.h"
      2
      3#include <err.h>
      4#include <math.h>
      5#include <string.h>
      6#include <stdint.h>
      7#include <stdio.h>
      8#include <stdlib.h>
      9
     10#define CHUNK_SIZE 4096
     11#define ZOOM_MAX 64
     12
     13#define MAX(a, b) ((a) > (b) ? (a) : (b))
     14#define MIN(a, b) ((a) > (b) ? (b) : (a))
     15
     16enum { PRESS = 1, HOLD };
     17
     18const char *file_path;
     19uint8_t *file_data;
     20ssize_t data_len;
     21ssize_t data_width;
     22ssize_t data_height;
     23
     24ssize_t data_window_start;
     25ssize_t data_window_len;
     26
     27Color *value_colors;
     28Color *entropy_colors;
     29
     30int entropy_ctx;
     31
     32int zoom;
     33double zoom_x, zoom_y;
     34
     35int bar_width;
     36int bar_zoom;
     37ssize_t bar_start;
     38bool show_bar;
     39
     40int window_width;
     41int window_height;
     42char *window_title;
     43bool show_pos;
     44bool show_value;
     45
     46int mouse_x, mouse_y;
     47
     48int  drag_mouse_x, drag_mouse_y;
     49double drag_zoom_x, drag_zoom_y;
     50bool drag;
     51
     52uint64_t hold_times[337] = { 0 };
     53
     54char fmtbuf[256];
     55
     56void
     57usage(void)
     58{
     59	printf("Usage: ntrop [-c CTX] FILE\n");
     60	exit(0);
     61}
     62
     63uint8_t *
     64read_file(const char *path, ssize_t *len)
     65{
     66	FILE *file;
     67	char *chunk;
     68	uint8_t *data;
     69	ssize_t nread;
     70	size_t cap;
     71
     72	chunk = malloc(CHUNK_SIZE);
     73	if (!chunk) err(1, "malloc");
     74
     75	file = fopen(file_path, "r");
     76	if (!file) err(1, "fopen");
     77
     78	cap = 16 * 1024;
     79	data = malloc(cap);
     80	if (!data) err(1, "malloc");
     81
     82	*len = 0;
     83	while ((nread = fread(chunk, 1, CHUNK_SIZE, file)) > 0) {
     84		if (*len + nread > cap) {
     85			cap *= 2;
     86			data = realloc(data, cap);
     87			if (!data) err(1, "realloc");
     88		}
     89		memcpy(data + *len, chunk, nread);
     90		*len += nread;
     91	}
     92
     93	fclose(file);
     94
     95	return data;
     96}
     97
     98void
     99init_value_colors(Color *data_colors)
    100{
    101	size_t pos;
    102	Color c;
    103
    104	c.a = 255;
    105	for (pos = 0; pos < data_len; pos++) {
    106		c.r = file_data[pos];
    107		c.g = file_data[pos];
    108		c.b = file_data[pos];
    109		data_colors[pos] = c;
    110	}
    111}
    112
    113void
    114init_entropy_colors(Color *data_colors)
    115{
    116	ssize_t i, k, pos, uniq;
    117	uint8_t *vals;
    118	size_t *counts;
    119	size_t ctx_count;
    120	double entropy;
    121	Color c;
    122
    123	vals = malloc(entropy_ctx * 2 + 1);
    124	if (!vals) err(1, "malloc");
    125
    126	counts = malloc(sizeof(size_t) * (entropy_ctx * 2 + 1));
    127	if (!counts) err(1, "malloc");
    128
    129	c.a = 255;
    130	for (pos = 0; pos < data_len; pos++) {
    131		uniq = 0;
    132		ctx_count = 0;
    133		for (i = -entropy_ctx; i <= entropy_ctx; i++) {
    134			if (pos + i < 0 || pos + i > data_len)
    135				continue;
    136			for (k = 0; k < uniq; k++) {
    137				if (vals[k] == file_data[pos + i]) {
    138					counts[k] += 1;
    139					break;
    140				}
    141			}
    142			if (k == uniq) {
    143				counts[uniq] = 1;
    144				vals[uniq] = file_data[pos + i];
    145				uniq += 1;
    146			}
    147			ctx_count++;
    148		}
    149		entropy = 0;
    150		for (k = 0; k < uniq; k++) {
    151			entropy += - 1.F * counts[k] / ctx_count
    152				* log2(1.F * counts[k] / ctx_count);
    153		}
    154		entropy /= - 1.F * log2(1.F / ctx_count);
    155		entropy *= 255;
    156		c.r = file_data[pos];
    157		c.g = 0;
    158		c.b = entropy;
    159		data_colors[pos] = c;
    160	}
    161
    162	free(vals);
    163	free(counts);
    164}
    165
    166int
    167key_press_hold(int cnt, ...)
    168{
    169	va_list ap;
    170	int i, key;
    171
    172	va_start(ap, cnt);
    173	for (i = 0; i < cnt; i++) {
    174		key = va_arg(ap, int);
    175
    176		if (!IsKeyDown(key)) {
    177			hold_times[key] = 0;
    178			continue;
    179		}
    180
    181		if (IsKeyPressed(key))
    182			return PRESS;
    183
    184		hold_times[key] += 1;
    185		if (hold_times[key] > 10)
    186			return HOLD;
    187	}
    188
    189	return 0;
    190}
    191
    192void
    193update_data_window_start(void)
    194{
    195	ssize_t len;
    196
    197	len = data_len - data_window_len;
    198	if (len % data_width)
    199		len += data_width - (len % data_width);
    200	data_window_start = MAX(0, MIN(len, data_window_start));
    201}
    202
    203void
    204update_data_window_len(void)
    205{
    206	update_data_window_start();
    207	data_window_len = MIN(data_len - data_window_start, data_window_len);
    208	data_window_len = MAX(0, data_window_len);
    209	data_height = data_window_len / data_width;
    210
    211	zoom_y = (data_height - 1.F * window_height / zoom) / 2.F;
    212}
    213
    214void
    215update_data_width(void)
    216{
    217
    218	data_width = MAX(1, data_width);
    219	update_data_window_len();
    220
    221	if (show_bar) {
    222		zoom_x = - 1.F * bar_width / zoom
    223			- (1.F * (window_width - bar_width) / zoom - data_width) / 2.F;
    224	} else {
    225		zoom_x = -(1.F * window_width / zoom - data_width) / 2.F;
    226	}
    227}
    228
    229void
    230vis(void)
    231{
    232	Color *data_colors;
    233	Image window_image;
    234	int window_init_frames;
    235	ssize_t pos, len;
    236	double mouse_move;
    237	ssize_t data_x, data_y;
    238	ssize_t data_pos;
    239	ssize_t x, y;
    240	int b;
    241
    242	data_colors = value_colors;
    243	show_value = true;
    244
    245	window_title = malloc(1024);
    246	if (!window_title) err(1, "malloc");
    247	snprintf(window_title, 1024, "ntrop %s", file_path);
    248
    249	SetConfigFlags(FLAG_WINDOW_RESIZABLE);
    250	SetTargetFPS(60);
    251
    252	SetTraceLogLevel(LOG_NONE);
    253
    254	InitWindow(800, 600, window_title);
    255
    256	window_image = GenImageColor(800, 600, (Color) { 0 });
    257
    258	zoom = 2;
    259	bar_zoom = 4;
    260	bar_start = 0;
    261	bar_width = MAX(80, window_width / 7);
    262	show_bar = true;
    263
    264	data_window_start = 0;
    265	data_window_len = data_len;
    266
    267	show_pos = false;
    268
    269	window_init_frames = 2;
    270	while (!WindowShouldClose()) {
    271		if (IsKeyPressed(KEY_Q))
    272			break;
    273
    274		if (window_init_frames || IsWindowResized() || IsKeyPressed(KEY_G)) {
    275			window_width = GetScreenWidth();
    276			window_height = GetScreenHeight();
    277			bar_width = MAX(80, window_width / 7);
    278			if (window_init_frames > 0) {
    279				data_width = MAX(1, MIN(data_len,
    280					(window_width - bar_width) / zoom / 2));
    281				data_window_len = MIN(data_len,
    282					MAX(window_width / 2 / zoom
    283						* window_height / 2 / zoom, 200));
    284				data_height = data_window_len / data_width
    285					+ !!(data_window_len % data_width);
    286			}
    287			if (IsKeyPressed(KEY_G))
    288				zoom = 2;
    289			update_data_width();
    290			ImageResize(&window_image, window_width, window_height);
    291			if (window_init_frames)
    292				window_init_frames -= 1;
    293		}
    294
    295		mouse_x = MIN(MAX(0, GetMouseX()), window_width);
    296		mouse_y = MIN(MAX(0, GetMouseY()), window_height);
    297
    298		if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
    299			drag_mouse_x = mouse_x;
    300			drag_mouse_y = mouse_y;
    301			drag_zoom_x = zoom_x;
    302			drag_zoom_y = zoom_y;
    303			drag = true;
    304		} else if (!IsMouseButtonDown(MOUSE_BUTTON_LEFT)) {
    305			drag = false;
    306		}
    307
    308		if (drag && show_bar && drag_mouse_x < bar_width) {
    309			if (mouse_y > window_height - 20) {
    310				bar_start += bar_zoom * bar_width * 20;
    311			} else if (mouse_y < 20) {
    312				bar_start -= bar_zoom * bar_width * 20;
    313			}
    314			bar_start = MAX(0, MIN(data_len - bar_width
    315				* window_height * bar_zoom, bar_start));
    316
    317			data_window_start = bar_start + (mouse_y * bar_width + mouse_x) * bar_zoom;
    318			data_window_start = MAX(0, MIN(data_len - data_window_len,
    319				data_window_start - data_window_len / 2));
    320			update_data_window_start();
    321		} else if (drag) {
    322			zoom_x = drag_zoom_x - (mouse_x - drag_mouse_x) * 1.F / zoom;
    323			zoom_y = drag_zoom_y - (mouse_y - drag_mouse_y) * 1.F / zoom;
    324		}
    325
    326		mouse_move = GetMouseWheelMove();
    327		if (mouse_move > 0 && zoom < ZOOM_MAX) {
    328			zoom_x += (1.F * window_width / zoom / 2)
    329				* (1.F * mouse_x / window_width);
    330			zoom_y += (1.F * window_height / zoom / 2)
    331				* (1.F * mouse_y / window_height);
    332			zoom *= 2;
    333		} else if (mouse_move < 0 && zoom > 1) {
    334			zoom_x -= (1.F * window_width / zoom)
    335				* (1.F * mouse_x / window_width);
    336			zoom_y -= (1.F * window_height / zoom)
    337				* (1.F * mouse_y / window_height);
    338			zoom /= 2;
    339		}
    340
    341		show_pos ^= IsKeyPressed(KEY_T);
    342		show_bar ^= IsKeyPressed(KEY_B);
    343
    344		if (IsKeyDown(KEY_LEFT_SHIFT)) {
    345			if ((b = key_press_hold(2, KEY_LEFT, KEY_A))) {
    346				data_width -= IsKeyDown(KEY_LEFT_ALT) ? 5 : 1;
    347				update_data_width();
    348			} else if ((b = key_press_hold(2, KEY_RIGHT, KEY_D))) {
    349				data_width += IsKeyDown(KEY_LEFT_ALT) ? 5 : 1;
    350				update_data_width();
    351			}
    352		} else if (IsKeyDown(KEY_LEFT_ALT)) {
    353			if ((b = key_press_hold(2, KEY_LEFT, KEY_A))) {
    354				data_window_start -= data_width
    355					* (b == HOLD ? 8 : 1);
    356				update_data_window_start();
    357			} else if ((b = key_press_hold(2, KEY_RIGHT, KEY_D))) {
    358				data_window_start += data_width
    359					* (b == HOLD ? 8 : 1);
    360				update_data_window_start();
    361			}
    362			if ((b = key_press_hold(2, KEY_UP, KEY_W))) {
    363				data_window_len -= data_width
    364					* (b == HOLD ? 8 : 1);
    365				update_data_window_len();
    366			} else if ((b = key_press_hold(2, KEY_DOWN, KEY_S))) {
    367				data_window_len += data_width
    368					* (b == HOLD ? 8 : 1);
    369				update_data_window_len();
    370			}
    371		} else {
    372			if (key_press_hold(2, KEY_LEFT, KEY_A)) {
    373				zoom_x -= 20.F / zoom;
    374			} else if (key_press_hold(2, KEY_RIGHT, KEY_D)) {
    375				zoom_x += 20.F / zoom;
    376			}
    377
    378			if (key_press_hold(2, KEY_UP, KEY_W)) {
    379				zoom_y -= 20.F / zoom;
    380			} else if (key_press_hold(2, KEY_DOWN, KEY_S)) {
    381				zoom_y += 20.F / zoom;
    382			}
    383		}
    384
    385		
    386		if (IsKeyPressed(KEY_P)) {
    387			show_value = !show_value;
    388			if (show_value)
    389				data_colors = value_colors;
    390			else
    391				data_colors = entropy_colors;
    392		}
    393
    394		ImageClearBackground(&window_image, BLACK);
    395
    396		for (y = 0; y < window_height; y++) {
    397			data_y = (ssize_t) (zoom_y) + (y / zoom);
    398			if (data_y < 0 || data_y >= data_height)
    399				continue;
    400
    401			for (x = 0; x < window_width; x++) {
    402				data_x = (ssize_t) (zoom_x) + (x / zoom);
    403				if (data_x < 0 || data_x >= data_width)
    404					continue;
    405
    406				data_pos = data_y * data_width + data_x;
    407				data_pos += data_window_start;
    408				len = data_window_start + data_window_len;
    409				if (len % window_width)
    410					len += window_width - (len % window_width);
    411				if (data_pos < len && data_pos < data_len) {
    412					ImageDrawPixel(&window_image,
    413						x, y, data_colors[data_pos]);
    414				}
    415			}
    416		}
    417
    418		if (show_bar) {
    419			ImageDrawRectangle(&window_image,
    420				0, 0, bar_width, window_height, BLACK);
    421			ImageDrawRectangle(&window_image, bar_width, 0,
    422				3, window_height, BLACK);
    423			ImageDrawLine(&window_image, bar_width + 1, 0,
    424				bar_width + 1, window_height, GRAY);
    425
    426			for (y = 0; y < window_height; y++) {
    427				for (x = 0; x < bar_width; x++) {
    428					data_pos = bar_start + (y * bar_width + x) * bar_zoom;
    429					if (data_pos < data_len) {
    430						ImageDrawPixel(&window_image,
    431							x, y, value_colors[data_pos]);
    432					}
    433				}
    434			}
    435
    436			pos = (data_window_start - bar_start) / bar_width / bar_zoom;
    437			ImageDrawLine(&window_image, 0, pos,
    438				bar_width, pos, WHITE);
    439
    440			pos = MIN(data_len, data_window_start + data_window_len);
    441			pos = (pos - bar_start) / bar_width / bar_zoom;
    442			ImageDrawLine(&window_image, 0, pos,
    443				bar_width, pos, WHITE);
    444		}
    445
    446		if (show_pos) {
    447			data_x = (ssize_t) zoom_x + (mouse_x / zoom);
    448			data_y = (ssize_t) zoom_y + (mouse_y / zoom);
    449			if (data_x >= 0 && data_x < data_width
    450					&& data_y >= 0 && data_y < data_height) {
    451				pos = data_window_start + data_y * data_width + data_x;
    452				snprintf(fmtbuf, sizeof(fmtbuf), "%08lx", pos);
    453				len = MeasureText(fmtbuf, 20) + 10;
    454				ImageDrawRectangle(&window_image,
    455					window_width - len - 9, 0,
    456					len + 9, 19, WHITE);
    457				ImageDrawText(&window_image,
    458					fmtbuf, window_width - len - 6,
    459					0, 20, BLACK);
    460				if (pos < data_len) {
    461					snprintf(fmtbuf, sizeof(fmtbuf), "%02x",
    462						file_data[pos]);
    463					len = MeasureText(fmtbuf, 20);
    464					ImageDrawRectangle(&window_image,
    465						window_width - len - 6, 19,
    466						len + 6, 19, WHITE);
    467					ImageDrawText(&window_image,
    468						fmtbuf, window_width - len - 3,
    469						20, 19, BLACK);
    470				}
    471			}
    472		}
    473
    474		Texture2D tex = LoadTextureFromImage(window_image);
    475
    476		BeginDrawing();
    477
    478		ClearBackground(BLACK);
    479
    480		DrawTexture(tex, 0, 0, WHITE);
    481
    482		EndDrawing();
    483
    484		UnloadTexture(tex);
    485	}
    486
    487	UnloadImage(window_image);
    488
    489	CloseWindow();
    490
    491	free(window_title);
    492}
    493
    494int
    495main(int argc, const char **argv)
    496{
    497	const char **arg;
    498
    499	file_path = NULL;
    500	entropy_ctx = 32;
    501	for (arg = argv + 1; *arg; arg++) {
    502		if (!strcmp("-c", *arg)) {
    503			arg += 1;
    504			if (!arg) usage();
    505			entropy_ctx = atoi(*arg);
    506			if (entropy_ctx <= 0) usage();
    507		} else if (!file_path) {
    508			file_path = *arg;
    509		} else {
    510			usage();
    511		}
    512	}
    513
    514	if (!file_path)
    515		usage();
    516
    517	if (!strcmp(file_path, "-"))
    518		file_path = "/dev/stdin";
    519
    520	file_data = read_file(file_path, &data_len);
    521
    522	value_colors = malloc(sizeof(Color) * data_len);
    523	if (!value_colors) err(1, "malloc");
    524	init_value_colors(value_colors);
    525
    526	entropy_colors = malloc(sizeof(Color) * data_len);
    527	if (!entropy_colors) err(1, "malloc");
    528	init_entropy_colors(entropy_colors);
    529
    530	vis();
    531
    532	free(value_colors);
    533	free(entropy_colors);
    534
    535	free(file_data);
    536}