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:
M | morph.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);
}