#define _POSIX_C_SOURCE 200809L #include "kd_image.h" #include #include #include #include #include #include #include #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)); }