From adam@uunet.pipex.com Wed Apr 23 08:33:19 1997 Date: Wed, 23 Apr 1997 09:15:57 GMT From: "Adam D. Moss" To: gimp-developer@scam.xcf.berkeley.edu Subject: [gimp-devel][patch] convert.c This patch to app/convert.c does the following groovy things: 1) Allows the image to not only be remapped/dithered to an optimal colourmap, but also to a web-optimal (n*tsc*pe) palette or a 1-bit mono colourmap. This'll be extended further in the future, S&P permitting. 2) Fixes a bug in the histogram code which meant that only the bottom layer in an RGB image could make any contribution to the generated indexed image palette (disasterous if the bottom layer was a fresh transparent layer, for instance). 3) Fixes the range of values which the gimp_convert_indexed procedure can take. NOTE: The advanced features of this function can't be accessed through procedure calls yet. *** ../../gimp-0.99.8/app/convert.c Thu Apr 10 01:52:55 1997 --- convert.c Wed Apr 23 08:07:04 1997 *************** *** 15,20 **** --- 15,21 ---- * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + #include #include #include *************** *** 35,40 **** --- 36,48 ---- #define NODITHER 0 #define FSDITHER 1 + /* adam's extra palette stuff */ + + #define MAKE_PALETTE 0 + #define REUSE_PALETTE 1 + #define WEB_PALETTE 2 + #define MONO_PALETTE 3 + #define PRECISION_R 6 #define PRECISION_G 6 #define PRECISION_B 5 *************** *** 56,61 **** --- 64,109 ---- #define G_SCALE 3 /* scale G distances by this much */ #define B_SCALE 1 /* and B by this much */ + + unsigned char webpal[] = + { + 255,255,255,255,255,204,255,255,153,255,255,102,255,255,51,255,255,0,255, + 204,255,255,204,204,255,204,153,255,204,102,255,204,51,255,204,0,255,153, + 255,255,153,204,255,153,153,255,153,102,255,153,51,255,153,0,255,102,255, + 255,102,204,255,102,153,255,102,102,255,102,51,255,102,0,255,51,255,255, + 51,204,255,51,153,255,51,102,255,51,51,255,51,0,255,0,255,255,0, + 204,255,0,153,255,0,102,255,0,51,255,0,0,204,255,255,204,255,204, + 204,255,153,204,255,102,204,255,51,204,255,0,204,204,255,204,204,204,204, + 204,153,204,204,102,204,204,51,204,204,0,204,153,255,204,153,204,204,153, + 153,204,153,102,204,153,51,204,153,0,204,102,255,204,102,204,204,102,153, + 204,102,102,204,102,51,204,102,0,204,51,255,204,51,204,204,51,153,204, + 51,102,204,51,51,204,51,0,204,0,255,204,0,204,204,0,153,204,0, + 102,204,0,51,204,0,0,153,255,255,153,255,204,153,255,153,153,255,102, + 153,255,51,153,255,0,153,204,255,153,204,204,153,204,153,153,204,102,153, + 204,51,153,204,0,153,153,255,153,153,204,153,153,153,153,153,102,153,153, + 51,153,153,0,153,102,255,153,102,204,153,102,153,153,102,102,153,102,51, + 153,102,0,153,51,255,153,51,204,153,51,153,153,51,102,153,51,51,153, + 51,0,153,0,255,153,0,204,153,0,153,153,0,102,153,0,51,153,0, + 0,102,255,255,102,255,204,102,255,153,102,255,102,102,255,51,102,255,0, + 102,204,255,102,204,204,102,204,153,102,204,102,102,204,51,102,204,0,102, + 153,255,102,153,204,102,153,153,102,153,102,102,153,51,102,153,0,102,102, + 255,102,102,204,102,102,153,102,102,102,102,102,51,102,102,0,102,51,255, + 102,51,204,102,51,153,102,51,102,102,51,51,102,51,0,102,0,255,102, + 0,204,102,0,153,102,0,102,102,0,51,102,0,0,51,255,255,51,255, + 204,51,255,153,51,255,102,51,255,51,51,255,0,51,204,255,51,204,204, + 51,204,153,51,204,102,51,204,51,51,204,0,51,153,255,51,153,204,51, + 153,153,51,153,102,51,153,51,51,153,0,51,102,255,51,102,204,51,102, + 153,51,102,102,51,102,51,51,102,0,51,51,255,51,51,204,51,51,153, + 51,51,102,51,51,51,51,51,0,51,0,255,51,0,204,51,0,153,51, + 0,102,51,0,51,51,0,0,0,255,255,0,255,204,0,255,153,0,255, + 102,0,255,51,0,255,0,0,204,255,0,204,204,0,204,153,0,204,102, + 0,204,51,0,204,0,0,153,255,0,153,204,0,153,153,0,153,102,0, + 153,51,0,153,0,0,102,255,0,102,204,0,102,153,0,102,102,0,102, + 51,0,102,0,0,51,255,0,51,204,0,51,153,0,51,102,0,51,51, + 0,51,0,0,0,255,0,0,204,0,0,153,0,0,102,0,0,51,0,0,0 + }; + + typedef struct _Color Color; typedef struct _QuantizeObj QuantizeObj; typedef void (* Pass1_Func) (QuantizeObj *); *************** *** 110,115 **** --- 158,169 ---- int dither; int num_cols; + int palette; + + int makepal_flag; + int webpal_flag; + int monopal_flag; + int reusepal_flag; } IndexedDialog; static Argument * convert_rgb_invoker (Argument *); *************** *** 119,134 **** static void indexed_ok_callback (GtkWidget *, gpointer); static void indexed_cancel_callback (GtkWidget *, gpointer); static void indexed_num_cols_update (GtkWidget *, gpointer); static void indexed_dither_update (GtkWidget *, gpointer); ! static void convert_image (GImage *, int, int, int); static void rgb_converter (Layer *, TileManager *, int); static void grayscale_converter (Layer *, TileManager *, int); static void generate_histogram_gray (Histogram, Layer *); static void generate_histogram_rgb (Histogram, Layer *); ! static QuantizeObj* initialize_median_cut (int, int, int); void convert_to_rgb (void *gimage_ptr) --- 173,191 ---- static void indexed_ok_callback (GtkWidget *, gpointer); static void indexed_cancel_callback (GtkWidget *, gpointer); static void indexed_num_cols_update (GtkWidget *, gpointer); + static void indexed_radio_update (GtkWidget *, gpointer); static void indexed_dither_update (GtkWidget *, gpointer); ! static void convert_image (GImage *, int, int, int, int); static void rgb_converter (Layer *, TileManager *, int); static void grayscale_converter (Layer *, TileManager *, int); + static void zero_histogram_gray (Histogram); + static void zero_histogram_rgb (Histogram); static void generate_histogram_gray (Histogram, Layer *); static void generate_histogram_rgb (Histogram, Layer *); ! static QuantizeObj* initialize_median_cut (int, int, int, int); void convert_to_rgb (void *gimage_ptr) *************** *** 136,142 **** GImage *gimage; gimage = (GImage *) gimage_ptr; ! convert_image (gimage, RGB, 0, 0); gdisplays_flush (); } --- 193,199 ---- GImage *gimage; gimage = (GImage *) gimage_ptr; ! convert_image (gimage, RGB, 0, 0, 0); gdisplays_flush (); } *************** *** 146,152 **** GImage *gimage; gimage = (GImage *) gimage_ptr; ! convert_image (gimage, GRAY, 0, 0); gdisplays_flush (); } --- 203,209 ---- GImage *gimage; gimage = (GImage *) gimage_ptr; ! convert_image (gimage, GRAY, 0, 0, 0); gdisplays_flush (); } *************** *** 166,172 **** --- 223,231 ---- GtkWidget *hbox; GtkWidget *label; GtkWidget *text; + GtkWidget *frame; GtkWidget *toggle; + GSList *group = NULL; gimage = (GImage *) gimage_ptr; dialog = (IndexedDialog *) g_malloc (sizeof (IndexedDialog)); *************** *** 174,191 **** dialog->num_cols = 256; dialog->dither = TRUE; dialog->shell = gtk_dialog_new (); gtk_window_set_title (GTK_WINDOW (dialog->shell), "Indexed Color Conversion"); vbox = gtk_vbox_new (FALSE, 5); ! gtk_container_border_width (GTK_CONTAINER (vbox), 10); ! gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog->shell)->vbox), vbox, TRUE, TRUE, 0); ! /* # of colors */ ! hbox = gtk_hbox_new (FALSE, 5); gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); ! label = gtk_label_new ("Quantize with # of colors: "); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, FALSE, 0); gtk_widget_show (label); --- 233,285 ---- dialog->num_cols = 256; dialog->dither = TRUE; + dialog->makepal_flag = TRUE; + dialog->webpal_flag = FALSE; + dialog->monopal_flag = FALSE; + dialog->reusepal_flag = FALSE; + + dialog->shell = gtk_dialog_new (); gtk_window_set_title (GTK_WINDOW (dialog->shell), "Indexed Color Conversion"); + + + + frame = gtk_frame_new ("Palette Options"); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN); + gtk_container_border_width (GTK_CONTAINER (frame), 2); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog->shell)->vbox), frame, TRUE, TRUE, 0); + gtk_widget_show(frame); + + vbox = gtk_vbox_new (FALSE, 5); ! gtk_container_border_width (GTK_CONTAINER (vbox), 2); ! gtk_container_border_width (GTK_CONTAINER (GTK_BOX (GTK_DIALOG (dialog->shell)->vbox)), 4); ! ! /* put the vbox in the frame */ ! gtk_container_add (GTK_CONTAINER (frame), vbox); ! gtk_widget_show(vbox); ! ! ! ! /* 'general palette' */ ! hbox = gtk_hbox_new (FALSE, 0); ! gtk_container_border_width (GTK_CONTAINER (vbox), 2); gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); ! ! toggle = gtk_radio_button_new_with_label (group, "Generate optimal palette: "); ! group = gtk_radio_button_group (GTK_RADIO_BUTTON (toggle)); ! gtk_box_pack_start (GTK_BOX (hbox), toggle, TRUE, TRUE, 0); ! gtk_signal_connect (GTK_OBJECT (toggle), "toggled", ! (GtkSignalFunc) indexed_radio_update, ! &(dialog->makepal_flag)); ! gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (toggle), dialog->makepal_flag); ! gtk_widget_show (toggle); ! ! ! label = gtk_label_new ("# of colors: "); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, FALSE, 0); gtk_widget_show (label); *************** *** 200,209 **** gtk_widget_show (text); gtk_widget_show (hbox); /* The dither toggle */ ! hbox = gtk_hbox_new (FALSE, 5); gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); toggle = gtk_check_button_new_with_label ("Enable Floyd-Steinberg dithering"); gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (toggle), dialog->dither); gtk_box_pack_start (GTK_BOX (hbox), toggle, TRUE, FALSE, 0); --- 294,368 ---- gtk_widget_show (text); gtk_widget_show (hbox); + + + + /* 'web palette' */ + hbox = gtk_hbox_new (FALSE, 2); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + + + toggle = + gtk_radio_button_new_with_label (group, "Use WWW-optimised palette"); + group = gtk_radio_button_group (GTK_RADIO_BUTTON (toggle)); + gtk_box_pack_start (GTK_BOX (hbox), toggle, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (toggle), "toggled", + (GtkSignalFunc) indexed_radio_update, + &(dialog->webpal_flag)); + gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (toggle), dialog->webpal_flag); + gtk_widget_show (toggle); + + gtk_widget_show (hbox); + + + + + + + + /* 'mono palette' */ + hbox = gtk_hbox_new (FALSE, 2); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + + + toggle = + gtk_radio_button_new_with_label (group, "Use black/white (1-bit) palette"); + group = gtk_radio_button_group (GTK_RADIO_BUTTON (toggle)); + gtk_box_pack_start (GTK_BOX (hbox), toggle, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (toggle), "toggled", + (GtkSignalFunc) indexed_radio_update, + &(dialog->monopal_flag)); + gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (toggle), dialog->monopal_flag); + gtk_widget_show (toggle); + + gtk_widget_show (hbox); + + + + + + + + frame = gtk_frame_new ("Dither Options"); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN); + gtk_container_border_width (GTK_CONTAINER (frame), 2); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog->shell)->vbox), frame, TRUE, TRUE, 0); + gtk_widget_show(frame); + + vbox = gtk_vbox_new (FALSE, 5); + gtk_container_border_width (GTK_CONTAINER (vbox), 10); + + /* put the vbox in the frame */ + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show(vbox); + + /* The dither toggle */ ! hbox = gtk_hbox_new (FALSE, 2); gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + + toggle = gtk_check_button_new_with_label ("Enable Floyd-Steinberg dithering"); gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (toggle), dialog->dither); gtk_box_pack_start (GTK_BOX (hbox), toggle, TRUE, FALSE, 0); *************** *** 228,238 **** gpointer client_data) { IndexedDialog *dialog; dialog = (IndexedDialog *) client_data; /* Convert the image to indexed color */ ! convert_image ((GImage *) dialog->gimage_ptr, INDEXED, dialog->num_cols, dialog->dither); gdisplays_flush (); gtk_widget_destroy (dialog->shell); --- 387,407 ---- gpointer client_data) { IndexedDialog *dialog; + int palette_type; dialog = (IndexedDialog *) client_data; + if (dialog->webpal_flag) palette_type = WEB_PALETTE; + else + if (dialog->monopal_flag) palette_type = MONO_PALETTE; + else + if (dialog->makepal_flag) palette_type = MAKE_PALETTE; + else + palette_type = REUSE_PALETTE; + + /* Convert the image to indexed color */ ! convert_image ((GImage *) dialog->gimage_ptr, INDEXED, dialog->num_cols, dialog->dither, palette_type); gdisplays_flush (); gtk_widget_destroy (dialog->shell); *************** *** 262,267 **** --- 431,452 ---- dialog->num_cols = BOUNDS (((int) atof (str)), 0, 256); } + + static void + indexed_radio_update (GtkWidget *widget, + gpointer data) + { + gint *toggle_val; + + toggle_val = (int *) data; + + if (GTK_TOGGLE_BUTTON (widget)->active) + *toggle_val = TRUE; + else + *toggle_val = FALSE; + } + + static void indexed_dither_update (GtkWidget *w, gpointer data) *************** *** 279,286 **** static void convert_image (GImage *gimage, int new_type, ! int num_cols, /* used only for new_type == INDEXED */ ! int dither) /* used only for new_type == INDEXED */ { QuantizeObj *quantobj; Layer *layer; --- 464,472 ---- static void convert_image (GImage *gimage, int new_type, ! int num_cols, /* used only for new_type == INDEXED */ ! int dither, /* used only for new_type == INDEXED */ ! int palette_type) /* used only for new_type == INDEXED */ { QuantizeObj *quantobj; Layer *layer; *************** *** 308,314 **** old_type = gimage->base_type; gimage->base_type = new_type; ! /* Build the histogram */ if (new_type == INDEXED) { int i, j; --- 494,500 ---- old_type = gimage->base_type; gimage->base_type = new_type; ! /* Convert to indexed? Build histogram if necessary. */ if (new_type == INDEXED) { int i, j; *************** *** 317,335 **** if (old_type == GRAY && num_cols == 256) dither = FALSE; ! quantobj = initialize_median_cut (old_type, num_cols, dither ? FSDITHER : NODITHER); - /* Build the histogram */ - list = gimage->layers; - while (list) - { - layer = (Layer *) list->data; - list = next_item (list); if (old_type == GRAY) ! generate_histogram_gray (quantobj->histogram, layer); else ! generate_histogram_rgb (quantobj->histogram, layer); } (* quantobj->first_pass) (quantobj); --- 503,531 ---- if (old_type == GRAY && num_cols == 256) dither = FALSE; ! quantobj = initialize_median_cut (old_type, num_cols, dither ? FSDITHER : NODITHER, palette_type); + if (palette_type == MAKE_PALETTE) + { + if (old_type == GRAY) ! zero_histogram_gray (quantobj->histogram); else ! zero_histogram_rgb (quantobj->histogram); ! ! /* Build the histogram */ ! list = gimage->layers; ! while (list) ! { ! layer = (Layer *) list->data; ! list = next_item (list); ! ! if (old_type == GRAY) ! generate_histogram_gray (quantobj->histogram, layer); ! else ! generate_histogram_rgb (quantobj->histogram, layer); ! } } (* quantobj->first_pass) (quantobj); *************** *** 596,604 **** int size; void *pr; - zero_histogram_gray (histogram); pixel_region_init (&srcPR, layer->tiles, 0, 0, layer->width, layer->height, FALSE); ! for (pr = pixel_regions_register (1, &srcPR); pr != NULL; pr = pixel_regions_process (pr)) { data = srcPR.data; size = srcPR.w * srcPR.h; --- 792,801 ---- int size; void *pr; pixel_region_init (&srcPR, layer->tiles, 0, 0, layer->width, layer->height, FALSE); ! for (pr = pixel_regions_register (1, &srcPR); ! pr != NULL; ! pr = pixel_regions_process (pr)) { data = srcPR.data; size = srcPR.w * srcPR.h; *************** *** 621,629 **** void *pr; ColorFreq *col; - zero_histogram_rgb (histogram); pixel_region_init (&srcPR, layer->tiles, 0, 0, layer->width, layer->height, FALSE); ! for (pr = pixel_regions_register (1, &srcPR); pr != NULL; pr = pixel_regions_process (pr)) { data = srcPR.data; size = srcPR.w * srcPR.h; --- 818,827 ---- void *pr; ColorFreq *col; pixel_region_init (&srcPR, layer->tiles, 0, 0, layer->width, layer->height, FALSE); ! for (pr = pixel_regions_register (1, &srcPR); ! pr != NULL; ! pr = pixel_regions_process (pr)) { data = srcPR.data; size = srcPR.w * srcPR.h; *************** *** 1066,1071 **** --- 1264,1270 ---- quantobj->cmap[icolor].red = (Rtotal + (total>>1)) / total; quantobj->cmap[icolor].green = (Gtotal + (total>>1)) / total; quantobj->cmap[icolor].blue = (Btotal + (total>>1)) / total; + } *************** *** 1535,1540 **** --- 1734,1768 ---- } + static void + monopal_pass1_rgb (QuantizeObj *quantobj) + { + quantobj -> actual_number_of_colors = 2; + quantobj -> cmap[0].red = 0; + quantobj -> cmap[0].green = 0; + quantobj -> cmap[0].blue = 0; + quantobj -> cmap[1].red = 255; + quantobj -> cmap[1].green = 255; + quantobj -> cmap[1].blue = 255; + } + + + static void + webpal_pass1_rgb (QuantizeObj *quantobj) + { + int i; + + quantobj -> actual_number_of_colors = 216; + + for (i=0;i<216;i++) + { + quantobj->cmap[i].red = webpal[i*3]; + quantobj->cmap[i].green = webpal[i*3 +1]; + quantobj->cmap[i].blue = webpal[i*3 +2]; + } + } + + /* * Map some rows of pixels to the output colormapped representation. */ *************** *** 1870,1875 **** --- 2098,2105 ---- int has_alpha; int width, height; + + zero_histogram_rgb (histogram); has_alpha = layer_has_alpha (layer); *************** *** 1962,1967 **** --- 2192,2198 ---- index = *cachep - 1; dest[INDEXED_PIX] = index; + if (has_alpha) dest[ALPHA_I_PIX] = (src[ALPHA_PIX] > 127) ? 255 : 0; *************** *** 2054,2060 **** static QuantizeObj* initialize_median_cut (int type, int num_colors, ! int dither_type) { QuantizeObj * quantobj; --- 2285,2292 ---- static QuantizeObj* initialize_median_cut (int type, int num_colors, ! int dither_type, ! int palette_type) { QuantizeObj * quantobj; *************** *** 2091,2097 **** } break; case RGB: ! quantobj->first_pass = median_cut_pass1_rgb; switch (dither_type) { case NODITHER: --- 2323,2340 ---- } break; case RGB: ! switch (palette_type) ! { ! case MAKE_PALETTE: ! quantobj->first_pass = median_cut_pass1_rgb; ! break; ! case WEB_PALETTE: ! quantobj->first_pass = webpal_pass1_rgb; ! break; ! case MONO_PALETTE: ! default: ! quantobj->first_pass = monopal_pass1_rgb; ! } switch (dither_type) { case NODITHER: *************** *** 2164,2170 **** success = (gimage_base_type (gimage) != RGB); if (success) ! convert_image ((void *) gimage, RGB, 0, 0); return procedural_db_return_args (&convert_rgb_proc, success); } --- 2407,2413 ---- success = (gimage_base_type (gimage) != RGB); if (success) ! convert_image ((void *) gimage, RGB, 0, 0, 0); return procedural_db_return_args (&convert_rgb_proc, success); } *************** *** 2222,2228 **** success = (gimage_base_type (gimage) != GRAY); if (success) ! convert_image ((void *) gimage, GRAY, 0, 0); return procedural_db_return_args (&convert_grayscale_proc, success); } --- 2465,2471 ---- success = (gimage_base_type (gimage) != GRAY); if (success) ! convert_image ((void *) gimage, GRAY, 0, 0, 0); return procedural_db_return_args (&convert_grayscale_proc, success); } *************** *** 2250,2256 **** { "gimp_convert_indexed", "Convert specified image to indexed color", ! "This procedure converts the specified image to indexed color. This process requires an image of type GRAY or RGB. The 'num_cols' arguments specifies how many colors the resulting image should be quantized to.", "Spencer Kimball & Peter Mattis", "Spencer Kimball & Peter Mattis", "1995-1996", --- 2493,2499 ---- { "gimp_convert_indexed", "Convert specified image to indexed color", ! "This procedure converts the specified image to indexed color. This process requires an image of type GRAY or RGB. The 'num_cols' arguments specifies how many colors the resulting image should be quantized to (1-256).", "Spencer Kimball & Peter Mattis", "Spencer Kimball & Peter Mattis", "1995-1996", *************** *** 2293,2304 **** if (success) { num_cols = args[2].value.pdb_int; ! if (num_cols < 0 || num_cols > 255) success = FALSE; } if (success) ! convert_image ((void *) gimage, INDEXED, num_cols, dither); return procedural_db_return_args (&convert_indexed_proc, success); } --- 2536,2547 ---- if (success) { num_cols = args[2].value.pdb_int; ! if (num_cols < 1 || num_cols > MAXNUMCOLORS) success = FALSE; } if (success) ! convert_image ((void *) gimage, INDEXED, num_cols, dither, 0); return procedural_db_return_args (&convert_indexed_proc, success); }