Imel Palette

Imel

Description

Extracts the colors palette from an image.


Source

#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;
}