morph.c (13425B)
1 2 /* See LICENSE file for copyright and license details. */ 3 4 #include <errno.h> 5 #include <math.h> 6 #include <stdarg.h> 7 #include <stdint.h> 8 #include <stdio.h> 9 #include <stdlib.h> 10 #include <string.h> 11 #include <unistd.h> 12 13 #include <Imlib2.h> 14 15 #define PRIMARY_COLOR_COUNT 32 16 #define PI 3.1415926535 17 18 #define WORKING_IMG_WIDTH 500 19 #define WORKING_IMG_HEIGHT 500 20 21 #define min(a, b) ((a) < (b) ? (a) : (b)) 22 #define max(a, b) ((a) > (b) ? (a) : (b)) 23 24 #define TARGET_COLOR(name, cr, cg, cb, cl) \ 25 color = match_color(palette, palette_weights, PRIMARY_COLOR_COUNT*4,\ 26 rgb_to_lab((col_rgb_t){ .r = cr, .g = cg, .b = cb}));\ 27 color.l = cl;\ 28 export_color(name, color); 29 30 /// Type to represent a color in the srgb color space. Use the functions 31 /// rgb_to_lab and lab_to_rgb to convert between different color spaces. 32 typedef struct { 33 float r; 34 float g; 35 float b; 36 } col_rgb_t; 37 38 /// Type to represent a color in the OkLab color space. Use the functions 39 /// rgb_to_lab and lab_to_rgb to convert between different color spaces. 40 typedef struct { 41 float l; 42 float a; 43 float b; 44 } col_lab_t; 45 46 typedef struct { 47 col_lab_t *first; 48 size_t len; 49 } col_cluster_t; 50 51 /// Exit the program with an error message. 52 void 53 die(const char *fmt, ...) { 54 va_list ap; 55 int saved_errno; 56 57 saved_errno = errno; 58 59 va_start(ap, fmt); 60 vfprintf(stderr, fmt, ap); 61 va_end(ap); 62 63 if(fmt[0] && fmt[strlen(fmt) - 1] == ':') 64 fprintf(stderr, " %s", strerror(saved_errno)); 65 fputc('\n', stderr); 66 exit(1); 67 } 68 69 void 70 usage() { 71 printf("morph [-hV] <image>\n"); 72 exit(0); 73 } 74 75 /// Compare the luminances of the two given colors. Returns 1 if the first has 76 /// a larger luminance, returns -1 otherwise. 77 int 78 compare_l(const void *a, const void *b) { 79 const col_lab_t *ca = (const col_lab_t *)a; 80 const col_lab_t *cb = (const col_lab_t *)b; 81 82 if(ca->l > cb->l) { 83 return 1; 84 } else { 85 return -1; 86 } 87 } 88 89 /// Compare the 'a' of the two given colors. Returns 1 if the first has a 90 /// larger 'a', returns -1 otherwise. 91 int 92 compare_a(const void *a, const void *b) { 93 const col_lab_t *ca = (const col_lab_t *)a; 94 const col_lab_t *cb = (const col_lab_t *)b; 95 96 if(ca->a > cb->a) { 97 return 1; 98 } else { 99 return -1; 100 } 101 } 102 103 /// Compare the 'b' of the two given colors. Returns 1 if the first has a 104 /// larger 'b', returns -1 otherwise. 105 int 106 compare_b(const void *a, const void *b) { 107 const col_lab_t *ca = (const col_lab_t *)a; 108 const col_lab_t *cb = (const col_lab_t *)b; 109 110 if(ca->b > cb->b) { 111 return 1; 112 } else { 113 return -1; 114 } 115 } 116 117 /// Returns the distance between the two given colors in the OkLab color space. 118 float 119 get_color_distance(const col_lab_t c1, const col_lab_t c2) { 120 return sqrtf((c1.l - c2.l) * (c1.l - c2.l) + 121 (c1.a - c2.a) * (c1.a - c2.a) + (c1.b - c2.b) * (c1.b - 122 c2.b)); 123 } 124 125 /// Convert a color in the srgb color space, used for color IO, to the OkLab 126 /// perceptual color space. 127 col_lab_t 128 rgb_to_lab(col_rgb_t rgb) { 129 130 col_lab_t lab; 131 float lin_r, lin_g, lin_b; 132 float l, m, s; 133 134 lin_r = rgb.r >= 0.04045 ? powf((rgb.r + 0.055) / 1.055, 135 2.4) : rgb.r / 12.92; 136 lin_g = rgb.g >= 0.04045 ? powf((rgb.g + 0.055) / 1.055, 137 2.4) : rgb.g / 12.92; 138 lin_b = rgb.b >= 0.04045 ? powf((rgb.b + 0.055) / 1.055, 139 2.4) : rgb.b / 12.92; 140 141 l = cbrtf(0.4122214708 * lin_r + 0.5363325363 * lin_g + 142 0.0514459929 * lin_b); 143 m = cbrtf(0.2119034982 * lin_r + 0.6806995451 * lin_g + 144 0.1073969566 * lin_b); 145 s = cbrtf(0.0883024619 * lin_r + 0.2817188376 * lin_g + 146 0.6299787005 * lin_b); 147 148 lab.l = 0.2104542553 * l + 0.7936177850 * m - 0.0040720468 * s; 149 lab.a = 1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s; 150 lab.b = 0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s; 151 152 return lab; 153 } 154 155 /// Convert a color in the OkLab perceptual color space to the srgb color space 156 /// for IO. 157 col_rgb_t 158 lab_to_rgb(col_lab_t lab) { 159 160 col_rgb_t rgb; 161 float l, m, s; 162 float lin_r, lin_g, lin_b; 163 164 l = lab.l + 0.3963377774 * lab.a + 0.2158037573 * lab.b; 165 m = lab.l - 0.1055613458 * lab.a - 0.0638541728 * lab.b; 166 s = lab.l - 0.0894841775 * lab.a - 1.2914855480 * lab.b; 167 168 l = l * l * l; 169 m = m * m * m; 170 s = s * s * s; 171 172 lin_r = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s; 173 lin_g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s; 174 lin_b = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s; 175 176 rgb.r = lin_r >= 0.0031308 ? 1.055 * powf(lin_r, 177 1 / 2.4) - 178 0.055 : 12.92 * lin_r; 179 rgb.g = lin_g >= 0.0031308 ? 1.055 * powf(lin_g, 180 1 / 2.4) - 181 0.055 : 12.92 * lin_g; 182 rgb.b = lin_b >= 0.0031308 ? 1.055 * powf(lin_b, 183 1 / 2.4) - 184 0.055 : 12.92 * lin_b; 185 186 return rgb; 187 } 188 189 /// Loads the image located at the given path and extracts the colors in the 190 /// pixels into an array of col_rgb_t. The caller is responsible for freeing 191 /// the return value. Returns NULL if the image failed to load. 192 col_rgb_t * 193 get_image_pixel_data(const char *path, int *pixel_count) { 194 195 int source_width, source_height; 196 col_rgb_t *image_pixels; 197 DATA32 *image_data, c; 198 Imlib_Image source, image; 199 200 source = imlib_load_image(path); 201 202 if(source == NULL) 203 return NULL; 204 205 imlib_context_set_image(source); 206 207 source_width = imlib_image_get_width(); 208 source_height = imlib_image_get_height(); 209 210 image = imlib_create_image(WORKING_IMG_WIDTH, WORKING_IMG_HEIGHT); 211 imlib_context_set_image(image); 212 213 imlib_blend_image_onto_image(source, 0, 0, 0, source_width, 214 source_height, 0, 0, WORKING_IMG_WIDTH, 215 WORKING_IMG_HEIGHT); 216 217 *pixel_count = WORKING_IMG_WIDTH * WORKING_IMG_HEIGHT; 218 219 image_pixels = malloc(*pixel_count * sizeof(col_rgb_t)); 220 221 image_data = imlib_image_get_data_for_reading_only(); 222 223 for(int pixel = 0; pixel < *pixel_count; pixel++) { 224 225 c = *(image_data + pixel); 226 227 image_pixels[pixel].r = ((c & 0x00ff0000) >> 16) / 255.0f; 228 image_pixels[pixel].g = ((c & 0x0000ff00) >> 8) / 255.0f; 229 image_pixels[pixel].b = (c & 0x000000ff) / 255.0f; 230 } 231 232 imlib_free_image(); 233 234 imlib_context_set_image(source); 235 imlib_free_image(); 236 237 return image_pixels; 238 } 239 240 /// Extract the dominant colors from among a list of colors in lab colorspace 241 col_lab_t * 242 cluster_image_colors(col_lab_t *colors, int color_count, int cluster_count) { 243 244 col_cluster_t *clusters; 245 col_lab_t *cluster_colors; 246 col_lab_t color; 247 248 float max_dim_size; 249 float max_l, min_l, max_a, min_a, max_b, min_b; 250 int largest_cluster, largest_dimension; 251 int cc, c, i; 252 253 clusters = malloc(cluster_count * sizeof(col_cluster_t)); 254 cluster_colors = malloc(cluster_count * sizeof(col_lab_t)); 255 256 clusters[0].first = colors; 257 clusters[0].len = color_count; 258 259 for(cc = 1; cc < cluster_count; cc++) { 260 261 max_dim_size = 0; 262 largest_cluster = 0; 263 largest_dimension = 0; 264 265 for(c = 0; c < cc; c++) { 266 267 min_l = 1; 268 max_l = -1; 269 min_a = 1; 270 max_a = -1; 271 min_b = 1; 272 max_b = -1; 273 274 for(i = 0; i < clusters[c].len; i++) { 275 color = clusters[c].first[i]; 276 277 min_l = min(min_l, color.l); 278 max_l = max(max_l, color.l); 279 280 min_a = min(min_a, color.a); 281 max_a = max(max_a, color.a); 282 283 min_b = min(min_b, color.b); 284 max_b = max(max_b, color.b); 285 } 286 287 if(max_l - min_l > max_dim_size) { 288 max_dim_size = max_l - min_l; 289 largest_cluster = c; 290 largest_dimension = 0; 291 } 292 293 if(max_a - min_a > max_dim_size) { 294 max_dim_size = max_a - min_a; 295 largest_cluster = c; 296 largest_dimension = 1; 297 } 298 299 if(max_b - min_b > max_dim_size) { 300 max_dim_size = max_b - min_b; 301 largest_cluster = c; 302 largest_dimension = 2; 303 } 304 } 305 306 // TODO OPTIMIZE v0.2: Replace with quickselect followed by partition 307 308 switch (largest_dimension) { 309 case 0: 310 qsort(clusters[largest_cluster].first, 311 clusters[largest_cluster].len, sizeof(col_lab_t), 312 compare_l); 313 break; 314 case 1: 315 qsort(clusters[largest_cluster].first, 316 clusters[largest_cluster].len, sizeof(col_lab_t), 317 compare_a); 318 break; 319 case 2: 320 qsort(clusters[largest_cluster].first, 321 clusters[largest_cluster].len, sizeof(col_lab_t), 322 compare_b); 323 break; 324 } 325 326 clusters[cc].first = 327 clusters[largest_cluster].first + 328 clusters[largest_cluster].len / 2; 329 clusters[cc].len = (clusters[largest_cluster].len + 1) / 2; 330 clusters[largest_cluster].len /= 2; 331 } 332 333 // TODO OPTIMIZE v0.2: Optimize this using quickselect 334 335 for(int c = 0; c < cluster_count; c++) { 336 337 qsort(clusters[c].first, clusters[c].len, sizeof(col_lab_t), 338 compare_l); 339 cluster_colors[c].l = 340 (clusters[c].first + clusters[c].len / 2)->l; 341 342 qsort(clusters[c].first, clusters[c].len, sizeof(col_lab_t), 343 compare_a); 344 cluster_colors[c].a = 345 (clusters[c].first + clusters[c].len / 2)->a; 346 347 qsort(clusters[c].first, clusters[c].len, sizeof(col_lab_t), 348 compare_b); 349 cluster_colors[c].b = 350 (clusters[c].first + clusters[c].len / 2)->b; 351 } 352 353 free(clusters); 354 355 return cluster_colors; 356 } 357 358 /// Rotates the hue of the given Lab color by the given angle in radians. 359 col_lab_t 360 hue_shift(col_lab_t in, float angle) { 361 362 col_lab_t out; 363 364 float cos = cosf(angle); 365 float sin = sinf(angle); 366 367 out.l = in.l; 368 out.a = cos * in.a - sin * in.b; 369 out.b = sin * in.a + cos * in.b; 370 371 return out; 372 } 373 374 /// Given a palette and a target color, finds the color in the palette closest 375 /// to the target color in Lab color space. The palette weights can be used to 376 /// biase the result towards specific colors; colors with larger weights are 377 /// more likely to be picked. 378 col_lab_t 379 match_color(col_lab_t *palette, float *palette_weights, size_t palette_size, 380 col_lab_t target_color) { 381 382 float score, best_score = 0; 383 col_lab_t best_color; 384 385 for(int c = 0; c < palette_size; c++) { 386 score = palette_weights[c] / get_color_distance(palette[c], 387 target_color); 388 389 if(score > best_score) { 390 best_score = score; 391 best_color = palette[c]; 392 } 393 } 394 395 return best_color; 396 } 397 398 /// Prints the given color in the X resources format, e.g. "name: #aabbcc" 399 void 400 export_color(const char *name, col_lab_t color) { 401 402 int red, green, blue; 403 col_rgb_t rgb; 404 405 rgb = lab_to_rgb(color); 406 407 red = min(1, max(0, rgb.r)) * 255; 408 green = min(1, max(0, rgb.g)) * 255; 409 blue = min(1, max(0, rgb.b)) * 255; 410 411 printf("%s: #%02x%02x%02x\n", name, red, green, blue); 412 } 413 414 void 415 parse_argv(int argc, char **argv, char **filepath) { 416 417 int c; 418 419 opterr = 0; 420 421 while((c = getopt(argc, argv, "hV")) != -1) { 422 switch (c) { 423 case 'V': 424 printf("morph-" VERSION "\n"); 425 exit(0); 426 default: 427 usage(); 428 } 429 } 430 431 if(optind >= argc) 432 usage(); 433 434 *filepath = argv[optind]; 435 } 436 437 int 438 main(int argc, char **argv) { 439 440 char *filepath; 441 int pixel_count; 442 int i; 443 col_rgb_t *image_pixels_rgb; 444 col_lab_t *image_pixels_lab; 445 446 col_lab_t *primary_colors; 447 448 col_lab_t palette[PRIMARY_COLOR_COUNT * 4]; 449 float palette_weights[PRIMARY_COLOR_COUNT * 4]; 450 451 col_lab_t color; 452 453 parse_argv(argc, argv, &filepath); 454 455 image_pixels_rgb = get_image_pixel_data(filepath, &pixel_count); 456 457 if(image_pixels_rgb == NULL) 458 die("Image failed to load."); 459 460 image_pixels_lab = malloc(pixel_count * sizeof(col_lab_t)); 461 462 for(i = 0; i < pixel_count; i++) { 463 image_pixels_lab[i] = rgb_to_lab(image_pixels_rgb[i]); 464 } 465 466 primary_colors = 467 cluster_image_colors(image_pixels_lab, pixel_count, 468 PRIMARY_COLOR_COUNT); 469 470 for(i = 0; i < PRIMARY_COLOR_COUNT; i++) { 471 palette[4 * i] = primary_colors[i]; 472 palette_weights[4 * i] = 1; 473 palette[4 * i + 1] = hue_shift(primary_colors[i], PI); 474 palette_weights[4 * i + 1] = 0.8; 475 palette[4 * i + 2] = hue_shift(primary_colors[i], PI / 6); 476 palette_weights[4 * i + 2] = 0.4; 477 palette[4 * i + 3] = hue_shift(primary_colors[i], -PI / 6); 478 palette_weights[4 * i + 3] = 0.4; 479 } 480 481 TARGET_COLOR("*.color0", 0, 0, 0, 0.25); 482 TARGET_COLOR("*.color1", 1, 0, 0, 0.7); 483 TARGET_COLOR("*.color2", 0, 1, 0, 0.7); 484 TARGET_COLOR("*.color3", 1, 1, 0, 0.7); 485 TARGET_COLOR("*.color4", 0, 0, 1, 0.7); 486 TARGET_COLOR("*.color5", 1, 0, 1, 0.7); 487 TARGET_COLOR("*.color6", 0, 1, 1, 0.7); 488 TARGET_COLOR("*.color7", 0, 0, 0, 0.9); 489 TARGET_COLOR("*.color8", 0, 0, 0, 0.4); 490 TARGET_COLOR("*.color9", 1, 0, 0, 0.8); 491 TARGET_COLOR("*.color10", 0, 1, 0, 0.8); 492 TARGET_COLOR("*.color11", 1, 1, 0, 0.8); 493 TARGET_COLOR("*.color12", 0, 0, 1, 0.8); 494 TARGET_COLOR("*.color13", 1, 0, 1, 0.8); 495 TARGET_COLOR("*.color14", 0, 1, 1, 0.8); 496 TARGET_COLOR("*.color15", 0, 0, 0, 0.9); 497 498 TARGET_COLOR("*.base", 0, 0, 0, 0.15); 499 500 TARGET_COLOR("*.surface0", 0, 0, 0, 0.2); 501 TARGET_COLOR("*.surface1", 0, 0, 0, 0.3); 502 503 TARGET_COLOR("*.text2", 0, 0, 0, 0.7); 504 TARGET_COLOR("*.text1", 0, 0, 0, 0.8); 505 TARGET_COLOR("*.text0", 0, 0, 0, 0.95); 506 507 TARGET_COLOR("*.highlight", 0.7, 0.7, 0.7, 0.9); 508 509 printf("*.wallpaper: %s\n", filepath); 510 511 free(image_pixels_rgb); 512 free(image_pixels_lab); 513 free(primary_colors); 514 }