Description
Extracts the colors palette from an image.
Extracts the colors palette from an image.
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdarg.h> #include <getopt.h> #include <math.h> #include <imel.h> struct color_list { ImelPixel rgb; ImelSize value; struct color_list *next; }; struct opt_data { char *input; char *output; char *map_output; bool map; bool antialias; bool distance; bool verbose; int qlevel; int colors; int ssize; } opt = { NULL, "palette.png", "map.png", false, false, false, false, 32, 8, 64 }; bool color_exists (ImelPixel, struct color_list *); ImelPixel find_similar (ImelPixel, struct color_list *); ImelPixel *list_to_array (struct color_list *, ImelSize); static int sort_colors (const void *p1, const void *p2) { return ((*(ImelPixel *) p2).level > (*(ImelPixel *) p1).level) ? 1 : ((*(ImelPixel *) p2).level < (*(ImelPixel *) p1).level) ? -1 : 0; } int main (int argc, char *argv[]) { int c, option_index; ImelImage *image, *white, *no_alpha; ImelError error; ImelHSL value; ImelSize x, y, cnt_colors = 0; ImelPixel px_white = { 255, 255, 255, 0 }, *cl_array; struct color_list *cl = NULL, *p; struct option long_options[] = { { "map", no_argument, 0, 'm' }, { "image", required_argument, 0, 'i' }, { "aa", no_argument, 0, 'a' }, { "cmp-dist", no_argument, 0, 'd' }, { "output", required_argument, 0, 'o' }, { "qlevel", required_argument, 0, 'q' }, { "colors", required_argument, 0, 'c' }, { "size", required_argument, 0, 's' }, { "verbose", required_argument, 0, 'v' }, { "help", no_argument, 0, 'h' }, { 0, 0, 0, 0 } }; while ( 1 ) { c = getopt_long (argc, argv, "hvdmai:o:q:c:s:", long_options, &option_index); if ( c == -1 ) break; switch (c) { case 'h': printf ("Opzioni:\n" " -m, --map [file] Saves the colors map of the original image.\n" " Default: 'map.png'\n" " -i, --image [file] Image from which to extract the palette.\n" " -o, --output [file] Output path.\n" " Default: 'palette.png'\n" " -q, --qlevel [value] Color quantization level.\n" " Default: 32.\n" " -c, --colors [value] Palette colors number.\n" " Default: 8.\n" " -s, --size [value] Colors size into the final image.\n" " Default: 64.\n" " -a, --aa Enable antialias, better precision but increases the processing time.\n" " -d, --cmp-dist Consider chromatic distance from two colors for image analysis. Raccomanded.\n" " -v, --verbose Enable verbose messages.\n" " -h, --help Show this message.\n\n" "Example:\n" " %s -d -c10 -i my_img.png\n" " %s -vd -q96 -m -i photo.jpeg\n", argv[0], argv[0]); break; case 'i': opt.input = strdup (optarg); break; case 'o': opt.output = strdup (optarg); break; case 'c': opt.colors = atoi (optarg); break; case 'v': opt.verbose = true; break; case 'd': opt.distance = true; break; case 'a': opt.antialias = true; break; case 'm': opt.map = true; if ( optarg ) opt.map_output = strdup (optarg); break; case 's': opt.ssize = atoi (optarg); if ( opt.ssize < 1 ) { fprintf (stderr, "--size: wrong values. Used 64.\n"); opt.ssize = 64; } break; case 'q': opt.qlevel = atoi (optarg); if ( opt.qlevel < 1 ) { fprintf (stderr, "--qlevel: wrong values. Used 32.\n"); opt.qlevel = 32; } break; } } if ( opt.input == NULL ) { fprintf (stderr, "No input image found.\n"); return 1; } image = imel_image_new_from (opt.input, 0, &error); if ( !image ) { fprintf (stderr, "Error %d: %s\n", error.code, error.description); return 1; } white = imel_image_new_with_background_color (image->width, image->height, px_white); no_alpha = imel_image_union (white, image, 255, IMEL_ALIGNMENT_TL); imel_image_free (image); imel_image_free (white); image = imel_image_copy (no_alpha); if ( !image ) return 1; if ( opt.antialias ) imel_image_apply_effect (image, IMEL_EFFECT_ANTIALIAS, 2 * opt.qlevel); imel_image_apply_effect (image, IMEL_EFFECT_RASTERIZE, (ImelSize) opt.qlevel); for ( y = 0; y < image->height; y++ ) { for ( x = 0; x < image->width; x++ ) { if ( color_exists (image->pixel[y][x], cl) ) continue; p = (struct color_list *) malloc (sizeof (struct color_list)); imel_pixel_set_from_pixel (&(p->rgb), image->pixel[y][x]), p->value = 0; p->next = cl; cl = p; cnt_colors++; } } if ( opt.verbose ) printf ("Colors: %d\n", cnt_colors); if ( cnt_colors < opt.colors ) { printf ("Reduce palette colors to %d\n", cnt_colors); opt.colors = cnt_colors; } for ( y = 0; y < no_alpha->height; y++ ) { for ( x = 0; x < no_alpha->width; x++ ) { no_alpha->pixel[y][x] = find_similar (no_alpha->pixel[y][x], cl); } } if ( opt.map ) { if ( opt.verbose ) { printf ("Saving map '%s'.. ", opt.map_output); fflush (stdout); } white = imel_image_new (no_alpha->width, no_alpha->height * 2); imel_image_insert_image (white, no_alpha, 0, 0); imel_image_insert_image (white, image, 0, no_alpha->height); if ( !imel_image_save_png (white, opt.map_output, IMEL_PNG_DEFAULT, &error) ) { fprintf (stderr, "Error %d: %s\n", error.code, error.description); imel_image_free (image); imel_image_free (no_alpha); imel_image_free (white); return 1; } imel_image_free (white); if ( opt.verbose ) { printf ("OK\n"); fflush (stdout); } } imel_image_free (image); cl_array = list_to_array (cl, cnt_colors); qsort (cl_array, cnt_colors, sizeof (ImelPixel *), sort_colors); image = imel_image_new (opt.ssize * opt.colors, opt.ssize); printf ("{\n"); for ( x = 0; x < opt.colors; x++ ) { imel_draw_rect (image, (opt.ssize * x), 0, (opt.ssize - 1) + (opt.ssize * x), opt.ssize - 1, cl_array[x], true); printf ("\t[ \"#%02x%02x%02x\", \"%.2lf%%\" ]%s", cl_array[x].red, cl_array[x].green, cl_array[x].blue, 100.f * ((double) cl_array[x].level) / (((double) no_alpha->width) * ((double) no_alpha->height)), ( x != opt.colors - 1 ) ? ",\n" : "\n"); } printf ("}\n"); imel_image_free (no_alpha); free (cl_array); for ( p = NULL, x = 0; cl; cl = cl->next, x++ ) { if ( p ) free (p); p = cl; } if ( p ) free (p); if ( opt.verbose ) { printf ("Saving palette '%s'.. ", opt.output); fflush (stdout); } if ( !imel_image_save_png (image, opt.output, IMEL_PNG_DEFAULT, &error) ) { fprintf (stderr, "Error %d: %s\n", error.code, error.description); imel_image_free (image); return 1; } imel_image_free (image); if ( opt.verbose ) { printf ("OK\n"); fflush (stdout); } return 0; } ImelPixel *list_to_array (struct color_list *list, ImelSize n_elem) { ImelPixel *array; ImelSize j; struct color_list *p; array = (ImelPixel *) malloc (n_elem * sizeof (ImelPixel)); for ( p = list, j = 0; j < n_elem && p; p = p->next, j++ ) { imel_pixel_set_from_pixel (&(array[j]), p->rgb); array[j].level = p->value; } return array; } bool color_exists (ImelPixel p, struct color_list *cl) { struct color_list *p_cl; if ( !cl ) return false; for ( p_cl = cl; p_cl; p_cl = p_cl->next ) { if ( imel_pixel_compare (p, p_cl->rgb, 0) && !opt.distance ) return true; if ( imel_pixel_get_distance (p, p_cl->rgb) < (double) opt.qlevel && opt.distance ) return true; } return false; } ImelPixel find_similar (ImelPixel p, struct color_list *cl) { struct color_list *p_cl, *ref_c; double c_dist, ref_d = -1; for ( p_cl = cl; p_cl; p_cl = p_cl->next ) { c_dist = imel_pixel_get_distance (p_cl->rgb, p); if ( ref_d == -1 || ref_d > c_dist ) { ref_d = c_dist; ref_c = p_cl; } if ( !c_dist ) break; } ref_c->value++; return ref_c->rgb; }