diff options
Diffstat (limited to 'src/kd_image.c')
| -rw-r--r-- | src/kd_image.c | 334 |
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)); +} |
