summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMikhail Burakov <mburakov@mailbox.org>2020-06-29 19:02:09 +0200
committerMikhail Burakov <mburakov@mailbox.org>2020-06-29 19:02:09 +0200
commit3261aee563ee7bb0cba7edf4e684ae9833fd9a3f (patch)
treedd354a1b6e65aa230b059dd8c3c093d30632b76a
parent3389b2ba80c60009fac91cece3241ec7b2b5b759 (diff)
Import current pui code to git
-rw-r--r--convert.c344
-rw-r--r--main.c190
-rw-r--r--makefile32
-rw-r--r--pui.c91
-rw-r--r--pui.h26
-rw-r--r--sample.bmpbin0 -> 16538 bytes
-rw-r--r--sample.ini28
7 files changed, 711 insertions, 0 deletions
diff --git a/convert.c b/convert.c
new file mode 100644
index 0000000..e2191ce
--- /dev/null
+++ b/convert.c
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2020 Mikhail Burakov. This file is part of Pui.
+ *
+ * Pui is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Pui is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Pui. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <ctype.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+enum {
+ kNoSection = 0,
+ kSectionElements,
+ kSectionDefaultPalette,
+ kSectionActivePalette
+};
+
+struct PuiElement {
+ uint8_t x, y, w, h;
+};
+
+struct MappedFile {
+ void* data;
+ size_t size;
+};
+
+struct BitmapHeader {
+ uint16_t magic;
+ uint32_t size;
+ uint16_t reserved[2];
+ uint32_t offset;
+} __attribute__((packed));
+
+struct DibHeader {
+ uint32_t size;
+ int32_t width;
+ int32_t height;
+ uint16_t planes;
+ uint16_t bpp;
+} __attribute__((packed));
+
+struct Bitmap {
+ struct MappedFile file;
+ struct BitmapHeader* bitmap_header;
+ struct DibHeader* dib_header;
+ uint8_t* ptr;
+ int index;
+};
+
+static void MapFile(const char* file, struct MappedFile* result) {
+ int fd = open(file, O_RDONLY);
+ if (fd == -1) err(1, "Failed to open file");
+ struct stat buf;
+ if (fstat(fd, &buf)) err(1, "Failed to stat file");
+ void* data = mmap(NULL, (size_t)buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ close(fd);
+ if (data == MAP_FAILED) err(1, "Failed to map file");
+ result->data = data;
+ result->size = (size_t)buf.st_size;
+}
+
+static void UnmapFile(struct MappedFile* result) {
+ munmap(result->data, result->size);
+}
+
+static int ReadBitmap(struct Bitmap* bitmap) {
+ uint8_t* buffer = bitmap->file.data;
+ if (bitmap->ptr < buffer + bitmap->bitmap_header->offset) return -1;
+ int result = bitmap->ptr[bitmap->index >> 1] >> ((~bitmap->index & 1) << 2);
+ if (++bitmap->index == bitmap->dib_header->width) {
+ size_t stride = ((size_t)(bitmap->dib_header->width >> 1) + 3) & ~3u;
+ bitmap->ptr -= stride;
+ bitmap->index = 0;
+ }
+ return result & 0x7;
+}
+
+static void OpenBitmap(const char* fname, struct Bitmap* bitmap) {
+ MapFile(fname, &bitmap->file);
+ uint8_t* data = bitmap->file.data;
+ bitmap->bitmap_header = (struct BitmapHeader*)data;
+ bitmap->dib_header = (struct DibHeader*)(data + sizeof(struct BitmapHeader));
+ if (bitmap->bitmap_header->magic != 0x4d42) err(1, "Invalid bitmap magic");
+ if (bitmap->dib_header->width < 0 || bitmap->dib_header->height < 0 ||
+ bitmap->dib_header->width > UINT16_MAX ||
+ bitmap->dib_header->height > UINT16_MAX ||
+ bitmap->dib_header->planes != 1 || bitmap->dib_header->bpp != 4)
+ err(1, "Unsupported bitmap format");
+ size_t stride = ((size_t)(bitmap->dib_header->width >> 1) + 3) & ~3u;
+ bitmap->ptr = data + bitmap->bitmap_header->offset +
+ stride * (size_t)(bitmap->dib_header->height - 1);
+}
+
+static void Write(int item, FILE* out) {
+ static int current = -1;
+ if (current == -1) {
+ if (item == -1) return;
+ current = (item & 0xf) << 4;
+ } else {
+ if (item == -1) item = 0;
+ current |= item & 0xf;
+ fputc(current, out);
+ current = -1;
+ }
+}
+
+static void WriteCounter(int counter, FILE* out) {
+ int pos = 0;
+ while (counter >> (++pos * 3))
+ ;
+ while (pos-- > 0) Write(0x8 | counter >> (pos * 3), out);
+}
+
+static void WriteSequence(int prev, int counter, FILE* out) {
+ switch (counter) {
+ case 1:
+ Write(prev, out);
+ break;
+ case 2:
+ Write(prev, out);
+ Write(prev, out);
+ break;
+ default:
+ WriteCounter(counter - 2, out);
+ Write(prev, out);
+ break;
+ }
+}
+
+static void SelectScale(int32_t size, uint8_t* value, uint8_t* scale) {
+ if (size < 0) err(1, "Size must be non-negative");
+ for (int shift = 0, mask = 0;; shift++, mask = mask << 1 | 1) {
+ if (size & mask) err(1, "Size is not aligned");
+ if (size < 0x100 << shift) {
+ *value = (uint8_t)(size >> shift);
+ *scale = (uint8_t)shift;
+ return;
+ }
+ }
+}
+
+static int IsMeaningless(int ch) { return !isgraph(ch); }
+static int IsMeaningful(int ch) { return isprint(ch); }
+
+static void SkipGroup(struct MappedFile* file, int (*pred)(int ch)) {
+ char* ptr = file->data;
+ size_t size = file->size;
+ for (; size && pred(*ptr); ptr++, size--)
+ ;
+ file->data = ptr;
+ file->size = size;
+}
+
+static int ChangeSection(struct MappedFile* file) {
+ char* begin = file->data;
+ SkipGroup(file, IsMeaningful);
+ size_t size = (size_t)((char*)file->data - begin);
+ static const char kElements[] = {'[', 'E', 'l', 'e', 'm',
+ 'e', 'n', 't', 's', ']'};
+ static const char kDefaultPalette[] = {'[', 'D', 'e', 'f', 'a', 'u',
+ 'l', 't', 'P', 'a', 'l', 'e',
+ 't', 't', 'e', ']'};
+ static const char kActivePalette[] = {'[', 'A', 'c', 't', 'i', 'v', 'e', 'P',
+ 'a', 'l', 'e', 't', 't', 'e', ']'};
+ switch (size) {
+ case sizeof(kElements):
+ if (!memcmp(begin, kElements, sizeof(kElements))) return kSectionElements;
+ break;
+ case sizeof(kDefaultPalette):
+ if (!memcmp(begin, kDefaultPalette, sizeof(kDefaultPalette)))
+ return kSectionDefaultPalette;
+ break;
+ case sizeof(kActivePalette):
+ if (!memcmp(begin, kActivePalette, sizeof(kActivePalette)))
+ return kSectionActivePalette;
+ break;
+ default:
+ break;
+ }
+ return kNoSection;
+}
+
+static int GetIndex(char* ptr, size_t size, const char* prefix,
+ size_t prefix_length) {
+ if (size < prefix_length + 3 || memcmp(ptr, prefix, prefix_length) ||
+ !isdigit(ptr[prefix_length]) || !isdigit(ptr[prefix_length + 1]) ||
+ ptr[prefix_length + 2] != '=')
+ return -1;
+ return atoi(ptr + prefix_length);
+}
+
+static int ParseElement(struct MappedFile* file, uint8_t scale[2],
+ struct PuiElement* elements) {
+ char* ptr = file->data;
+ SkipGroup(file, IsMeaningful);
+ size_t size = (size_t)((char*)file->data - ptr);
+ static const char kElement[] = {'E', 'l', 'e', 'm', 'e', 'n', 't'};
+ int index = GetIndex(ptr, size, kElement, sizeof(kElement));
+ if (index == -1 || index > 14) return -1;
+ ptr += sizeof(kElement) + 3;
+ size -= sizeof(kElement) + 3;
+ int values[] = {0, 0, 0, 0};
+ int *value = values, *end = values + 4;
+ for (; size; ptr++, size--) {
+ if ('0' <= *ptr && *ptr <= '9') {
+ *value = *value * 10 + *ptr - '0';
+ } else if (*ptr == ',') {
+ if (++value == end) break;
+ } else {
+ break;
+ }
+ }
+ elements[index].x = (uint8_t)(values[0] >> scale[0]);
+ elements[index].y = (uint8_t)(values[1] >> scale[1]);
+ elements[index].w = (uint8_t)(values[2] >> scale[0]);
+ elements[index].h = (uint8_t)(values[3] >> scale[1]);
+ return index;
+}
+
+static int ParsePalette(struct MappedFile* file, uint32_t* palette) {
+ char* ptr = file->data;
+ SkipGroup(file, IsMeaningful);
+ size_t size = (size_t)((char*)file->data - ptr);
+ static const char kColor[] = {'C', 'o', 'l', 'o', 'r'};
+ int index = GetIndex(ptr, size, kColor, sizeof(kColor));
+ if (index == -1 || index > 7) return -1;
+ ptr += sizeof(kColor) + 3;
+ size -= sizeof(kColor) + 3;
+ uint32_t value = 0;
+ for (; size; ptr++, size--) {
+ if ('0' <= *ptr && *ptr <= '9') {
+ value = value << 4 | (unsigned)(*ptr - '0');
+ } else if ('A' <= *ptr && *ptr <= 'F') {
+ value = value << 4 | (unsigned)(*ptr - 'A' + 10);
+ } else if ('a' <= *ptr && *ptr <= 'f') {
+ value = value << 4 | (unsigned)(*ptr - 'a' + 10);
+ } else {
+ break;
+ }
+ }
+ palette[index] = (value >> 24 & 0xff) | (value >> 8 & 0xff00) |
+ (value << 8 & 0xff0000) | (value << 24 & 0xff000000);
+ return index;
+}
+
+static void CompressDefinition(const struct MappedFile* definition,
+ uint8_t scale[2], FILE* out) {
+ struct PuiElement elements[0xf];
+ memset(elements, 0, sizeof(elements));
+ uint32_t palettes[2][8];
+ memset(palettes, 0, sizeof(palettes));
+ int section = kNoSection;
+ int max_element = 0, max_color = 0;
+ for (struct MappedFile def = *definition;;) {
+ SkipGroup(&def, IsMeaningless);
+ if (!def.size) break;
+ switch (*(char*)def.data) {
+ case '#':
+ SkipGroup(&def, IsMeaningful);
+ continue;
+ case '[':
+ section = ChangeSection(&def);
+ continue;
+ default:
+ break;
+ }
+ switch (section) {
+ case kSectionElements: {
+ int index = ParseElement(&def, scale, elements);
+ if (index > max_element) max_element = index;
+ break;
+ }
+ case kSectionDefaultPalette: {
+ int index = ParsePalette(&def, palettes[0]);
+ if (index > max_color) max_color = index;
+ break;
+ }
+ case kSectionActivePalette: {
+ int index = ParsePalette(&def, palettes[1]);
+ if (index > max_color) max_color = index;
+ break;
+ }
+ default:
+ SkipGroup(&def, IsMeaningful);
+ break;
+ }
+ }
+ int count_elements = max_element + 1;
+ int count_colors = max_color + 1;
+ uint8_t counter = (count_elements << 4 & 0xf0) | (count_colors & 0xf);
+ fwrite(&counter, 1, 1, out);
+ fwrite(elements, (size_t)count_elements * sizeof(struct PuiElement), 1, out);
+ fwrite(palettes[0], (size_t)count_colors * sizeof(uint32_t), 1, out);
+ fwrite(palettes[1], (size_t)count_colors * sizeof(uint32_t), 1, out);
+}
+
+int main(int argc, char** argv) {
+ if (argc < 3) err(1, "Usage: %s [source.bmp] [definition.ini]", argv[0]);
+ struct Bitmap bitmap;
+ memset(&bitmap, 0, sizeof(bitmap));
+ OpenBitmap(argv[1], &bitmap);
+ uint8_t whs[3], scale[2];
+ SelectScale(bitmap.dib_header->width, &whs[0], &scale[0]);
+ SelectScale(bitmap.dib_header->height, &whs[1], &scale[1]);
+ struct MappedFile definition;
+ MapFile(argv[2], &definition);
+ whs[2] = (uint8_t)(scale[0] << 4 | scale[1]);
+ fwrite(whs, sizeof(whs), 1, stdout);
+ CompressDefinition(&definition, scale, stdout);
+ UnmapFile(&definition);
+ for (int counter = 0, prev = -1;;) {
+ int current = ReadBitmap(&bitmap);
+ if (current == prev || prev == -1) {
+ counter++;
+ } else {
+ WriteSequence(prev, counter, stdout);
+ if (current == -1) break;
+ counter = 1;
+ }
+ prev = current;
+ }
+ UnmapFile(&bitmap.file);
+ Write(-1, stdout);
+ return 0;
+}
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..5cc4299
--- /dev/null
+++ b/main.c
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2020 Mikhail Burakov. This file is part of Pui.
+ *
+ * Pui is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Pui is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Pui. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <wayland-client.h>
+
+#include "pui.h"
+#include "xdg-shell.h"
+
+struct Context {
+ const void* pui;
+
+ struct wl_display* display;
+ struct wl_registry* registry;
+
+ struct wl_shm* shm;
+ struct wl_compositor* compositor;
+ struct xdg_wm_base* wm_base;
+
+ struct wl_surface* surface;
+ struct xdg_surface* xdg_surface;
+ struct xdg_toplevel* xdg_toplevel;
+};
+
+// === Wayland registry ===
+
+static void OnRegistryGlobal(void* data, struct wl_registry* registry,
+ uint32_t name, const char* interface,
+ uint32_t version) {
+ (void)version;
+ struct Context* ctx = data;
+ if (!strcmp(interface, wl_compositor_interface.name))
+ ctx->compositor =
+ wl_registry_bind(registry, name, &wl_compositor_interface, 4);
+ else if (!strcmp(interface, wl_shm_interface.name))
+ ctx->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
+ else if (!strcmp(interface, xdg_wm_base_interface.name))
+ ctx->wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 2);
+}
+
+static void OnRegistryGlobalRemove(void* data, struct wl_registry* registry,
+ uint32_t name) {
+ (void)data;
+ (void)registry;
+ (void)name;
+}
+
+static const struct wl_registry_listener g_registry_listener = {
+ .global = OnRegistryGlobal, .global_remove = OnRegistryGlobalRemove};
+
+// === Wayland WM base listener ===
+
+static void OnPing(void* data, struct xdg_wm_base* xdg_wm_base,
+ uint32_t serial) {
+ (void)data;
+ xdg_wm_base_pong(xdg_wm_base, serial);
+}
+
+static const struct xdg_wm_base_listener g_wm_base_listener = {.ping = OnPing};
+
+// === Wayland buffer ===
+
+static void OnBufferRelease(void* data, struct wl_buffer* wl_buffer) {
+ (void)data;
+ wl_buffer_destroy(wl_buffer);
+}
+
+static const struct wl_buffer_listener g_buffer_listener = {
+ .release = OnBufferRelease};
+
+// === Wayland XDG surface ===
+
+static void OnSurfaceConfigure(void* data, struct xdg_surface* xdg_surface,
+ uint32_t serial) {
+ (void)data;
+ xdg_surface_ack_configure(xdg_surface, serial);
+}
+
+static const struct xdg_surface_listener g_surface_listener = {
+ .configure = OnSurfaceConfigure};
+
+// === Helper functions ===
+
+static const void* CreatePui(const char* filename) {
+ int fd = open(filename, O_RDONLY);
+ if (fd == -1) err(1, "Failed to open pui file");
+ struct stat buf;
+ if (fstat(fd, &buf)) err(1, "Failed to stat pui file");
+ const void* result =
+ mmap(NULL, (size_t)buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (result == MAP_FAILED) err(1, "Failed to mmap pui file");
+ return result;
+}
+
+static int CreateShm(int size) {
+ static const char kShmName[] = "/wl_shm-pui";
+ int fd = shm_open(kShmName, O_RDWR | O_CREAT | O_EXCL, 0600);
+ if (fd == -1) err(1, "Failed to create shm");
+ shm_unlink(kShmName);
+ if (ftruncate(fd, size)) err(1, "Failed to truncate shm");
+ return fd;
+}
+
+static void RenderPui(const void* pui, int fd) {
+ int width = PuiGetWidth(pui);
+ int height = PuiGetHeight(pui);
+ size_t size = (size_t)width * (size_t)height * 4;
+ void* buffer = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (buffer == MAP_FAILED) err(1, "Failed to map shm");
+ PuiRender(pui, buffer, width, 0);
+ for (int i = 0; i < width * height; i++) {
+ uint32_t* ptr = (uint32_t*)buffer + i;
+ *ptr = (*ptr << 16 & 0xff0000) | (*ptr >> 16 & 0xff) | (*ptr & 0xff00);
+ }
+ munmap(buffer, size);
+}
+
+static struct wl_buffer* PrepareBuffer(struct Context* ctx) {
+ int width = PuiGetWidth(ctx->pui);
+ int height = PuiGetHeight(ctx->pui);
+ int stride = width * 4;
+ int size = stride * height;
+ int fd = CreateShm(size);
+ struct wl_shm_pool* pool = wl_shm_create_pool(ctx->shm, fd, size);
+ struct wl_buffer* buffer = wl_shm_pool_create_buffer(
+ pool, 0, width, height, stride, WL_SHM_FORMAT_XRGB8888);
+ wl_shm_pool_destroy(pool);
+ RenderPui(ctx->pui, fd);
+ close(fd);
+ return buffer;
+}
+
+int main(int argc, char** argv) {
+ if (argc < 2) err(1, "Usage: %s [file.pui]", argv[0]);
+ struct Context ctx = {.pui = CreatePui(argv[1]),
+ .display = wl_display_connect(NULL)};
+ if (!ctx.pui) err(1, "Failed to create pui");
+ if (!ctx.display) err(1, "Failed to connect to wayland display");
+ ctx.registry = wl_display_get_registry(ctx.display);
+ if (!ctx.registry) err(1, "Failed to get wayland registry");
+ if (wl_registry_add_listener(ctx.registry, &g_registry_listener, &ctx))
+ err(1, "Failed to add wayland registry listener");
+ if (wl_display_roundtrip(ctx.display) == -1)
+ err(1, "Failed to roundtrip wayland display");
+
+ if (xdg_wm_base_add_listener(ctx.wm_base, &g_wm_base_listener, &ctx))
+ err(1, "Failed to add wayland wm base listener");
+ ctx.surface = wl_compositor_create_surface(ctx.compositor);
+ if (!ctx.surface) err(1, "Failed to create wayland surface");
+ ctx.xdg_surface = xdg_wm_base_get_xdg_surface(ctx.wm_base, ctx.surface);
+ if (!ctx.xdg_surface) err(1, "Failed to get wayland xdg surface");
+ if (xdg_surface_add_listener(ctx.xdg_surface, &g_surface_listener, &ctx))
+ err(1, "Failed to add wayland xdg surface listener");
+ ctx.xdg_toplevel = xdg_surface_get_toplevel(ctx.xdg_surface);
+ if (!ctx.xdg_toplevel) err(1, "Failed to get wayland xdg toplevel");
+ xdg_toplevel_set_title(ctx.xdg_toplevel, "Pui");
+ wl_surface_commit(ctx.surface);
+ if (wl_display_roundtrip(ctx.display) == -1)
+ err(1, "Failed to roundtrip wayland display");
+
+ struct wl_buffer* buffer = PrepareBuffer(&ctx);
+ wl_buffer_add_listener(buffer, &g_buffer_listener, &ctx);
+ wl_surface_attach(ctx.surface, buffer, 0, 0);
+ wl_surface_commit(ctx.surface);
+
+ while (wl_display_dispatch(ctx.display) != -1)
+ ;
+ err(1, "Failed to dispatch wayland display");
+}
diff --git a/makefile b/makefile
new file mode 100644
index 0000000..b954d99
--- /dev/null
+++ b/makefile
@@ -0,0 +1,32 @@
+target:=pui
+objects:=main.o pui.o xdg-shell.o
+protocols:=/usr/share/wayland-protocols
+wl_headers:=xdg-shell.h
+
+CFLAGS:=-O3 -Wall -Wextra -Werror -pedantic
+LDFLAGS:=-O3 -s -lwayland-client -lrt
+
+all: $(target) sample.pui
+
+sample.pui: convert sample.bmp sample.ini
+ $^ > $@
+
+convert: convert.c
+ $(CC) $(CFLAGS) -s $< -o $@
+
+$(target): $(objects)
+ $(CC) $(LDFLAGS) $^ -o $@
+
+%.o: %.c *.h $(wl_headers)
+ $(CC) $(CFLAGS) -c $< -o $@
+
+%.c: $(protocols)/*/*/%.xml
+ wayland-scanner private-code $< $@
+
+%.h: $(protocols)/*/*/%.xml
+ wayland-scanner client-header $< $@
+
+clean:
+ rm $(target) $(objects) $(wl_headers) convert sample.pui
+
+.PRECIOUS: $(wl_headers)
diff --git a/pui.c b/pui.c
new file mode 100644
index 0000000..cb89991
--- /dev/null
+++ b/pui.c
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2020 Mikhail Burakov. This file is part of Pui.
+ *
+ * Pui is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Pui is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Pui. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "pui.h"
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+struct PuiHeader {
+ uint8_t w, h, scale, count;
+};
+
+struct PuiElement {
+ uint8_t x, y, w, h;
+};
+
+static_assert(sizeof(struct PuiHeader) == 4, "Invalid PuiHeader size");
+static_assert(sizeof(struct PuiElement) == 4, "Invalid PuiElement size");
+
+int PuiGetWidth(const void* pui_data) {
+ const struct PuiHeader* pui_header = pui_data;
+ int sx = pui_header->scale >> 4;
+ return pui_header->w << sx;
+}
+
+int PuiGetHeight(const void* pui_data) {
+ const struct PuiHeader* pui_header = pui_data;
+ int sy = pui_header->scale & 0xf;
+ return pui_header->h << sy;
+}
+
+int PuiHitTest(const void* pui_data, int x, int y) {
+ int result = 0;
+ const struct PuiHeader* pui_header = pui_data;
+ x >>= pui_header->scale >> 4;
+ y >>= pui_header->scale & 0xf;
+ int count = pui_header->count >> 4;
+ for (int i = 0; i < count; ++i) {
+ const struct PuiElement* element =
+ (const struct PuiElement*)pui_data + 1 + i;
+ int hit = element->x <= x && x < element->x + element->w &&
+ element->y <= y && y < element->y + element->h;
+ result |= hit << i;
+ }
+ return result;
+}
+
+void PuiRender(const void* pui_data, void* buffer, int stride, int active) {
+ const struct PuiHeader* pui_header = pui_data;
+ int width = pui_header->w << (pui_header->scale >> 4);
+ int height = pui_header->h << (pui_header->scale & 0xf);
+ int elements = pui_header->count >> 4;
+ int colors = pui_header->count & 0xf;
+ const uint32_t* palettes[] = {
+ (const uint32_t*)pui_data + 1 + elements,
+ (const uint32_t*)pui_data + 1 + elements + colors,
+ };
+ const uint8_t* bitmap_data =
+ (const uint8_t*)pui_data + 4 * (1 + elements + 2 * colors);
+ for (int idx = 0, counter = 0, offset = 0; offset < width * height; idx++) {
+ int value = bitmap_data[idx >> 1];
+ if (~idx & 1) value = value >> 4;
+ if (value & 0x8) {
+ counter = counter << 3 | (value & 0x7);
+ continue;
+ }
+ for (counter = counter ? counter + 2 : 1; counter-- > 0; offset++) {
+ int x = offset % width;
+ int y = offset / width;
+ int palette = !!(PuiHitTest(pui_data, x, y) & active);
+ ((uint32_t*)buffer)[x + y * stride] = palettes[palette][value & 0x7];
+ }
+ counter = 0;
+ }
+}
diff --git a/pui.h b/pui.h
new file mode 100644
index 0000000..4d58e68
--- /dev/null
+++ b/pui.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2020 Mikhail Burakov. This file is part of Pui.
+ *
+ * Pui is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Pui is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Pui. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef PUI_PUI_H_
+#define PUI_PUI_H_
+
+int PuiGetWidth(const void* pui_data);
+int PuiGetHeight(const void* pui_data);
+int PuiHitTest(const void* pui_data, int x, int y);
+void PuiRender(const void* pui_data, void* buffer, int stride, int active);
+
+#endif // PUI_PUI_H_
diff --git a/sample.bmp b/sample.bmp
new file mode 100644
index 0000000..3188f68
--- /dev/null
+++ b/sample.bmp
Binary files differ
diff --git a/sample.ini b/sample.ini
new file mode 100644
index 0000000..d89a17d
--- /dev/null
+++ b/sample.ini
@@ -0,0 +1,28 @@
+[Elements]
+Element00=16,16,96,16
+Element01=16,40,96,16
+Element02=16,72,96,24
+Element03=16,104,96,24
+Element04=16,136,96,24
+Element05=16,168,96,24
+Element06=16,200,96,24
+
+[DefaultPalette]
+Color00=ffffd700
+Color01=62626200
+Color02=ffffd700
+Color03=80000000
+Color04=40200400
+Color05=ffffff00
+Color06=80808000
+Color07=00000000
+
+[ActivePalette]
+Color00=ffffd700
+Color01=62626200
+Color02=58585800
+Color03=80000000
+Color04=40200400
+Color05=ffffff00
+Color06=80808000
+Color07=00000000