summaryrefslogtreecommitdiff
path: root/src/kd_image.c
diff options
context:
space:
mode:
authorDoan Luu Duc Tai <doan@luuduct.ai>2026-10-11 19:21:34 -0500
committerDoan Luu Duc Tai <doan@luuduct.ai>2026-10-11 19:21:34 -0500
commitf1ae8c36c94a26e8aff2339e241542f430c96913 (patch)
tree998bc5dceffa7ddd691953717e966502dc4ff622 /src/kd_image.c
Initial commitHEADmain
Diffstat (limited to 'src/kd_image.c')
-rw-r--r--src/kd_image.c334
1 files changed, 334 insertions, 0 deletions
diff --git a/src/kd_image.c b/src/kd_image.c
new file mode 100644
index 0000000..12f8485
--- /dev/null
+++ b/src/kd_image.c
@@ -0,0 +1,334 @@
+#define _POSIX_C_SOURCE 200809L
+
+#include "kd_image.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <X11/Xutil.h>
+
+#define KD_PPM_MAX_PIXELS 20000000UL
+#define KD_PPM_TOKEN_SIZE 64
+
+typedef struct KDXImageHandle {
+ XImage *ximage;
+ GC clip_mask_gc;
+ Pixmap clip_mask;
+} KDXImageHandle;
+
+typedef struct KDRGBImage {
+ unsigned int width;
+ unsigned int height;
+ unsigned char *pixels;
+} KDRGBImage;
+
+static int kd_ppm_token(FILE *file, char *token, size_t token_size)
+{
+ int c;
+ size_t used;
+ int overflow;
+
+ do {
+ c = fgetc(file);
+ if (c == EOF) {
+ return 0;
+ }
+ if (c == '#') {
+ do {
+ c = fgetc(file);
+ } while (c != EOF && c != '\n' && c != '\r');
+ continue;
+ }
+ } while (isspace((unsigned char)c));
+
+ used = 0U;
+ overflow = 0;
+ while (c != EOF && !isspace((unsigned char)c) && c != '#') {
+ if (used + 1U < token_size) {
+ token[used++] = (char)c;
+ } else {
+ overflow = 1;
+ }
+ c = fgetc(file);
+ }
+ if (c == '#') {
+ do {
+ c = fgetc(file);
+ } while (c != EOF && c != '\n' && c != '\r');
+ } else if (c == '\r') {
+ int next_character;
+ next_character = fgetc(file);
+ if (next_character != '\n' && next_character != EOF) {
+ (void)ungetc(next_character, file);
+ }
+ }
+ token[used] = '\0';
+ return overflow ? -1 : 1;
+}
+
+static int kd_parse_positive(const char *token, unsigned long *value)
+{
+ char *end;
+ unsigned long parsed;
+
+ errno = 0;
+ parsed = strtoul(token, &end, 10);
+ if (errno != 0 || end == token || *end != '\0' || parsed == 0UL) {
+ return 0;
+ }
+ *value = parsed;
+ return 1;
+}
+
+static void kd_rgb_free(KDRGBImage *image)
+{
+ if (image != NULL) {
+ free(image->pixels);
+ image->pixels = NULL;
+ image->width = 0U;
+ image->height = 0U;
+ }
+}
+
+static int kd_ppm_read(FILE *file, KDRGBImage *image)
+{
+ char token[KD_PPM_TOKEN_SIZE];
+ int token_result;
+ int binary;
+ unsigned long width;
+ unsigned long height;
+ unsigned long max_value;
+ unsigned long pixel_count;
+ unsigned long i;
+ unsigned long scaled;
+ unsigned char *pixels;
+ size_t bytes;
+ size_t offset;
+ unsigned int channel;
+ char *end;
+
+ memset(image, 0, sizeof(*image));
+ token_result = kd_ppm_token(file, token, sizeof(token));
+ if (token_result != 1 || (strcmp(token, "P6") != 0 && strcmp(token, "P3") != 0)) {
+ return 0;
+ }
+ binary = strcmp(token, "P6") == 0;
+
+ if (kd_ppm_token(file, token, sizeof(token)) != 1 ||
+ !kd_parse_positive(token, &width)) {
+ return 0;
+ }
+ if (kd_ppm_token(file, token, sizeof(token)) != 1 ||
+ !kd_parse_positive(token, &height)) {
+ return 0;
+ }
+ if (kd_ppm_token(file, token, sizeof(token)) != 1 ||
+ !kd_parse_positive(token, &max_value) || max_value > 255UL) {
+ return 0;
+ }
+ pixel_count = width * height;
+ if (width > 8192UL || height > 8192UL ||
+ pixel_count > KD_PPM_MAX_PIXELS ||
+ pixel_count > (unsigned long)(((size_t)-1) / 3U)) {
+ return 0;
+ }
+ bytes = (size_t)pixel_count * 3U;
+ pixels = (unsigned char *)malloc(bytes);
+ if (pixels == NULL) {
+ return 0;
+ }
+
+ if (binary) {
+ if (fread(pixels, 1U, bytes, file) != bytes) {
+ free(pixels);
+ return 0;
+ }
+ if (max_value != 255UL) {
+ for (i = 0UL; i < (unsigned long)bytes; i++) {
+ scaled = ((unsigned long)pixels[i] * 255UL + max_value / 2UL) /
+ max_value;
+ pixels[i] = (unsigned char)scaled;
+ }
+ }
+ } else {
+ offset = 0U;
+ for (i = 0UL; i < pixel_count; i++) {
+ for (channel = 0U; channel < 3U; channel++) {
+ if (kd_ppm_token(file, token, sizeof(token)) != 1) {
+ free(pixels);
+ return 0;
+ }
+ errno = 0;
+ scaled = strtoul(token, &end, 10);
+ if (errno != 0 || end == token || *end != '\0' ||
+ scaled > max_value) {
+ free(pixels);
+ return 0;
+ }
+ pixels[offset++] = (unsigned char)
+ ((scaled * 255UL + max_value / 2UL) / max_value);
+ }
+ }
+ }
+ image->width = (unsigned int)width;
+ image->height = (unsigned int)height;
+ image->pixels = pixels;
+ return 1;
+}
+
+static unsigned long kd_component_pixel(unsigned char value, unsigned long mask)
+{
+ unsigned int shift;
+ unsigned long reduced_mask;
+ unsigned long scaled;
+
+ if (mask == 0UL) {
+ return 0UL;
+ }
+ shift = 0U;
+ reduced_mask = mask;
+ while ((reduced_mask & 1UL) == 0UL) {
+ reduced_mask >>= 1;
+ shift++;
+ }
+ scaled = ((unsigned long)value * reduced_mask + 127UL) / 255UL;
+ return (scaled << shift) & mask;
+}
+
+static void kd_choose_size(unsigned int source_width,
+ unsigned int source_height,
+ unsigned int max_width,
+ unsigned int max_height,
+ unsigned int *result_width,
+ unsigned int *result_height)
+{
+ double scale;
+
+ scale = 1.0;
+ if (max_width == 0U) {
+ max_width = source_width;
+ }
+ if (max_height == 0U) {
+ max_height = source_height;
+ }
+ if (source_width > max_width) {
+ scale = (double)max_width / (double)source_width;
+ }
+ if ((double)source_height * scale > (double)max_height) {
+ scale = (double)max_height / (double)source_height;
+ }
+ *result_width = (unsigned int)((double)source_width * scale);
+ *result_height = (unsigned int)((double)source_height * scale);
+ if (*result_width == 0U) {
+ *result_width = 1U;
+ }
+ if (*result_height == 0U) {
+ *result_height = 1U;
+ }
+}
+
+struct nk_image kd_ppm_load(Display *display, int screen,
+ const char *path,
+ unsigned int max_width,
+ unsigned int max_height)
+{
+ struct nk_image result;
+ FILE *file;
+ KDRGBImage source;
+ KDXImageHandle *handle;
+ XImage *ximage;
+ Visual *visual;
+ unsigned int out_width;
+ unsigned int out_height;
+ unsigned int x;
+ unsigned int y;
+ unsigned int source_x;
+ unsigned int source_y;
+ size_t source_offset;
+ unsigned char red;
+ unsigned char green;
+ unsigned char blue;
+ unsigned long pixel;
+
+ memset(&result, 0, sizeof(result));
+ memset(&source, 0, sizeof(source));
+ file = fopen(path, "rb");
+ if (file == NULL) {
+ return result;
+ }
+ if (!kd_ppm_read(file, &source)) {
+ (void)fclose(file);
+ return result;
+ }
+ (void)fclose(file);
+ kd_choose_size(source.width, source.height, max_width, max_height,
+ &out_width, &out_height);
+
+ visual = DefaultVisual(display, screen);
+ ximage = XCreateImage(display, visual,
+ (unsigned int)DefaultDepth(display, screen),
+ ZPixmap, 0, NULL, out_width, out_height,
+ BitmapPad(display), 0);
+ if (ximage == NULL) {
+ kd_rgb_free(&source);
+ return result;
+ }
+ ximage->data = (char *)calloc((size_t)ximage->bytes_per_line,
+ (size_t)out_height);
+ if (ximage->data == NULL) {
+ XDestroyImage(ximage);
+ kd_rgb_free(&source);
+ return result;
+ }
+
+ for (y = 0U; y < out_height; y++) {
+ source_y = (unsigned int)(((unsigned long)y * source.height) / out_height);
+ for (x = 0U; x < out_width; x++) {
+ source_x = (unsigned int)(((unsigned long)x * source.width) / out_width);
+ source_offset = ((size_t)source_y * source.width + source_x) * 3U;
+ red = source.pixels[source_offset];
+ green = source.pixels[source_offset + 1U];
+ blue = source.pixels[source_offset + 2U];
+ pixel = kd_component_pixel(red, visual->red_mask) |
+ kd_component_pixel(green, visual->green_mask) |
+ kd_component_pixel(blue, visual->blue_mask);
+ (void)XPutPixel(ximage, (int)x, (int)y, pixel);
+ }
+ }
+ kd_rgb_free(&source);
+
+ handle = (KDXImageHandle *)calloc(1U, sizeof(KDXImageHandle));
+ if (handle == NULL) {
+ XDestroyImage(ximage);
+ return result;
+ }
+ handle->ximage = ximage;
+ handle->clip_mask_gc = (GC)0;
+ handle->clip_mask = (Pixmap)0;
+ result.handle.ptr = (void *)handle;
+ result.w = (unsigned short)out_width;
+ result.h = (unsigned short)out_height;
+ result.region[0] = 0;
+ result.region[1] = 0;
+ result.region[2] = (unsigned short)out_width;
+ result.region[3] = (unsigned short)out_height;
+ return result;
+}
+
+void kd_ppm_free(struct nk_image *image)
+{
+ KDXImageHandle *handle;
+
+ if (image == NULL || image->handle.ptr == NULL) {
+ return;
+ }
+ handle = (KDXImageHandle *)image->handle.ptr;
+ if (handle->ximage != NULL) {
+ XDestroyImage(handle->ximage);
+ }
+ free(handle);
+ memset(image, 0, sizeof(*image));
+}