home | blog | art | now | git gpg | email | rss

morph

Generate xresources colors from an image.
git clone https://pollux.codes/git/morph.git
Log | Files | Refs | README | LICENSE
commit 6f4565a9ed0e17dfb2a1ff0689594315796bd16d
parent e5309c10078b6479f53a83f28b53339362f7c05d
Author: Pollux <pollux@pollux.codes>
Date:   Sun, 20 Jul 2025 21:58:17 -0500

fix: Misc code improvements

- Add proper error handling.
- Clean up code formatting.

Signed-off-by: Pollux <pollux@pollux.codes>

Diffstat:
Mmorph.c | 191++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
1 file changed, 111 insertions(+), 80 deletions(-)

diff --git a/morph.c b/morph.c @@ -1,10 +1,13 @@ /* See LICENSE file for copyright and license details. */ +#include <errno.h> #include <math.h> +#include <stdarg.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> +#include <string.h> #include <Imlib2.h> @@ -40,6 +43,23 @@ typedef struct { size_t len; } col_cluster_t; +void +die(const char *fmt, ...) { + va_list ap; + int saved_errno; + + saved_errno = errno; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if(fmt[0] && fmt[strlen(fmt) - 1] == ':') + fprintf(stderr, " %s", strerror(saved_errno)); + fputc('\n', stderr); + exit(1); +} + int compare_l(const void *a, const void *b) { const col_lab_t *ca = (const col_lab_t *)a; @@ -76,6 +96,13 @@ compare_b(const void *a, const void *b) { } } +float +get_color_distance(const col_lab_t c1, const col_lab_t c2) { + return sqrtf((c1.l - c2.l) * (c1.l - c2.l) + + (c1.a - c2.a) * (c1.a - c2.a) + (c1.b - c2.b) * (c1.b - + c2.b)); +} + /// Convert a color in the srgb color space, used for color IO, to the OkLab /// perceptual color space. col_lab_t @@ -142,14 +169,18 @@ lab_to_rgb(col_lab_t lab) { /// Loads the image located at the given path and extracts the colors in the /// pixels into an array of col_rgb_t. The caller is responsible for freeing -/// the return value. +/// the return value. Returns NULL if the image failed to load. col_rgb_t * get_image_pixel_data(const char *path, int *pixel_count) { int image_width, image_height; col_rgb_t *image_pixels; + DATA32 *image_data, c; + Imlib_Image image; - Imlib_Image image = imlib_load_image(path); // TODO: Make sure image loaded + image = imlib_load_image(path); + if(image == NULL) + return NULL; imlib_context_set_image(image); @@ -160,14 +191,15 @@ get_image_pixel_data(const char *path, int *pixel_count) { image_pixels = malloc(*pixel_count * sizeof(col_rgb_t)); - DATA32 *image_data = imlib_image_get_data_for_reading_only(); + image_data = imlib_image_get_data_for_reading_only(); + + for(int pixel = 0; pixel < *pixel_count; pixel++) { - for(int pixel = 0; pixel < image_width * image_height; pixel++) { - DATA32 color = *(image_data + pixel); + c = *(image_data + pixel); - image_pixels[pixel].r = ((color & 0x00ff0000) >> 16) / 255.0f; - image_pixels[pixel].g = ((color & 0x0000ff00) >> 8) / 255.0f; - image_pixels[pixel].b = (color & 0x000000ff) / 255.0f; + image_pixels[pixel].r = ((c & 0x00ff0000) >> 16) / 255.0f; + image_pixels[pixel].g = ((c & 0x0000ff00) >> 8) / 255.0f; + image_pixels[pixel].b = (c & 0x000000ff) / 255.0f; } imlib_free_image(); @@ -179,30 +211,38 @@ get_image_pixel_data(const char *path, int *pixel_count) { col_lab_t * cluster_image_colors(col_lab_t *colors, int color_count, int cluster_count) { - col_cluster_t *clusters = - malloc(cluster_count * sizeof(col_cluster_t)); - col_lab_t *cluster_colors = - malloc(cluster_count * sizeof(col_lab_t)); - clusters[0].first = colors; - clusters[0].len = color_count; + col_cluster_t *clusters; + col_lab_t *cluster_colors; + col_lab_t color; - for(int cc = 1; cc < cluster_count; cc++) { + float max_dim_size; + float max_l, min_l, max_a, min_a, max_b, min_b; + int largest_cluster, largest_dimension; + int cc, c, i; - float max_dim_size = 0; - int largest_cluster = 0; - int largest_dimension = 0; + clusters = malloc(cluster_count * sizeof(col_cluster_t)); + cluster_colors = malloc(cluster_count * sizeof(col_lab_t)); - for(int c = 0; c < cc; c++) { + clusters[0].first = colors; + clusters[0].len = color_count; - col_cluster_t cluster = clusters[c]; + for(cc = 1; cc < cluster_count; cc++) { - // Find spread in L, a, and b channels. + max_dim_size = 0; + largest_cluster = 0; + largest_dimension = 0; - float min_l = 1, max_l = 0, min_a = 1, max_a = - -1, min_b = 1, max_b = -1; + for(c = 0; c < cc; c++) { - for(int i = 0; i < cluster.len; i++) { - col_lab_t color = *(cluster.first + i); + min_l = 1; + max_l = -1; + min_a = 1; + max_a = -1; + min_b = 1; + max_b = -1; + + for(i = 0; i < clusters[c].len; i++) { + color = clusters[c].first[i]; min_l = min(min_l, color.l); max_l = max(max_l, color.l); @@ -214,8 +254,6 @@ cluster_image_colors(col_lab_t *colors, int color_count, int cluster_count) { max_b = max(max_b, color.b); } - // Compare with max_dim_size and set if applicable - if(max_l - min_l > max_dim_size) { max_dim_size = max_l - min_l; largest_cluster = c; @@ -235,7 +273,6 @@ cluster_image_colors(col_lab_t *colors, int color_count, int cluster_count) { } } - // Sort largest collection by largest dimension and split into two clusters // TODO OPTIMIZE: Replace with quickselect followed by partition switch (largest_dimension) { @@ -256,16 +293,13 @@ cluster_image_colors(col_lab_t *colors, int color_count, int cluster_count) { break; } - int total_len = clusters[largest_cluster].len; - - clusters[largest_cluster].len = total_len / 2; clusters[cc].first = clusters[largest_cluster].first + - clusters[largest_cluster].len; - clusters[cc].len = total_len - clusters[largest_cluster].len; + clusters[largest_cluster].len / 2; + clusters[cc].len = (clusters[largest_cluster].len + 1) / 2; + clusters[largest_cluster].len /= 2; } - // Find the median color of each cluster // TODO OPTIMIZE: Optimize this using quickselect for(int c = 0; c < cluster_count; c++) { @@ -310,26 +344,16 @@ col_lab_t match_color(col_lab_t *palette, float *palette_weights, size_t palette_size, col_lab_t target_color) { - float best_score = 0; + float score, best_score = 0; col_lab_t best_color; for(int c = 0; c < palette_size; c++) { - col_lab_t palette_color = palette[c]; - float color_distance = - sqrtf((palette_color.l - - target_color.l) * (palette_color.l - - target_color.l) + - (palette_color.a - - target_color.a) * (palette_color.a - - target_color.a) + - (palette_color.b - - target_color.b) * (palette_color.b - - target_color.b)); - float score = palette_weights[c] / color_distance; + score = palette_weights[c] / get_color_distance(palette[c], + target_color); if(score > best_score) { best_score = score; - best_color = palette_color; + best_color = palette[c]; } } @@ -338,11 +362,15 @@ match_color(col_lab_t *palette, float *palette_weights, size_t palette_size, void export_color(const char *name, col_lab_t color) { - col_rgb_t rgb = lab_to_rgb(color); - int red = min(1, max(0, rgb.r)) * 255; - int green = min(1, max(0, rgb.g)) * 255; - int blue = min(1, max(0, rgb.b)) * 255; + int red, green, blue; + col_rgb_t rgb; + + rgb = lab_to_rgb(color); + + red = min(1, max(0, rgb.r)) * 255; + green = min(1, max(0, rgb.g)) * 255; + blue = min(1, max(0, rgb.b)) * 255; printf("%s: #%02x%02x%02x\n", name, red, green, blue); } @@ -350,44 +378,47 @@ export_color(const char *name, col_lab_t color) { int main(int argc, char **argv) { - if(argc <= 1) { - printf("Need path to image.\n"); - return 1; - } - int pixel_count; - col_rgb_t *image_pixels = - get_image_pixel_data(argv[1], &pixel_count); + int i; + col_rgb_t *image_pixels_rgb; + col_lab_t *image_pixels_lab; + + col_lab_t *primary_colors; + + col_lab_t palette[PRIMARY_COLOR_COUNT * 4]; + float palette_weights[PRIMARY_COLOR_COUNT * 4]; - // TODO: White-balance colors first using GIMP algorithm + col_lab_t color; + + if(argc <= 1) + die("No image provided."); + + image_pixels_rgb = get_image_pixel_data(argv[1], &pixel_count); - col_lab_t *image_pixels_lab = - malloc(pixel_count * sizeof(col_lab_t)); + if(image_pixels_rgb == NULL) + die("Image failed to load."); - for(int i = 0; i < pixel_count; i++) { - image_pixels_lab[i] = rgb_to_lab(image_pixels[i]); + image_pixels_lab = malloc(pixel_count * sizeof(col_lab_t)); + + for(i = 0; i < pixel_count; i++) { + image_pixels_lab[i] = rgb_to_lab(image_pixels_rgb[i]); } - col_lab_t *primary_colors = + primary_colors = cluster_image_colors(image_pixels_lab, pixel_count, PRIMARY_COLOR_COUNT); - col_lab_t palette[PRIMARY_COLOR_COUNT * 4]; - float palette_weights[PRIMARY_COLOR_COUNT * 4]; - - for(int c = 0; c < PRIMARY_COLOR_COUNT; c++) { - palette[4 * c] = primary_colors[c]; - palette_weights[4 * c] = 1; - palette[4 * c + 1] = hue_shift(primary_colors[c], PI); - palette_weights[4 * c + 1] = 0.8; - palette[4 * c + 2] = hue_shift(primary_colors[c], PI / 6); - palette_weights[4 * c + 2] = 0.4; - palette[4 * c + 3] = hue_shift(primary_colors[c], -PI / 6); - palette_weights[4 * c + 3] = 0.4; + for(i = 0; i < PRIMARY_COLOR_COUNT; i++) { + palette[4 * i] = primary_colors[i]; + palette_weights[4 * i] = 1; + palette[4 * i + 1] = hue_shift(primary_colors[i], PI); + palette_weights[4 * i + 1] = 0.8; + palette[4 * i + 2] = hue_shift(primary_colors[i], PI / 6); + palette_weights[4 * i + 2] = 0.4; + palette[4 * i + 3] = hue_shift(primary_colors[i], -PI / 6); + palette_weights[4 * i + 3] = 0.4; } - col_lab_t color; - TARGET_COLOR("st.color0", 0, 0, 0, 0.25); TARGET_COLOR("st.color1", 1, 0, 0, 0.7); TARGET_COLOR("st.color2", 0, 1, 0, 0.7); @@ -407,7 +438,7 @@ main(int argc, char **argv) { TARGET_COLOR("st.surface", 0, 0, 0, 0.15); TARGET_COLOR("st.on_surface", 0, 0, 0, 0.95); - free(image_pixels); + free(image_pixels_rgb); free(image_pixels_lab); free(primary_colors); }