gimp/app/convert.c

3129 lines
86 KiB
C

/* The GIMP -- an image manipulation program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/*
* (out of date) TODO for Convert:
*
* Use palette of another open INDEXED image
* Different dither types
* Alpha dithering
*
* Do error-splitting trick for GREY->INDEXED
*/
/*
* 99/02/24 - Many revisions to the box-cut quantizer used in RGB->INDEXED
* conversion. Box to be cut is chosen on the basis of posessing an axis
* with the largest sum of weighted perceptible error, rather than based on
* volume or population. The box is split along this axis rather than its
* longest axis, at the point of error mean rather than simply at its centre.
* Error-limiting in the F-S dither has been disabled - it may become optional
* again later. If you're convinced that you have an image where the old
* dither looks better, let me know. [Adam]
*
* 99/01/10 - Hourglass... [Adam]
*
* 98/07/25 - Convert-to-indexed now remembers the last invocation's
* settings. Also, GRAY->INDEXED more flexible. [Adam]
*
* 98/07/05 - Sucked the warning about quantizing to too many colours into
* a text widget embedded in the dialog, improved intelligence of dialog
* to default 'custom palette' selection to 'Web' if available, and
* in this case not bother to present the native WWW-palette radio
* button. [Adam]
*
* 98/04/13 - avoid a division by zero when converting an empty gray-scale
* image (who would like to do such a thing anyway??) [Sven ]
*
* 98/03/23 - fixed a longstanding fencepost - hopefully the *right*
* way, *again*. [Adam]
*
* 97/11/14 - added a proper pdb interface and support for dithering
* to custom palettes (based on a patch by Eric Hernes) [Yosh]
*
* 97/11/04 - fixed the accidental use of the colour-counting case
* when palette_type is WEB or MONO. [Adam]
*
* 97/10/25 - colour-counting implemented (could use some hashing, but
* performance actually seems okay) - now RGB->INDEXED conversion isn't
* destructive if it doesn't have to be. [Adam]
*
* 97/10/14 - fixed divide-by-zero when converting a completely transparent
* RGB image to indexed. [Adam]
*
* 97/07/01 - started todo/revision log. Put code back in to
* eliminate full-alpha pixels from RGB histogram.
* [Adam D. Moss - adam@gimp.org]
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "appenv.h"
#include "actionarea.h"
#include "convert.h"
#include "cursorutil.h"
#include "drawable.h"
#include "floating_sel.h"
#include "fsdither.h"
#include "gdisplay.h"
#include "interface.h"
#include "undo.h"
#include "palette.h"
#include "libgimp/gimpintl.h"
#include "layer_pvt.h" /* ick. */
#include "drawable_pvt.h" /* ick ick. */
#include "tile_manager_pvt.h" /* ick ick ick. */
#define NODITHER 0
#define FSDITHER 1
#define NODESTRUCTDITHER 2
#define PRECISION_R 6
#define PRECISION_G 6
#define PRECISION_B 5
#define HIST_R_ELEMS (1<<PRECISION_R)
#define HIST_G_ELEMS (1<<PRECISION_G)
#define HIST_B_ELEMS (1<<PRECISION_B)
#define MR HIST_G_ELEMS*HIST_B_ELEMS
#define MG HIST_B_ELEMS
#define BITS_IN_SAMPLE 8
#define R_SHIFT (BITS_IN_SAMPLE-PRECISION_R)
#define G_SHIFT (BITS_IN_SAMPLE-PRECISION_G)
#define B_SHIFT (BITS_IN_SAMPLE-PRECISION_B)
#define R_SCALE 30 /* scale R distances by this much */
#define G_SCALE 59 /* scale G distances by this much */
#define B_SCALE 11 /* and B by this much */
#define INTENSITY(r,g,b) (r * 0.30 + g * 0.59 + b * 0.11 + 0.001)
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 *);
typedef void (* Pass2_Func) (QuantizeObj *, Layer *, TileManager *);
typedef void (* Cleanup_Func) (QuantizeObj *);
typedef unsigned long ColorFreq;
typedef ColorFreq *Histogram;
struct _Color
{
int red;
int green;
int blue;
};
struct _QuantizeObj
{
Pass1_Func first_pass; /* first pass over image data creates colormap */
Pass2_Func second_pass; /* second pass maps from image data to colormap */
Cleanup_Func delete_func; /* function to clean up data associated with private */
int desired_number_of_colors; /* Number of colors we will allow */
int actual_number_of_colors; /* Number of colors actually needed */
Color cmap[256]; /* colormap created by quantization */
Histogram histogram; /* holds the histogram */
};
typedef struct
{
/* The bounds of the box (inclusive); expressed as histogram indexes */
int Rmin, Rmax;
int Rhalferror;
int Gmin, Gmax;
int Ghalferror;
int Bmin, Bmax;
int Bhalferror;
/* The volume (actually 2-norm) of the box */
int volume;
/* The number of nonzero histogram cells within this box */
long colorcount;
/* The sum of the weighted error within this box */
guint64 error;
/* The sum of the unweighted error within this box */
guint64 rerror;
guint64 gerror;
guint64 berror;
} box, *boxptr;
typedef struct
{
long ncolors;
long dither;
} Options;
typedef struct
{
GtkWidget * shell;
GimpImage* gimage;
int dither;
int num_cols;
int palette;
int makepal_flag;
int webpal_flag;
int custompal_flag;
int monopal_flag;
int reusepal_flag;
} IndexedDialog;
static void indexed_ok_callback (GtkWidget *, gpointer);
static void indexed_cancel_callback (GtkWidget *, gpointer);
static gint indexed_delete_callback (GtkWidget *, GdkEvent *, 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 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 *, int col_limit);
static QuantizeObj* initialize_median_cut (int, int, int, int);
static void
compute_color_rgb (QuantizeObj *quantobj,
Histogram histogram,
boxptr boxp,
int icolor);
static unsigned char found_cols[MAXNUMCOLORS][3];
static int num_found_cols;
static gboolean needs_quantize;
static GtkWidget *build_palette_menu(int *default_palette);
static void palette_entries_callback(GtkWidget *w, gpointer client_data);
static gboolean UserHasWebPal = FALSE;
PaletteEntriesP theCustomPalette = NULL;
/* Defaults */
static int snum_cols = 256;
static gboolean sdither = TRUE;
static gboolean smakepal_flag = TRUE;
static gboolean swebpal_flag = FALSE;
static gboolean scustompal_flag = FALSE;
static gboolean smonopal_flag = FALSE;
static gboolean sreusepal_flag = FALSE;
void
convert_to_rgb (GimpImage *gimage)
{
convert_image (gimage, RGB, 0, 0, 0);
gdisplays_flush ();
}
void
convert_to_grayscale (GimpImage* gimage)
{
convert_image (gimage, GRAY, 0, 0, 0);
gdisplays_flush ();
}
/* the action area structure */
static ActionAreaItem action_items[] =
{
{ N_("OK"), indexed_ok_callback, NULL, NULL },
{ N_("Cancel"), indexed_cancel_callback, NULL, NULL }
};
/*
static void
realize_text (GtkWidget *text, gpointer data)
{
gtk_text_set_word_wrap (GTK_TEXT (text), TRUE);
gtk_text_freeze (GTK_TEXT (text));
gtk_text_insert (GTK_TEXT (text), NULL, &text->style->black, NULL,
"You are attempting to convert an image with alpha/layers from RGB/GRAY to INDEXED. You should not generate a palette of more than 255 colors if you intend to create a transparent or animated GIF file from this image.",
-1);
gtk_text_thaw (GTK_TEXT (text));
}
*/
void
convert_to_indexed (GimpImage *gimage)
{
IndexedDialog *dialog;
GtkWidget *vbox;
GtkWidget *hbox;
GtkWidget *label;
GtkWidget *text;
GtkWidget *frame;
GtkWidget *toggle;
GSList *group = NULL;
dialog = g_new(IndexedDialog, 1);
dialog->gimage = gimage;
dialog->num_cols = snum_cols;
dialog->dither = sdither;
dialog->makepal_flag = smakepal_flag;
dialog->webpal_flag = swebpal_flag;
dialog->custompal_flag = scustompal_flag;
dialog->monopal_flag = smonopal_flag;
dialog->reusepal_flag = sreusepal_flag;
dialog->shell = gtk_dialog_new ();
gtk_window_set_wmclass (GTK_WINDOW (dialog->shell), "indexed_color_conversion", "Gimp");
gtk_window_set_title (GTK_WINDOW (dialog->shell), _("Indexed Color Conversion"));
gtk_signal_connect (GTK_OBJECT (dialog->shell), "delete_event",
GTK_SIGNAL_FUNC (indexed_delete_callback),
dialog);
frame = gtk_frame_new (_("Palette Options"));
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
gtk_container_set_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, 1);
gtk_container_set_border_width (GTK_CONTAINER (vbox), 2);
gtk_container_set_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);
/* 'generate palette' */
hbox = gtk_hbox_new (FALSE, 1);
gtk_container_set_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_active (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);
text = gtk_entry_new ();
{
if (dialog->num_cols == 256)
{
if ((!gimage_is_empty (gimage))
&&
(
gimage->layers->next
||
layer_has_alpha((Layer *) gimage->layers->data)
)
)
{
gtk_entry_set_text (GTK_ENTRY (text), "255");
dialog->num_cols = 255;
}
else
{
gtk_entry_set_text (GTK_ENTRY (text), "256");
}
}
else
{
gchar tempstr[50];
sprintf(tempstr, "%d", dialog->num_cols);
gtk_entry_set_text (GTK_ENTRY (text), tempstr);
}
gtk_widget_set_usize (text, 50, 25);
gtk_box_pack_start (GTK_BOX (hbox), text, FALSE, FALSE, 0);
gtk_signal_connect (GTK_OBJECT (text), "changed",
(GtkSignalFunc) indexed_num_cols_update,
dialog);
}
gtk_widget_show (text);
gtk_widget_show (hbox);
if (TRUE /* gimage->base_type == RGB */ )
{
GtkWidget *menu;
GtkWidget *palette_option_menu;
int default_palette;
menu = build_palette_menu(&default_palette);
if (menu)
{
/* 'custom' palette from dialog */
hbox = gtk_hbox_new (FALSE, 1);
gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
toggle = gtk_radio_button_new_with_label (group, _("Use custom 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->custompal_flag));
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle),
dialog->custompal_flag);
gtk_widget_show (toggle);
palette_option_menu = gtk_option_menu_new();
gtk_option_menu_set_menu (GTK_OPTION_MENU(palette_option_menu), menu);
gtk_option_menu_set_history(GTK_OPTION_MENU(palette_option_menu),
default_palette);
gtk_box_pack_start(GTK_BOX(hbox), palette_option_menu, TRUE, TRUE, 2);
gtk_widget_show(palette_option_menu);
gtk_widget_show (hbox);
}
}
if (!UserHasWebPal)
{
/* 'web palette'
* Only presented as an option to the user if they do not
* already have the 'Web' GIMP palette installed on their
* system.
*/
hbox = gtk_hbox_new (FALSE, 1);
{
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_active (GTK_TOGGLE_BUTTON (toggle), dialog->webpal_flag);
}
gtk_widget_show (toggle);
}
gtk_widget_show (hbox);
}
/* 'mono palette' */
hbox = gtk_hbox_new (FALSE, 1);
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_active (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_set_border_width (GTK_CONTAINER (frame), 2);
gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog->shell)->vbox), frame, TRUE, TRUE, 0);
vbox = gtk_vbox_new (FALSE, 1);
gtk_container_set_border_width (GTK_CONTAINER (vbox), 1);
/* 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, 1);
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_active (GTK_TOGGLE_BUTTON (toggle), dialog->dither);
gtk_box_pack_start (GTK_BOX (hbox), toggle, TRUE, FALSE, 0);
gtk_signal_connect (GTK_OBJECT (toggle), "toggled",
(GtkSignalFunc) indexed_dither_update,
dialog);
gtk_widget_show (label);
gtk_widget_show (toggle);
gtk_widget_show (hbox);
}
gtk_widget_show(frame);
/* if the image isn't non-alpha/layered, set the default number of
colours to one less than max, to leave room for a transparent index
for transparent/animated GIFs */
if ((!gimage_is_empty (gimage))
&&
(
gimage->layers->next
||
layer_has_alpha((Layer *) gimage->layers->data)
)
)
{
frame = gtk_frame_new (_(" [ Warning ] "));
{
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
gtk_container_set_border_width (GTK_CONTAINER (frame), 2);
gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog->shell)->vbox), frame, TRUE, TRUE, 0);
label = gtk_label_new ("You are attempting to convert an image with alpha/layers from RGB/GRAY to INDEXED.\n"
"\tYou should not generate a palette of more than 255 colors if you intend to create a transparent or animated GIF file from this image.");
gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_FILL);
gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
gtk_container_add (GTK_CONTAINER (frame), label);
gtk_widget_show(label);
/*
table = gtk_table_new (2, 1, FALSE);
{
gtk_container_set_border_width (GTK_CONTAINER (table), 1);
gtk_container_add (GTK_CONTAINER (frame), table);
text = gtk_text_new (NULL, NULL);
{
gtk_table_attach (GTK_TABLE (table), text, 0, 1, 0, 1,
GTK_FILL | GTK_EXPAND,
GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 0);
gtk_signal_connect (GTK_OBJECT (text), "realize",
GTK_SIGNAL_FUNC (realize_text), NULL);
}
gtk_widget_show (text);
scrollbar = gtk_vscrollbar_new (GTK_TEXT (text)->vadj);
{
gtk_table_attach (GTK_TABLE (table), scrollbar, 1, 2, 0, 1,
GTK_FILL, GTK_EXPAND | GTK_FILL | GTK_SHRINK,
0, 0);
}
gtk_widget_show (scrollbar);
}
gtk_widget_show(table);
*/
}
gtk_widget_show(frame);
}
/* The action area */
action_items[0].user_data = dialog;
action_items[1].user_data = dialog;
build_action_area (GTK_DIALOG (dialog->shell), action_items, 2, 0);
gtk_widget_show (vbox);
gtk_widget_show (dialog->shell);
}
static GtkWidget *
build_palette_menu(int *default_palette)
{
GtkWidget *menu;
GtkWidget *menu_item;
GSList *list;
PaletteEntriesP entries;
int i, item;
UserHasWebPal = FALSE;
if(!palette_entries_list)
{
palette_init_palettes(FALSE);
}
list = palette_entries_list;
if (!list)
return NULL;
menu = gtk_menu_new();
for(i=0, item=0, list = palette_entries_list, *default_palette = -1;
list;
i++, list = g_slist_next (list))
{
entries = (PaletteEntriesP) list->data;
/* Preferentially, the initial default is 'Web' if available */
if (*default_palette==-1 &&
g_strcasecmp(entries->name, "Web")==0)
{
theCustomPalette = entries;
UserHasWebPal = TRUE;
}
/* We can't dither to > 256 colors */
if (entries->n_colors <= 256)
{
menu_item = gtk_menu_item_new_with_label (entries->name);
gtk_signal_connect( GTK_OBJECT(menu_item), "activate",
(GtkSignalFunc) palette_entries_callback,
(gpointer)entries);
gtk_container_add(GTK_CONTAINER(menu), menu_item);
gtk_widget_show(menu_item);
if (theCustomPalette == entries)
{
*default_palette = item;
}
item++;
}
}
/* default to first one (only used if 'web' palette not avail.) */
if(*default_palette==-1)
{
theCustomPalette = (PaletteEntriesP) palette_entries_list->data;
*default_palette = 0;
}
return menu;
}
static void
palette_entries_callback(GtkWidget *w, gpointer client_data){
theCustomPalette = (PaletteEntriesP)client_data;
}
static void
indexed_ok_callback (GtkWidget *widget,
gpointer client_data)
{
IndexedDialog *dialog;
int palette_type;
dialog = (IndexedDialog *) client_data;
if (dialog->webpal_flag) palette_type = WEB_PALETTE;
else
if (dialog->custompal_flag) palette_type = CUSTOM_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 (dialog->gimage, INDEXED, dialog->num_cols,
dialog->dither, palette_type);
gdisplays_flush ();
/* Save defaults for next time */
snum_cols = dialog->num_cols;
sdither = dialog->dither;
smakepal_flag = dialog->makepal_flag;
swebpal_flag = dialog->webpal_flag;
scustompal_flag = dialog->custompal_flag;
smonopal_flag = dialog->monopal_flag;
sreusepal_flag = dialog->reusepal_flag;
gtk_widget_destroy (dialog->shell);
g_free (dialog);
dialog = NULL;
}
static gint
indexed_delete_callback (GtkWidget *w,
GdkEvent *e,
gpointer client_data)
{
indexed_cancel_callback (w, client_data);
return TRUE;
}
static void
indexed_cancel_callback (GtkWidget *widget,
gpointer client_data)
{
IndexedDialog *dialog;
dialog = (IndexedDialog *) client_data;
gtk_widget_destroy (dialog->shell);
g_free (dialog);
dialog = NULL;
}
static void
indexed_num_cols_update (GtkWidget *w,
gpointer data)
{
IndexedDialog *dialog;
char *str;
str = gtk_entry_get_text (GTK_ENTRY (w));
dialog = (IndexedDialog *) data;
dialog->num_cols = BOUNDS(((int) atof (str)), 1, 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)
{
IndexedDialog *dialog;
dialog = (IndexedDialog *) data;
if (GTK_TOGGLE_BUTTON (w)->active)
dialog->dither = TRUE;
else
dialog->dither = FALSE;
}
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;
Layer *floating_layer;
int old_type;
GSList *list;
int new_layer_type;
int new_layer_bytes;
int has_alpha;
TileManager *new_tiles;
quantobj = NULL;
new_layer_type = RGBA_GIMAGE;
new_layer_bytes = 4;
gimp_add_busy_cursors();
/* Get the floating layer if one exists */
floating_layer = gimage_floating_sel (gimage);
undo_push_group_start (gimage, GIMAGE_MOD_UNDO);
/* Relax the floating selection */
if (floating_layer)
floating_sel_relax (floating_layer, TRUE);
/* Push the image size to the stack */
undo_push_gimage_mod (gimage);
/* Set the new base type */
old_type = gimage->base_type;
gimage->base_type = new_type;
/* Convert to indexed? Build histogram if necessary. */
if (new_type == INDEXED)
{
int i, j;
/* don't dither if the input is grayscale and we are simply mapping every color */
if (old_type == GRAY && num_cols == 256 && palette_type == MAKE_PALETTE)
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);
/* To begin, assume that there are fewer colours in
* the image than the user actually asked for. In that
* case, we don't need to quantize or dither.
*/
needs_quantize = FALSE;
num_found_cols = 0;
/* Build the histogram */
list = gimage->layers;
while (list)
{
layer = (Layer *) list->data;
list = g_slist_next (list);
if (old_type == GRAY)
generate_histogram_gray (quantobj->histogram, layer);
else
generate_histogram_rgb (quantobj->histogram, layer, num_cols);
/*
* Note: generate_histogram_rgb may set needs_quantize if
* the image contains more colours than the limit specified
* by the user.
*/
}
}
if (
(old_type == RGB) &&
(!needs_quantize) &&
(palette_type == MAKE_PALETTE)
)
{
/* If this is an RGB image, and the user wanted a custom-built
* generated palette, and this image has no more colours than
* the user asked for, we don't need the first pass (quantization).
*
* There's also no point in dithering, since there's no error to
* spread. So we destroy the old quantobj and make a new one
* with the remapping function set to a special LUT-based
* no-dither remapper.
*/
quantobj->delete_func (quantobj);
quantobj = initialize_median_cut (old_type, num_cols,
NODESTRUCTDITHER, palette_type);
/* We can skip the first pass (palette creation) */
quantobj->actual_number_of_colors = num_found_cols;
for (i = 0; i < num_found_cols; i++)
{
quantobj->cmap[i].red = found_cols[i][0];
quantobj->cmap[i].green = found_cols[i][1];
quantobj->cmap[i].blue = found_cols[i][2];
}
}
else
{
(* quantobj->first_pass) (quantobj);
}
if (gimage->cmap)
g_free (gimage->cmap);
gimage->cmap = (unsigned char *) g_malloc (COLORMAP_SIZE);
gimage->num_cols = quantobj->actual_number_of_colors;
for (i = 0, j = 0; i < quantobj->actual_number_of_colors; i++)
{
gimage->cmap[j++] = quantobj->cmap[i].red;
gimage->cmap[j++] = quantobj->cmap[i].green;
gimage->cmap[j++] = quantobj->cmap[i].blue;
}
}
/* Convert all layers */
list = gimage->layers;
while (list)
{
layer = (Layer *) list->data;
list = g_slist_next (list);
has_alpha = layer_has_alpha (layer);
switch (new_type)
{
case RGB:
new_layer_type = (has_alpha) ? RGBA_GIMAGE : RGB_GIMAGE;
new_layer_bytes = (has_alpha) ? 4 : 3;
break;
case GRAY:
new_layer_type = (has_alpha) ? GRAYA_GIMAGE : GRAY_GIMAGE;
new_layer_bytes = (has_alpha) ? 2 : 1;
break;
case INDEXED:
new_layer_type = (has_alpha) ? INDEXEDA_GIMAGE : INDEXED_GIMAGE;
new_layer_bytes = (has_alpha) ? 2 : 1;
break;
default:
break;
}
new_tiles = tile_manager_new (GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height, new_layer_bytes);
switch (new_type)
{
case RGB:
rgb_converter (layer, new_tiles, old_type);
break;
case GRAY:
grayscale_converter (layer, new_tiles, old_type);
break;
case INDEXED:
(* quantobj->second_pass) (quantobj, layer, new_tiles);
break;
default:
break;
}
/* Push the layer on the undo stack */
undo_push_layer_mod (gimage, layer);
GIMP_DRAWABLE(layer)->tiles = new_tiles;
GIMP_DRAWABLE(layer)->bytes = new_layer_bytes;
GIMP_DRAWABLE(layer)->type = new_layer_type;
GIMP_DRAWABLE(layer)->has_alpha = TYPE_HAS_ALPHA(new_layer_type);
}
/* Delete the quantizer object, if there is one */
if (quantobj)
quantobj->delete_func (quantobj);
/* Make sure the projection is up to date */
gimage_projection_realloc (gimage);
/* Rigor the floating selection */
if (floating_layer)
floating_sel_rigor (floating_layer, TRUE);
undo_push_group_end (gimage);
/* shrink wrap and update all views */
layer_invalidate_previews (gimage);
gimage_invalidate_preview (gimage);
gdisplays_update_title (gimage);
gdisplays_update_full (gimage);
gimp_image_colormap_changed (gimage, -1);
gimp_remove_busy_cursors(NULL);
}
static void
rgb_converter (Layer *layer,
TileManager *new_tiles,
int old_type)
{
PixelRegion srcPR, destPR;
int row, col;
int offset;
int has_alpha;
unsigned char *src, *s;
unsigned char *dest, *d;
unsigned char *cmap;
void *pr;
has_alpha = layer_has_alpha (layer);
pixel_region_init (&srcPR, GIMP_DRAWABLE(layer)->tiles, 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height, FALSE);
pixel_region_init (&destPR, new_tiles, 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height, TRUE);
for (pr = pixel_regions_register (2, &srcPR, &destPR); pr != NULL; pr = pixel_regions_process (pr))
{
src = srcPR.data;
dest = destPR.data;
switch (old_type)
{
case GRAY:
for (row = 0; row < srcPR.h; row++)
{
s = src;
d = dest;
for (col = 0; col < srcPR.w; col++)
{
d[RED_PIX] = *s;
d[GREEN_PIX] = *s;
d[BLUE_PIX] = *s;
d += 3;
s++;
if (has_alpha)
*d++ = *s++;
}
src += srcPR.rowstride;
dest += destPR.rowstride;
}
break;
case INDEXED:
cmap = drawable_cmap (GIMP_DRAWABLE(layer));
for (row = 0; row < srcPR.h; row++)
{
s = src;
d = dest;
for (col = 0; col < srcPR.w; col++)
{
offset = *s++ * 3;
d[RED_PIX] = cmap[offset + 0];
d[GREEN_PIX] = cmap[offset + 1];
d[BLUE_PIX] = cmap[offset + 2];
d += 3;
if (has_alpha)
*d++ = *s++;
}
src += srcPR.rowstride;
dest += destPR.rowstride;
}
break;
default:
break;
}
}
}
static void
grayscale_converter (Layer *layer,
TileManager *new_tiles,
int old_type)
{
PixelRegion srcPR, destPR;
int row, col;
int offset, val;
int has_alpha;
unsigned char *src, *s;
unsigned char *dest, *d;
unsigned char *cmap;
void *pr;
has_alpha = layer_has_alpha (layer);
pixel_region_init (&srcPR, GIMP_DRAWABLE(layer)->tiles, 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height, FALSE);
pixel_region_init (&destPR, new_tiles, 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height, TRUE);
for (pr = pixel_regions_register (2, &srcPR, &destPR); pr != NULL; pr = pixel_regions_process (pr))
{
src = srcPR.data;
dest = destPR.data;
switch (old_type)
{
case RGB:
for (row = 0; row < srcPR.h; row++)
{
s = src;
d = dest;
for (col = 0; col < srcPR.w; col++)
{
val = INTENSITY (s[RED_PIX], s[GREEN_PIX], s[BLUE_PIX]);
*d++ = (unsigned char) val;
s += 3;
if (has_alpha)
*d++ = *s++;
}
src += srcPR.rowstride;
dest += destPR.rowstride;
}
break;
case INDEXED:
cmap = drawable_cmap (GIMP_DRAWABLE(layer));
for (row = 0; row < srcPR.h; row++)
{
s = src;
d = dest;
for (col = 0; col < srcPR.w; col++)
{
offset = *s++ * 3;
val = INTENSITY (cmap[offset+0], cmap[offset+1], cmap[offset+2]);
*d++ = (unsigned char) val;
if (has_alpha)
*d++ = *s++;
}
src += srcPR.rowstride;
dest += destPR.rowstride;
}
break;
default:
break;
}
}
}
/*
* Indexed color conversion machinery
*/
static void
zero_histogram_gray (Histogram histogram)
{
int i;
for (i = 0; i < 256; i++)
histogram[i] = 0;
}
static void
zero_histogram_rgb (Histogram histogram)
{
int r, g, b;
for (r = 0; r < HIST_R_ELEMS; r++)
for (g = 0; g < HIST_G_ELEMS; g++)
for (b = 0; b < HIST_B_ELEMS; b++)
histogram[r*MR + g*MG + b] = 0;
}
static void
generate_histogram_gray (Histogram histogram,
Layer *layer)
{
PixelRegion srcPR;
unsigned char *data;
int size;
void *pr;
gboolean has_alpha;
has_alpha = (gboolean) layer_has_alpha(layer);
pixel_region_init (&srcPR, GIMP_DRAWABLE(layer)->tiles, 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(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;
while (size--)
{
histogram[*data] ++;
data += srcPR.bytes;
}
}
}
static void
generate_histogram_rgb (Histogram histogram,
Layer *layer,
int col_limit)
{
PixelRegion srcPR;
unsigned char *data;
int size;
void *pr;
ColorFreq *col;
gboolean has_alpha;
int nfc_iter;
has_alpha = (gboolean) layer_has_alpha(layer);
/* g_print ("col_limit = %d, nfc = %d\n", col_limit, num_found_cols);*/
pixel_region_init (&srcPR, GIMP_DRAWABLE(layer)->tiles, 0, 0,
GIMP_DRAWABLE(layer)->width,
GIMP_DRAWABLE(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;
if (needs_quantize)
{
while (size--)
{
if ((has_alpha && (data[ALPHA_PIX]&128)) || (!has_alpha))
{
col = & histogram[(data[RED_PIX] >> R_SHIFT) * MR +
(data[GREEN_PIX] >> G_SHIFT) * MG +
(data[BLUE_PIX] >> B_SHIFT)];
(*col)++;
}
data += srcPR.bytes;
}
}
else
{
while (size--)
{
if ((has_alpha && (data[ALPHA_PIX]&128)) || (!has_alpha))
{
col = & histogram[(data[RED_PIX] >> R_SHIFT) * MR +
(data[GREEN_PIX] >> G_SHIFT) * MG +
(data[BLUE_PIX] >> B_SHIFT)];
(*col)++;
if (!needs_quantize)
{
for (nfc_iter = 0;
nfc_iter < num_found_cols;
nfc_iter++)
{
if (
(data[RED_PIX] == found_cols[nfc_iter][0])
&&
(data[GREEN_PIX] == found_cols[nfc_iter][1])
&&
(data[BLUE_PIX] == found_cols[nfc_iter][2])
)
goto already_found;
}
/* Colour was not in the table of
* existing colours
*/
num_found_cols++;
if (num_found_cols > col_limit)
{
/* There are more colours in the image
* than were allowed. We switch to plain
* histogram calculation with a view to
* quantizing at a later stage.
*/
needs_quantize = TRUE;
/* g_print (_("\nmax colours exceeded - needs quantize.\n"));*/
goto already_found;
}
else
{
/* Remember the new colour we just found.
*/
found_cols[num_found_cols-1][0] = data[RED_PIX];
found_cols[num_found_cols-1][1] = data[GREEN_PIX];
found_cols[num_found_cols-1][2] = data[BLUE_PIX];
}
}
}
already_found:
data += srcPR.bytes;
}
}
}
/* g_print ("O: col_limit = %d, nfc = %d\n", col_limit, num_found_cols);*/
}
static boxptr
find_split_candidate (boxptr boxlist,
int numboxes)
{
boxptr boxp;
int i;
guint64 maxc = 0;
boxptr which = NULL;
for (i = 0, boxp = boxlist; i < numboxes; i++, boxp++)
{
if (boxp->volume > 0)
{
if (boxp->gerror*G_SCALE > maxc)
{
which = boxp;
maxc = boxp->gerror*G_SCALE;
}
if (boxp->rerror*R_SCALE > maxc)
{
which = boxp;
maxc = boxp->rerror*R_SCALE;
}
if (boxp->berror*B_SCALE > maxc)
{
which = boxp;
maxc = boxp->berror*B_SCALE;
}
}
}
return which;
}
#if 0
static boxptr
find_biggest_color_pop (boxptr boxlist,
int numboxes)
/* Find the splittable box with the largest color population */
/* Returns NULL if no splittable boxes remain */
{
}
#endif
static boxptr
find_biggest_volume (boxptr boxlist,
int numboxes)
/* Find the splittable box with the largest (scaled) volume */
/* Returns NULL if no splittable boxes remain */
{
boxptr boxp;
int i;
int maxv = 0;
boxptr which = NULL;
for (i = 0, boxp = boxlist; i < numboxes; i++, boxp++)
{
if (boxp->volume > maxv)
{
which = boxp;
maxv = boxp->volume;
}
}
return which;
}
static void
update_box_gray (Histogram histogram,
boxptr boxp)
/* Shrink the min/max bounds of a box to enclose only nonzero elements, */
/* and recompute its volume and population */
{
int i, min, max, dist;
long ccount;
min = boxp->Rmin;
max = boxp->Rmax;
if (max > min)
for (i = min; i <= max; i++)
{
if (histogram[i] != 0)
{
boxp->Rmin = min = i;
break;
}
}
if (max > min)
for (i = max; i >= min; i--)
{
if (histogram[i] != 0)
{
boxp->Rmax = max = i;
break;
}
}
/* Update box volume.
* We use 2-norm rather than real volume here; this biases the method
* against making long narrow boxes, and it has the side benefit that
* a box is splittable iff norm > 0.
* Since the differences are expressed in histogram-cell units,
* we have to shift back to JSAMPLE units to get consistent distances;
* after which, we scale according to the selected distance scale factors.
*/
dist = max - min;
boxp->volume = dist * dist;
/* Now scan remaining volume of box and compute population */
ccount = 0;
for (i = min; i <= max; i++)
if (histogram[i] != 0)
ccount++;
boxp->colorcount = ccount;
}
static void
update_box_rgb (Histogram histogram,
boxptr boxp)
/* Shrink the min/max bounds of a box to enclose only nonzero elements, */
/* and recompute its volume, population and error */
{
ColorFreq * histp;
int R,G,B;
int Rmin,Rmax,Gmin,Gmax,Bmin,Bmax;
int dist0,dist1,dist2;
long ccount;
guint64 tempRerror;
guint64 tempGerror;
guint64 tempBerror;
QuantizeObj dummyqo;
box dummybox;
Rmin = boxp->Rmin; Rmax = boxp->Rmax;
Gmin = boxp->Gmin; Gmax = boxp->Gmax;
Bmin = boxp->Bmin; Bmax = boxp->Bmax;
if (Rmax > Rmin)
for (R = Rmin; R <= Rmax; R++)
for (G = Gmin; G <= Gmax; G++)
{
histp = histogram + R*MR + G*MG + Bmin;
for (B = Bmin; B <= Bmax; B++)
if (*histp++ != 0)
{
boxp->Rmin = Rmin = R;
goto have_Rmin;
}
}
have_Rmin:
if (Rmax > Rmin)
for (R = Rmax; R >= Rmin; R--)
for (G = Gmin; G <= Gmax; G++)
{
histp = histogram + R*MR + G*MG + Bmin;
for (B = Bmin; B <= Bmax; B++)
if (*histp++ != 0)
{
boxp->Rmax = Rmax = R;
goto have_Rmax;
}
}
have_Rmax:
if (Gmax > Gmin)
for (G = Gmin; G <= Gmax; G++)
for (R = Rmin; R <= Rmax; R++)
{
histp = histogram + R*MR + G*MG + Bmin;
for (B = Bmin; B <= Bmax; B++)
if (*histp++ != 0)
{
boxp->Gmin = Gmin = G;
goto have_Gmin;
}
}
have_Gmin:
if (Gmax > Gmin)
for (G = Gmax; G >= Gmin; G--)
for (R = Rmin; R <= Rmax; R++)
{
histp = histogram + R*MR + G*MG + Bmin;
for (B = Bmin; B <= Bmax; B++)
if (*histp++ != 0)
{
boxp->Gmax = Gmax = G;
goto have_Gmax;
}
}
have_Gmax:
if (Bmax > Bmin)
for (B = Bmin; B <= Bmax; B++)
for (R = Rmin; R <= Rmax; R++)
{
histp = histogram + R*MR + Gmin*MG + B;
for (G = Gmin; G <= Gmax; G++, histp += MG)
if (*histp != 0)
{
boxp->Bmin = Bmin = B;
goto have_Bmin;
}
}
have_Bmin:
if (Bmax > Bmin)
for (B = Bmax; B >= Bmin; B--)
for (R = Rmin; R <= Rmax; R++)
{
histp = histogram + R*MR + Gmin*MG + B;
for (G = Gmin; G <= Gmax; G++, histp += MG)
if (*histp != 0)
{
boxp->Bmax = Bmax = B;
goto have_Bmax;
}
}
have_Bmax:
/* Update box volume.
* We use 2-norm rather than real volume here; this biases the method
* against making long narrow boxes, and it has the side benefit that
* a box is splittable iff norm > 0.
* Since the differences are expressed in histogram-cell units,
* we have to shift back to JSAMPLE units to get consistent distances;
* after which, we scale according to the selected distance scale factors.
*/
dist0 = (( + Rmax - Rmin) << R_SHIFT) * R_SCALE;
dist1 = (( + Gmax - Gmin) << G_SHIFT) * G_SCALE;
dist2 = (( + Bmax - Bmin) << B_SHIFT) * B_SCALE;
boxp->volume = dist0*dist0 + dist1*dist1 + dist2*dist2;
compute_color_rgb(&dummyqo, histogram, boxp, 0);
/*printf("(%d %d %d)\n", dummyqo.cmap[0].red,dummyqo.cmap[0].green,dummyqo.cmap[0].blue);
fflush(stdout);*/
/* Now scan remaining volume of box and compute population */
ccount = 0;
boxp->error = 0;
boxp->rerror = 0;
boxp->gerror = 0;
boxp->berror = 0;
for (R = Rmin; R <= Rmax; R++)
for (G = Gmin; G <= Gmax; G++)
{
histp = histogram + R*MR + G*MG + Bmin;
for (B = Bmin; B <= Bmax; B++, histp++)
if (*histp != 0)
{
int ge, be, re;
dummybox.Rmin = dummybox.Rmax = R;
dummybox.Gmin = dummybox.Gmax = G;
dummybox.Bmin = dummybox.Bmax = B;
compute_color_rgb(&dummyqo, histogram, &dummybox, 1);
re = dummyqo.cmap[0].red - dummyqo.cmap[1].red;
ge = dummyqo.cmap[0].green - dummyqo.cmap[1].green;
be = dummyqo.cmap[0].blue - dummyqo.cmap[1].blue;
boxp->rerror += (*histp) * re*re;
boxp->gerror += (*histp) * ge*ge;
boxp->berror += (*histp) * be*be;
boxp->error += (*histp) *
(
re*re*R_SCALE + ge*ge*G_SCALE + be*be*B_SCALE
);
ccount += *histp;
}
}
/* Scan again, taking note of halfway error point for red axis */
tempRerror = 0;
boxp->Rhalferror = Rmin;
for (R = Rmin; R < Rmax; R++)
{
dummybox.Rmin = dummybox.Rmax = R;
for (G = Gmin; G <= Gmax; G++)
{
dummybox.Gmin = dummybox.Gmax = G;
histp = histogram + R*MR + G*MG + Bmin;
for (B = Bmin; B <= Bmax; B++, histp++)
if (*histp != 0)
{
int re;
dummybox.Bmin = dummybox.Bmax = B;
compute_color_rgb(&dummyqo, histogram, &dummybox, 1);
re = dummyqo.cmap[0].red - dummyqo.cmap[1].red;
tempRerror += (*histp)*re*re;
if (tempRerror*2 > boxp->rerror)
goto green_axisscan;
else
boxp->Rhalferror = R;
}
}
}
green_axisscan:
/* Scan again, taking note of halfway error point for green axis */
tempGerror = 0;
boxp->Ghalferror = Gmin;
for (G = Gmin; G < Gmax; G++)
{
dummybox.Gmin = dummybox.Gmax = G;
for (R = Rmin; R <= Rmax; R++)
{
dummybox.Rmin = dummybox.Rmax = R;
histp = histogram + R*MR + G*MG + Bmin;
for (B = Bmin; B <= Bmax; B++, histp++)
if (*histp != 0)
{
int ge;
dummybox.Bmin = dummybox.Bmax = B;
compute_color_rgb(&dummyqo, histogram, &dummybox, 1);
ge = dummyqo.cmap[0].green - dummyqo.cmap[1].green;
tempGerror += (*histp)*ge*ge;
if (tempGerror*2 > boxp->gerror)
goto blue_axisscan;
else
boxp->Ghalferror = G;
}
}
}
blue_axisscan:
/* Scan again, taking note of halfway error point for blue axis */
tempBerror = 0;
boxp->Bhalferror = Bmin;
for (B = Bmin; B < Bmax; B++)
{
dummybox.Bmin = dummybox.Bmax = B;
for (R = Rmin; R <= Rmax; R++)
{
dummybox.Rmin = dummybox.Rmax = R;
for (G = Gmin; G <= Gmax; G++)
{
histp = histogram + R*MR + G*MG + B;
if (*histp != 0)
{
int be;
dummybox.Gmin = dummybox.Gmax = G;
compute_color_rgb(&dummyqo, histogram, &dummybox, 1);
be = dummyqo.cmap[0].blue - dummyqo.cmap[1].blue;
tempBerror += (*histp)*be*be;
if (tempBerror*2 > boxp->berror)
goto finished_axesscan;
else
boxp->Bhalferror = B;
}
}
}
}
finished_axesscan:
/*
// boxp->Rhalferror = (Rmin+Rmax)/2;
// boxp->Ghalferror = (Gmin+Gmax)/2;
// boxp->Bhalferror = (Bmin+Bmax)/2;
*/
/*boxp->error = (sqrt((double)(boxp->error/ccount)));*/
/* boxp->rerror = (sqrt((double)((boxp->rerror)/ccount)));
boxp->gerror = (sqrt((double)((boxp->gerror)/ccount)));
boxp->berror = (sqrt((double)((boxp->berror)/ccount)));*/
/*printf(":%lld / %ld: ", boxp->error, ccount);
printf("(%d-%d-%d)(%d-%d-%d)(%d-%d-%d)\n",
Rmin, boxp->Rhalferror, Rmax,
Gmin, boxp->Ghalferror, Gmax,
Bmin, boxp->Bhalferror, Bmax
);
fflush(stdout);*/
boxp->colorcount = ccount;
}
static int
median_cut_gray (Histogram histogram,
boxptr boxlist,
int numboxes,
int desired_colors)
/* Repeatedly select and split the largest box until we have enough boxes */
{
int lb;
boxptr b1, b2;
while (numboxes < desired_colors)
{
/* Select box to split.
* Current algorithm: by population for first half, then by volume.
*/
#if 0
if (numboxes*2 <= desired_colors)
{
b1 = find_biggest_color_pop (boxlist, numboxes);
}
else
#endif
{
b1 = find_biggest_volume (boxlist, numboxes);
}
if (b1 == NULL) /* no splittable boxes left! */
break;
b2 = boxlist + numboxes; /* where new box will go */
/* Copy the color bounds to the new box. */
b2->Rmax = b1->Rmax;
b2->Rmin = b1->Rmin;
/* Current algorithm: split at halfway point.
* (Since the box has been shrunk to minimum volume,
* any split will produce two nonempty subboxes.)
* Note that lb value is max for lower box, so must be < old max.
*/
lb = (b1->Rmax + b1->Rmin) / 2;
b1->Rmax = lb;
b2->Rmin = lb + 1;
/* Update stats for boxes */
update_box_gray (histogram, b1);
update_box_gray (histogram, b2);
numboxes++;
}
return numboxes;
}
static int
median_cut_rgb (Histogram histogram,
boxptr boxlist,
int numboxes,
int desired_colors)
/* Repeatedly select and split the largest box until we have enough boxes */
{
int n,lb;
guint64 R,G,B,cmax;
boxptr b1,b2;
while (numboxes < desired_colors) {
#if 0
/* Select box to split.
* Current algorithm: by population for first half, then by volume.
*/
if (1 || numboxes*2 <= desired_colors)
{
printf("O ");
b1 = find_biggest_color_pop (boxlist, numboxes);
}
else
{
printf(". ");
b1 = find_biggest_volume (boxlist, numboxes);
}
#endif
b1 = find_split_candidate (boxlist, numboxes);
if (b1 == NULL) /* no splittable boxes left! */
break;
b2 = boxlist + numboxes; /* where new box will go */
/* Copy the color bounds to the new box. */
b2->Rmax = b1->Rmax; b2->Gmax = b1->Gmax; b2->Bmax = b1->Bmax;
b2->Rmin = b1->Rmin; b2->Gmin = b1->Gmin; b2->Bmin = b1->Bmin;
/* Choose which axis to split the box on.
* See notes in update_box about scaling distances.
*/
/*
// R = ((b1->Rmax - b1->Rmin) << R_SHIFT) * R_SCALE;
// G = ((b1->Gmax - b1->Gmin) << G_SHIFT) * G_SCALE;
//B = ((b1->Bmax - b1->Bmin) << B_SHIFT) * B_SCALE;
*/
R = R_SCALE*b1->rerror;/* * (((b1->Rmax - b1->Rmin) << R_SHIFT)) * R_SCALE; */
G = G_SCALE*b1->gerror;/* * (((b1->Gmax - b1->Gmin) << G_SHIFT)) * G_SCALE; */
B = B_SCALE*b1->berror;/* * (((b1->Bmax - b1->Bmin) << B_SHIFT)) * B_SCALE; */
/* We want to break any ties in favor of green, then red, blue last.
*/
cmax = G; n = 1;
if (R > cmax) { cmax = R; n = 0; }
if (B > cmax) { n = 2; }
/* Choose split point along selected axis, and update box bounds.
* Note that lb value is max for lower box, so must be < old max.
*/
switch (n)
{
case 0:
lb = b1->Rhalferror;/* *0 + (b1->Rmax + b1->Rmin) / 2; */
b1->Rmax = lb;
b2->Rmin = lb+1;
g_assert(b1->Rmax >= b1->Rmin);
g_assert(b2->Rmax >= b2->Rmin);
break;
case 1:
lb = b1->Ghalferror;/* *0 + (b1->Gmax + b1->Gmin) / 2; */
b1->Gmax = lb;
b2->Gmin = lb+1;
g_assert(b1->Gmax >= b1->Gmin);
g_assert(b2->Gmax >= b2->Gmin);
break;
case 2:
lb = b1->Bhalferror;/* *0 + (b1->Bmax + b1->Bmin) / 2; */
b1->Bmax = lb;
b2->Bmin = lb+1;
g_assert(b1->Bmax >= b1->Bmin);
g_assert(b2->Bmax >= b2->Bmin);
break;
}
/* Update stats for boxes */
update_box_rgb (histogram, b1);
update_box_rgb (histogram, b2);
numboxes++;
}
return numboxes;
}
static void
compute_color_gray (QuantizeObj *quantobj,
Histogram histogram,
boxptr boxp,
int icolor)
/* Compute representative color for a box, put it in colormap[icolor] */
{
int i, min, max;
long count;
long total;
long gtotal;
min = boxp->Rmin;
max = boxp->Rmax;
total = 0;
gtotal = 0;
for (i = min; i <= max; i++)
{
count = histogram[i];
if (count != 0)
{
total += count;
gtotal += i * count;
}
}
if (total != 0)
{
quantobj->cmap[icolor].red = (gtotal + (total >> 1)) / total;
quantobj->cmap[icolor].green = quantobj->cmap[icolor].red;
quantobj->cmap[icolor].blue = quantobj->cmap[icolor].red;
}
else /* The only situation where total==0 is if the image was null or
* all-transparent. In that case we just put a dummy value in
* the colourmap.
*/
{
quantobj->cmap[icolor].red =
quantobj->cmap[icolor].green =
quantobj->cmap[icolor].blue = 0;
}
}
static void
compute_color_rgb (QuantizeObj *quantobj,
Histogram histogram,
boxptr boxp,
int icolor)
/* Compute representative color for a box, put it in colormap[icolor] */
{
/* Current algorithm: mean weighted by pixels (not colors) */
/* Note it is important to get the rounding correct! */
ColorFreq * histp;
int R, G, B;
int Rmin, Rmax;
int Gmin, Gmax;
int Bmin, Bmax;
long count;
long total = 0;
long Rtotal = 0;
long Gtotal = 0;
long Btotal = 0;
Rmin = boxp->Rmin; Rmax = boxp->Rmax;
Gmin = boxp->Gmin; Gmax = boxp->Gmax;
Bmin = boxp->Bmin; Bmax = boxp->Bmax;
for (R = Rmin; R <= Rmax; R++)
for (G = Gmin; G <= Gmax; G++)
{
histp = histogram + R*MR + G*MG + Bmin;
for (B = Bmin; B <= Bmax; B++)
{
if ((count = *histp++) != 0)
{
total += count;
Rtotal += ((R << R_SHIFT) + ((1<<R_SHIFT)>>1)) * count;
Gtotal += ((G << G_SHIFT) + ((1<<G_SHIFT)>>1)) * count;
Btotal += ((B << B_SHIFT) + ((1<<B_SHIFT)>>1)) * count;
}
}
}
if (total != 0)
{
quantobj->cmap[icolor].red = (Rtotal + (total>>1)) / total;
quantobj->cmap[icolor].green = (Gtotal + (total>>1)) / total;
quantobj->cmap[icolor].blue = (Btotal + (total>>1)) / total;
}
else /* The only situation where total==0 is if the image was null or
* all-transparent. In that case we just put a dummy value in
* the colourmap.
*/
{
quantobj->cmap[icolor].red =
quantobj->cmap[icolor].green =
quantobj->cmap[icolor].blue = 0;
}
}
static void
select_colors_gray (QuantizeObj *quantobj,
Histogram histogram)
/* Master routine for color selection */
{
boxptr boxlist;
int numboxes;
int desired = quantobj->desired_number_of_colors;
int i;
/* Allocate workspace for box list */
boxlist = (boxptr) g_malloc ( desired * sizeof(box) );
/* Initialize one box containing whole space */
numboxes = 1;
boxlist[0].Rmin = 0;
boxlist[0].Rmax = 255;
/* Shrink it to actually-used volume and set its statistics */
update_box_gray (histogram, boxlist);
/* Perform median-cut to produce final box list */
numboxes = median_cut_gray (histogram, boxlist, numboxes, desired);
quantobj->actual_number_of_colors = numboxes;
/* Compute the representative color for each box, fill colormap */
for (i = 0; i < numboxes; i++)
compute_color_gray (quantobj, histogram, boxlist + i, i);
}
static void
select_colors_rgb (QuantizeObj *quantobj,
Histogram histogram)
/* Master routine for color selection */
{
boxptr boxlist;
int numboxes;
int desired = quantobj->desired_number_of_colors;
int i;
/* Allocate workspace for box list */
boxlist = (boxptr) g_malloc ( desired * sizeof(box) );
/* Initialize one box containing whole space */
numboxes = 1;
boxlist[0].Rmin = 0;
boxlist[0].Rmax = (1 << PRECISION_R) - 1;
boxlist[0].Gmin = 0;
boxlist[0].Gmax = (1 << PRECISION_G) - 1;
boxlist[0].Bmin = 0;
boxlist[0].Bmax = (1 << PRECISION_B) - 1;
/* Shrink it to actually-used volume and set its statistics */
update_box_rgb (histogram, boxlist);
/* Perform median-cut to produce final box list */
numboxes = median_cut_rgb (histogram, boxlist, numboxes, desired);
quantobj->actual_number_of_colors = numboxes;
/* Compute the representative color for each box, fill colormap */
for (i = 0; i < numboxes; i++)
compute_color_rgb (quantobj, histogram, boxlist + i, i);
}
/*
* These routines are concerned with the time-critical task of mapping input
* colors to the nearest color in the selected colormap.
*
* We re-use the histogram space as an "inverse color map", essentially a
* cache for the results of nearest-color searches. All colors within a
* histogram cell will be mapped to the same colormap entry, namely the one
* closest to the cell's center. This may not be quite the closest entry to
* the actual input color, but it's almost as good. A zero in the cache
* indicates we haven't found the nearest color for that cell yet; the array
* is cleared to zeroes before starting the mapping pass. When we find the
* nearest color for a cell, its colormap index plus one is recorded in the
* cache for future use. The pass2 scanning routines call fill_inverse_cmap
* when they need to use an unfilled entry in the cache.
*
* Our method of efficiently finding nearest colors is based on the "locally
* sorted search" idea described by Heckbert and on the incremental distance
* calculation described by Spencer W. Thomas in chapter III.1 of Graphics
* Gems II (James Arvo, ed. Academic Press, 1991). Thomas points out that
* the distances from a given colormap entry to each cell of the histogram can
* be computed quickly using an incremental method: the differences between
* distances to adjacent cells themselves differ by a constant. This allows a
* fairly fast implementation of the "brute force" approach of computing the
* distance from every colormap entry to every histogram cell. Unfortunately,
* it needs a work array to hold the best-distance-so-far for each histogram
* cell (because the inner loop has to be over cells, not colormap entries).
* The work array elements have to be ints, so the work array would need
* 256Kb at our recommended precision. This is not feasible in DOS machines.
*
* To get around these problems, we apply Thomas' method to compute the
* nearest colors for only the cells within a small subbox of the histogram.
* The work array need be only as big as the subbox, so the memory usage
* problem is solved. Furthermore, we need not fill subboxes that are never
* referenced in pass2; many images use only part of the color gamut, so a
* fair amount of work is saved. An additional advantage of this
* approach is that we can apply Heckbert's locality criterion to quickly
* eliminate colormap entries that are far away from the subbox; typically
* three-fourths of the colormap entries are rejected by Heckbert's criterion,
* and we need not compute their distances to individual cells in the subbox.
* The speed of this approach is heavily influenced by the subbox size: too
* small means too much overhead, too big loses because Heckbert's criterion
* can't eliminate as many colormap entries. Empirically the best subbox
* size seems to be about 1/512th of the histogram (1/8th in each direction).
*
* Thomas' article also describes a refined method which is asymptotically
* faster than the brute-force method, but it is also far more complex and
* cannot efficiently be applied to small subboxes. It is therefore not
* useful for programs intended to be portable to DOS machines. On machines
* with plenty of memory, filling the whole histogram in one shot with Thomas'
* refined method might be faster than the present code --- but then again,
* it might not be any faster, and it's certainly more complicated.
*/
/* log2(histogram cells in update box) for each axis; this can be adjusted */
#define BOX_R_LOG (PRECISION_R-3)
#define BOX_G_LOG (PRECISION_G-3)
#define BOX_B_LOG (PRECISION_B-3)
#define BOX_R_ELEMS (1<<BOX_R_LOG) /* # of hist cells in update box */
#define BOX_G_ELEMS (1<<BOX_G_LOG)
#define BOX_B_ELEMS (1<<BOX_B_LOG)
#define BOX_R_SHIFT (R_SHIFT + BOX_R_LOG)
#define BOX_G_SHIFT (G_SHIFT + BOX_G_LOG)
#define BOX_B_SHIFT (B_SHIFT + BOX_B_LOG)
/*
* The next three routines implement inverse colormap filling. They could
* all be folded into one big routine, but splitting them up this way saves
* some stack space (the mindist[] and bestdist[] arrays need not coexist)
* and may allow some compilers to produce better code by registerizing more
* inner-loop variables.
*/
static int
find_nearby_colors (QuantizeObj *quantobj,
int minR,
int minG,
int minB,
int colorlist[])
/* Locate the colormap entries close enough to an update box to be candidates
* for the nearest entry to some cell(s) in the update box. The update box
* is specified by the center coordinates of its first cell. The number of
* candidate colormap entries is returned, and their colormap indexes are
* placed in colorlist[].
* This routine uses Heckbert's "locally sorted search" criterion to select
* the colors that need further consideration.
*/
{
int numcolors = quantobj->actual_number_of_colors;
int maxR, maxG, maxB;
int centerR, centerG, centerB;
int i, x, ncolors;
int minmaxdist, min_dist, max_dist, tdist;
int mindist[MAXNUMCOLORS]; /* min distance to colormap entry i */
/* Compute true coordinates of update box's upper corner and center.
* Actually we compute the coordinates of the center of the upper-corner
* histogram cell, which are the upper bounds of the volume we care about.
* Note that since ">>" rounds down, the "center" values may be closer to
* min than to max; hence comparisons to them must be "<=", not "<".
*/
maxR = minR + ((1 << BOX_R_SHIFT) - (1 << R_SHIFT));
centerR = (minR + maxR) >> 1;
maxG = minG + ((1 << BOX_G_SHIFT) - (1 << G_SHIFT));
centerG = (minG + maxG) >> 1;
maxB = minB + ((1 << BOX_B_SHIFT) - (1 << B_SHIFT));
centerB = (minB + maxB) >> 1;
/* For each color in colormap, find:
* 1. its minimum squared-distance to any point in the update box
* (zero if color is within update box);
* 2. its maximum squared-distance to any point in the update box.
* Both of these can be found by considering only the corners of the box.
* We save the minimum distance for each color in mindist[];
* only the smallest maximum distance is of interest.
*/
minmaxdist = 0x7FFFFFFFL;
for (i = 0; i < numcolors; i++) {
/* We compute the squared-R-distance term, then add in the other two. */
x = quantobj->cmap[i].red;
if (x < minR) {
tdist = (x - minR) * R_SCALE;
min_dist = tdist*tdist;
tdist = (x - maxR) * R_SCALE;
max_dist = tdist*tdist;
} else if (x > maxR) {
tdist = (x - maxR) * R_SCALE;
min_dist = tdist*tdist;
tdist = (x - minR) * R_SCALE;
max_dist = tdist*tdist;
} else {
/* within cell range so no contribution to min_dist */
min_dist = 0;
if (x <= centerR) {
tdist = (x - maxR) * R_SCALE;
max_dist = tdist*tdist;
} else {
tdist = (x - minR) * R_SCALE;
max_dist = tdist*tdist;
}
}
x = quantobj->cmap[i].green;
if (x < minG) {
tdist = (x - minG) * G_SCALE;
min_dist += tdist*tdist;
tdist = (x - maxG) * G_SCALE;
max_dist += tdist*tdist;
} else if (x > maxG) {
tdist = (x - maxG) * G_SCALE;
min_dist += tdist*tdist;
tdist = (x - minG) * G_SCALE;
max_dist += tdist*tdist;
} else {
/* within cell range so no contribution to min_dist */
if (x <= centerG) {
tdist = (x - maxG) * G_SCALE;
max_dist += tdist*tdist;
} else {
tdist = (x - minG) * G_SCALE;
max_dist += tdist*tdist;
}
}
x = quantobj->cmap[i].blue;
if (x < minB) {
tdist = (x - minB) * B_SCALE;
min_dist += tdist*tdist;
tdist = (x - maxB) * B_SCALE;
max_dist += tdist*tdist;
} else if (x > maxB) {
tdist = (x - maxB) * B_SCALE;
min_dist += tdist*tdist;
tdist = (x - minB) * B_SCALE;
max_dist += tdist*tdist;
} else {
/* within cell range so no contribution to min_dist */
if (x <= centerB) {
tdist = (x - maxB) * B_SCALE;
max_dist += tdist*tdist;
} else {
tdist = (x - minB) * B_SCALE;
max_dist += tdist*tdist;
}
}
mindist[i] = min_dist; /* save away the results */
if (max_dist < minmaxdist)
minmaxdist = max_dist;
}
/* Now we know that no cell in the update box is more than minmaxdist
* away from some colormap entry. Therefore, only colors that are
* within minmaxdist of some part of the box need be considered.
*/
ncolors = 0;
for (i = 0; i < numcolors; i++) {
if (mindist[i] <= minmaxdist)
colorlist[ncolors++] = i;
}
return ncolors;
}
static void
find_best_colors (QuantizeObj *quantobj,
int minR,
int minG,
int minB,
int numcolors,
int colorlist[],
int bestcolor[])
/* Find the closest colormap entry for each cell in the update box,
* given the list of candidate colors prepared by find_nearby_colors.
* Return the indexes of the closest entries in the bestcolor[] array.
* This routine uses Thomas' incremental distance calculation method to
* find the distance from a colormap entry to successive cells in the box.
*/
{
int iR, iG, iB;
int i, icolor;
int * bptr; /* pointer into bestdist[] array */
int * cptr; /* pointer into bestcolor[] array */
int dist0, dist1; /* initial distance values */
int dist2; /* current distance in inner loop */
int xx0, xx1; /* distance increments */
int xx2;
int inR, inG, inB; /* initial values for increments */
/* This array holds the distance to the nearest-so-far color for each cell */
int bestdist[BOX_R_ELEMS * BOX_G_ELEMS * BOX_B_ELEMS];
/* Initialize best-distance for each cell of the update box */
bptr = bestdist;
for (i = BOX_R_ELEMS*BOX_G_ELEMS*BOX_B_ELEMS-1; i >= 0; i--)
*bptr++ = 0x7FFFFFFFL;
/* For each color selected by find_nearby_colors,
* compute its distance to the center of each cell in the box.
* If that's less than best-so-far, update best distance and color number.
*/
/* Nominal steps between cell centers ("x" in Thomas article) */
#define STEP_R ((1 << R_SHIFT) * R_SCALE)
#define STEP_G ((1 << G_SHIFT) * G_SCALE)
#define STEP_B ((1 << B_SHIFT) * B_SCALE)
for (i = 0; i < numcolors; i++) {
icolor = colorlist[i];
/* Compute (square of) distance from minR/G/B to this color */
inR = (minR - quantobj->cmap[icolor].red) * R_SCALE;
dist0 = inR*inR;
inG = (minG - quantobj->cmap[icolor].green) * G_SCALE;
dist0 += inG*inG;
inB = (minB - quantobj->cmap[icolor].blue) * B_SCALE;
dist0 += inB*inB;
/* Form the initial difference increments */
inR = inR * (2 * STEP_R) + STEP_R * STEP_R;
inG = inG * (2 * STEP_G) + STEP_G * STEP_G;
inB = inB * (2 * STEP_B) + STEP_B * STEP_B;
/* Now loop over all cells in box, updating distance per Thomas method */
bptr = bestdist;
cptr = bestcolor;
xx0 = inR;
for (iR = BOX_R_ELEMS-1; iR >= 0; iR--) {
dist1 = dist0;
xx1 = inG;
for (iG = BOX_G_ELEMS-1; iG >= 0; iG--) {
dist2 = dist1;
xx2 = inB;
for (iB = BOX_B_ELEMS-1; iB >= 0; iB--) {
if (dist2 < *bptr) {
*bptr = dist2;
*cptr = icolor;
}
dist2 += xx2;
xx2 += 2 * STEP_B * STEP_B;
bptr++;
cptr++;
}
dist1 += xx1;
xx1 += 2 * STEP_G * STEP_G;
}
dist0 += xx0;
xx0 += 2 * STEP_R * STEP_R;
}
}
}
static void
fill_inverse_cmap_gray (QuantizeObj *quantobj,
Histogram histogram,
int pixel)
/* Fill the inverse-colormap entries in the update box that contains */
/* histogram cell R/G/B. (Only that one cell MUST be filled, but */
/* we can fill as many others as we wish.) */
{
Color *cmap;
long dist;
long mindist;
int mindisti;
int i;
cmap = quantobj->cmap;
mindist = 65536;
mindisti = -1;
for (i = 0; i < quantobj->actual_number_of_colors; i++)
{
dist = pixel - cmap[i].red;
dist *= dist;
if (dist < mindist)
{
mindist = dist;
mindisti = i;
}
}
if (i >= 0)
histogram[pixel] = mindisti + 1;
}
static void
fill_inverse_cmap_rgb (QuantizeObj *quantobj,
Histogram histogram,
int R,
int G,
int B)
/* Fill the inverse-colormap entries in the update box that contains */
/* histogram cell R/G/B. (Only that one cell MUST be filled, but */
/* we can fill as many others as we wish.) */
{
int minR, minG, minB; /* lower left corner of update box */
int iR, iG, iB;
int * cptr; /* pointer into bestcolor[] array */
ColorFreq * cachep; /* pointer into main cache array */
/* This array lists the candidate colormap indexes. */
int colorlist[MAXNUMCOLORS];
int numcolors; /* number of candidate colors */
/* This array holds the actually closest colormap index for each cell. */
int bestcolor[BOX_R_ELEMS * BOX_G_ELEMS * BOX_B_ELEMS];
/* Convert cell coordinates to update box id */
R >>= BOX_R_LOG;
G >>= BOX_G_LOG;
B >>= BOX_B_LOG;
/* Compute true coordinates of update box's origin corner.
* Actually we compute the coordinates of the center of the corner
* histogram cell, which are the lower bounds of the volume we care about.
*/
minR = (R << BOX_R_SHIFT) + ((1 << R_SHIFT) >> 1);
minG = (G << BOX_G_SHIFT) + ((1 << G_SHIFT) >> 1);
minB = (B << BOX_B_SHIFT) + ((1 << B_SHIFT) >> 1);
/* Determine which colormap entries are close enough to be candidates
* for the nearest entry to some cell in the update box.
*/
numcolors = find_nearby_colors (quantobj, minR, minG, minB, colorlist);
/* Determine the actually nearest colors. */
find_best_colors (quantobj, minR, minG, minB, numcolors, colorlist,
bestcolor);
/* Save the best color numbers (plus 1) in the main cache array */
R <<= BOX_R_LOG; /* convert id back to base cell indexes */
G <<= BOX_G_LOG;
B <<= BOX_B_LOG;
cptr = bestcolor;
for (iR = 0; iR < BOX_R_ELEMS; iR++) {
for (iG = 0; iG < BOX_G_ELEMS; iG++) {
cachep = & histogram[(R+iR)*MR+(G+iG)*MG+B];
for (iB = 0; iB < BOX_B_ELEMS; iB++) {
*cachep++ = (*cptr++) + 1;
}
}
}
}
/* This is pass 1 */
static void
median_cut_pass1_gray (QuantizeObj *quantobj)
{
select_colors_gray (quantobj, quantobj->histogram);
}
static void
median_cut_pass1_rgb (QuantizeObj *quantobj)
{
select_colors_rgb (quantobj, quantobj->histogram);
}
static void
monopal_pass1 (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 (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];
}
}
static void
custompal_pass1 (QuantizeObj *quantobj)
{
int i;
GSList *list;
PaletteEntryP entry;
/* fprintf(stderr, "custompal_pass1: using (theCustomPalette %s) from (file %s)\n",
theCustomPalette->name, theCustomPalette->filename);*/
for (i=0,list=theCustomPalette->colors;
list;
i++,list=g_slist_next(list))
{
entry=(PaletteEntryP)list->data;
quantobj->cmap[i].red = entry->color[0];
quantobj->cmap[i].green = entry->color[1];
quantobj->cmap[i].blue = entry->color[2];
}
quantobj -> actual_number_of_colors = i;
}
/*
* Map some rows of pixels to the output colormapped representation.
*/
static void
median_cut_pass2_no_dither_gray (QuantizeObj *quantobj,
Layer *layer,
TileManager *new_tiles)
{
PixelRegion srcPR, destPR;
Histogram histogram = quantobj->histogram;
ColorFreq * cachep;
unsigned char *src, *dest;
int row, col;
int pixel;
int has_alpha;
void *pr;
zero_histogram_gray (histogram);
has_alpha = layer_has_alpha (layer);
pixel_region_init (&srcPR, GIMP_DRAWABLE(layer)->tiles, 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height, FALSE);
pixel_region_init (&destPR, new_tiles, 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height, TRUE);
for (pr = pixel_regions_register (2, &srcPR, &destPR); pr != NULL; pr = pixel_regions_process (pr))
{
src = srcPR.data;
dest = destPR.data;
for (row = 0; row < srcPR.h; row++)
{
for (col = 0; col < srcPR.w; col++)
{
/* get pixel value and index into the cache */
pixel = src[GRAY_PIX];
cachep = &histogram[pixel];
/* If we have not seen this color before, find nearest colormap entry */
/* and update the cache */
if (*cachep == 0)
fill_inverse_cmap_gray (quantobj, histogram, pixel);
/* Now emit the colormap index for this cell */
dest[INDEXED_PIX] = *cachep - 1;
if (has_alpha)
dest[ALPHA_I_PIX] = (src[ALPHA_G_PIX] > 127) ? 255 : 0;
src += srcPR.bytes;
dest += destPR.bytes;
}
}
}
}
static void
median_cut_pass2_no_dither_rgb (QuantizeObj *quantobj,
Layer *layer,
TileManager *new_tiles)
{
PixelRegion srcPR, destPR;
Histogram histogram = quantobj->histogram;
ColorFreq * cachep;
unsigned char *src, *dest;
int R, G, B;
int row, col;
int has_alpha;
void *pr;
int red_pix = RED_PIX;
int green_pix = GREEN_PIX;
int blue_pix = BLUE_PIX;
int alpha_pix = ALPHA_PIX;
/* In the case of web/mono palettes, we actually force
* grayscale drawables through the rgb pass2 functions
*/
if (drawable_gray (GIMP_DRAWABLE(layer)))
red_pix = green_pix = blue_pix = GRAY_PIX;
zero_histogram_rgb (histogram);
has_alpha = layer_has_alpha (layer);
pixel_region_init (&srcPR, GIMP_DRAWABLE(layer)->tiles, 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height, FALSE);
pixel_region_init (&destPR, new_tiles, 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height, TRUE);
for (pr = pixel_regions_register (2, &srcPR, &destPR); pr != NULL; pr = pixel_regions_process (pr))
{
src = srcPR.data;
dest = destPR.data;
for (row = 0; row < srcPR.h; row++)
{
for (col = 0; col < srcPR.w; col++)
{
/* get pixel value and index into the cache */
R = (src[red_pix]) >> R_SHIFT;
G = (src[green_pix]) >> G_SHIFT;
B = (src[blue_pix]) >> B_SHIFT;
cachep = &histogram[R*MR + G*MG + B];
/* If we have not seen this color before, find nearest colormap entry */
/* and update the cache */
if (*cachep == 0)
fill_inverse_cmap_rgb (quantobj, histogram, R, G, B);
/* Now emit the colormap index for this cell */
dest[INDEXED_PIX] = *cachep - 1;
if (has_alpha)
dest[ALPHA_I_PIX] = (src[alpha_pix] > 127) ? 255 : 0;
src += srcPR.bytes;
dest += destPR.bytes;
}
}
}
}
static void
median_cut_pass2_nodestruct_dither_rgb (QuantizeObj *quantobj,
Layer *layer,
TileManager *new_tiles)
{
PixelRegion srcPR, destPR;
unsigned char *src, *dest;
int row, col;
int has_alpha;
void *pr;
int red_pix = RED_PIX;
int green_pix = GREEN_PIX;
int blue_pix = BLUE_PIX;
int alpha_pix = ALPHA_PIX;
int i;
int lastindex = 0;
int lastred = -1;
int lastgreen = -1;
int lastblue = -1;
has_alpha = layer_has_alpha (layer);
pixel_region_init (&srcPR, GIMP_DRAWABLE(layer)->tiles, 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height, FALSE);
pixel_region_init (&destPR, new_tiles, 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height, TRUE);
for (pr = pixel_regions_register (2, &srcPR, &destPR); pr != NULL; pr = pixel_regions_process (pr))
{
src = srcPR.data;
dest = destPR.data;
for (row = 0; row < srcPR.h; row++)
{
for (col = 0; col < srcPR.w; col++)
{
if ((has_alpha && (src[alpha_pix]>127))
|| !has_alpha)
{
if ((lastred == src[red_pix]) &&
(lastgreen == src[green_pix]) &&
(lastblue == src[blue_pix]))
{
/* same pixel colour as last time */
dest[INDEXED_PIX] = lastindex;
if (has_alpha)
dest[ALPHA_I_PIX] = 255;
}
else
{
for (i = 0 ;
i < quantobj->actual_number_of_colors;
i++)
{
if (
(quantobj->cmap[i].red == src[red_pix]) &&
(quantobj->cmap[i].green == src[green_pix]) &&
(quantobj->cmap[i].blue == src[blue_pix])
)
{
lastred = src[red_pix];
lastgreen = src[green_pix];
lastblue = src[blue_pix];
lastindex = i;
goto got_colour;
}
}
g_error (_("Non-existant colour was expected to "
"be in non-destructive colourmap."));
got_colour:
dest[INDEXED_PIX] = lastindex;
if (has_alpha)
dest[ALPHA_I_PIX] = 255;
}
}
else
{ /* have alpha, and transparent */
dest[ALPHA_I_PIX] = 0;
}
src += srcPR.bytes;
dest += destPR.bytes;
}
}
}
}
/*
* Initialize the error-limiting transfer function (lookup table).
* The raw F-S error computation can potentially compute error values of up to
* +- MAXJSAMPLE. But we want the maximum correction applied to a pixel to be
* much less, otherwise obviously wrong pixels will be created. (Typical
* effects include weird fringes at color-area boundaries, isolated bright
* pixels in a dark area, etc.) The standard advice for avoiding this problem
* is to ensure that the "corners" of the color cube are allocated as output
* colors; then repeated errors in the same direction cannot cause cascading
* error buildup. However, that only prevents the error from getting
* completely out of hand; Aaron Giles reports that error limiting improves
* the results even with corner colors allocated.
* A simple clamping of the error values to about +- MAXJSAMPLE/8 works pretty
* well, but the smoother transfer function used below is even better. Thanks
* to Aaron Giles for this idea.
*/
static int *
init_error_limit (void)
/* Allocate and fill in the error_limiter table */
{
int *table;
int in, out;
table = g_malloc (sizeof (int) * (255 * 2 + 1));
table += 255; /* so we can index -255 ... +255 */
/* #define STEPSIZE 16 */
#define STEPSIZE 200
for (in = 0; in < STEPSIZE; in++)
{
table[in] = in;
table[-in] = -in;
}
for (; in <= 255; in++)
{
table[in] = STEPSIZE;
table[-in] = -STEPSIZE;
}
return (table);
/* Map errors 1:1 up to +- 16 */
out = 0;
for (in = 0; in < STEPSIZE; in++, out++)
{
table[in] = out;
table[-in] = -out;
}
/* Map errors 1:2 up to +- 3*16 */
for (; in < STEPSIZE*3; in++, out += (in&1) ? 0 : 1)
{
table[in] = out;
table[-in] = -out;
}
/* Clamp the rest to final out value (which is 32) */
for (; in <= 255; in++)
{
table[in] = out;
table[-in] = -out;
}
#undef STEPSIZE
return table;
}
/*
* Map some rows of pixels to the output colormapped representation.
* Perform floyd-steinberg dithering.
*/
static void
median_cut_pass2_fs_dither_gray (QuantizeObj *quantobj,
Layer *layer,
TileManager *new_tiles)
{
PixelRegion srcPR, destPR;
Histogram histogram = quantobj->histogram;
ColorFreq *cachep;
Color *color;
int *error_limiter;
short *fs_err1, *fs_err2;
short *fs_err3, *fs_err4;
short *range_limiter;
int src_bytes, dest_bytes;
unsigned char *src, *dest;
unsigned char *src_buf, *dest_buf;
int *next_row, *prev_row;
int *nr, *pr;
int *tmp;
int pixel;
int pixele;
int row, col;
int index;
int step_dest, step_src;
int odd_row;
int has_alpha;
int width, height;
zero_histogram_gray (histogram);
has_alpha = layer_has_alpha (layer);
pixel_region_init (&srcPR, GIMP_DRAWABLE(layer)->tiles, 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height, FALSE);
pixel_region_init (&destPR, new_tiles, 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height, TRUE);
src_bytes = GIMP_DRAWABLE(layer)->bytes;
dest_bytes = new_tiles->bpp;
width = GIMP_DRAWABLE(layer)->width;
height = GIMP_DRAWABLE(layer)->height;
error_limiter = init_error_limit ();
range_limiter = range_array + 256;
src_buf = g_malloc (width * src_bytes);
dest_buf = g_malloc (width * dest_bytes);
next_row = g_malloc (sizeof (int) * (width + 2));
prev_row = g_malloc (sizeof (int) * (width + 2));
memset (prev_row, 0, (width + 2) * sizeof (int));
fs_err1 = floyd_steinberg_error1 + 511;
fs_err2 = floyd_steinberg_error2 + 511;
fs_err3 = floyd_steinberg_error3 + 511;
fs_err4 = floyd_steinberg_error4 + 511;
odd_row = 0;
for (row = 0; row < height; row++)
{
pixel_region_get_row (&srcPR, 0, row, width, src_buf, 1);
src = src_buf;
dest = dest_buf;
nr = next_row;
pr = prev_row + 1;
if (odd_row)
{
step_dest = -dest_bytes;
step_src = -src_bytes;
src += (width * src_bytes) - src_bytes;
dest += (width * dest_bytes) - dest_bytes;
nr += width + 1;
pr += width;
*(nr - 1) = 0;
}
else
{
step_dest = dest_bytes;
step_src = src_bytes;
*(nr + 1) = 0;
}
*nr = 0;
for (col = 0; col < width; col++)
{
pixel = range_limiter[src[GRAY_PIX] + error_limiter[*pr]];
cachep = &histogram[pixel];
/* If we have not seen this color before, find nearest colormap entry */
/* and update the cache */
if (*cachep == 0)
fill_inverse_cmap_gray (quantobj, histogram, pixel);
index = *cachep - 1;
dest[INDEXED_PIX] = index;
if (has_alpha)
dest[ALPHA_I_PIX] = (src[ALPHA_G_PIX] > 127) ? 255 : 0;
color = &quantobj->cmap[index];
pixele = pixel - color->red;
if (odd_row)
{
*(--pr) += fs_err1[pixele];
*nr-- += fs_err2[pixele];
*nr += fs_err3[pixele];
*(nr-1) = fs_err4[pixele];
}
else
{
*(++pr) += fs_err1[pixele];
*nr++ += fs_err2[pixele];
*nr += fs_err3[pixele];
*(nr+1) = fs_err4[pixele];
}
dest += step_dest;
src += step_src;
}
tmp = next_row;
next_row = prev_row;
prev_row = tmp;
odd_row = !odd_row;
pixel_region_set_row (&destPR, 0, row, width, dest_buf);
}
g_free (error_limiter - 255);
g_free (next_row);
g_free (prev_row);
g_free (src_buf);
g_free (dest_buf);
}
static void
median_cut_pass2_fs_dither_rgb (QuantizeObj *quantobj,
Layer *layer,
TileManager *new_tiles)
{
PixelRegion srcPR, destPR;
Histogram histogram = quantobj->histogram;
ColorFreq *cachep;
Color *color;
int *error_limiter;
short *fs_err1, *fs_err2;
short *fs_err3, *fs_err4;
short *range_limiter;
int src_bytes, dest_bytes;
unsigned char *src, *dest;
unsigned char *src_buf, *dest_buf;
int *red_n_row, *red_p_row;
int *grn_n_row, *grn_p_row;
int *blu_n_row, *blu_p_row;
int *rnr, *rpr;
int *gnr, *gpr;
int *bnr, *bpr;
int *tmp;
int r, g, b;
int re, ge, be;
int row, col;
int index;
int step_dest, step_src;
int odd_row;
int has_alpha;
int width, height;
int red_pix = RED_PIX;
int green_pix = GREEN_PIX;
int blue_pix = BLUE_PIX;
int alpha_pix = ALPHA_PIX;
/* In the case of web/mono palettes, we actually force
* grayscale drawables through the rgb pass2 functions
*/
if (drawable_gray (GIMP_DRAWABLE(layer)))
red_pix = green_pix = blue_pix = GRAY_PIX;
zero_histogram_rgb (histogram);
has_alpha = layer_has_alpha (layer);
pixel_region_init (&srcPR, GIMP_DRAWABLE(layer)->tiles, 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height, FALSE);
pixel_region_init (&destPR, new_tiles, 0, 0, GIMP_DRAWABLE(layer)->width, GIMP_DRAWABLE(layer)->height, TRUE);
src_bytes = GIMP_DRAWABLE(layer)->bytes;
dest_bytes = new_tiles->bpp;
width = GIMP_DRAWABLE(layer)->width;
height = GIMP_DRAWABLE(layer)->height;
error_limiter = init_error_limit ();
range_limiter = range_array + 256;
src_buf = g_malloc (width * src_bytes);
dest_buf = g_malloc (width * dest_bytes);
red_n_row = g_malloc (sizeof (int) * (width + 2));
red_p_row = g_malloc (sizeof (int) * (width + 2));
grn_n_row = g_malloc (sizeof (int) * (width + 2));
grn_p_row = g_malloc (sizeof (int) * (width + 2));
blu_n_row = g_malloc (sizeof (int) * (width + 2));
blu_p_row = g_malloc (sizeof (int) * (width + 2));
memset (red_p_row, 0, (width + 2) * sizeof (int));
memset (grn_p_row, 0, (width + 2) * sizeof (int));
memset (blu_p_row, 0, (width + 2) * sizeof (int));
fs_err1 = floyd_steinberg_error1 + 511;
fs_err2 = floyd_steinberg_error2 + 511;
fs_err3 = floyd_steinberg_error3 + 511;
fs_err4 = floyd_steinberg_error4 + 511;
odd_row = 0;
for (row = 0; row < height; row++)
{
pixel_region_get_row (&srcPR, 0, row, width, src_buf, 1);
src = src_buf;
dest = dest_buf;
rnr = red_n_row;
gnr = grn_n_row;
bnr = blu_n_row;
rpr = red_p_row + 1;
gpr = grn_p_row + 1;
bpr = blu_p_row + 1;
if (odd_row)
{
step_dest = -dest_bytes;
step_src = -src_bytes;
src += (width * src_bytes) - src_bytes;
dest += (width * dest_bytes) - dest_bytes;
rnr += width + 1;
gnr += width + 1;
bnr += width + 1;
rpr += width;
gpr += width;
bpr += width;
*(rnr - 1) = *(gnr - 1) = *(bnr - 1) = 0;
}
else
{
step_dest = dest_bytes;
step_src = src_bytes;
*(rnr + 1) = *(gnr + 1) = *(bnr + 1) = 0;
}
*rnr = *gnr = *bnr = 0;
for (col = 0; col < width; col++)
{
r = range_limiter[src[red_pix] + error_limiter[*rpr]];
g = range_limiter[src[green_pix] + error_limiter[*gpr]];
b = range_limiter[src[blue_pix] + error_limiter[*bpr]];
re = r >> R_SHIFT;
ge = g >> G_SHIFT;
be = b >> B_SHIFT;
cachep = &histogram[re*MR + ge*MG + be];
/* If we have not seen this color before, find nearest colormap entry */
/* and update the cache */
if (*cachep == 0)
fill_inverse_cmap_rgb (quantobj, histogram, re, ge, be);
index = *cachep - 1;
dest[INDEXED_PIX] = index;
if (has_alpha)
dest[ALPHA_I_PIX] = (src[alpha_pix] > 127) ? 255 : 0;
/* Abortive initial attempt at alpha-dithering :) */
/* if (has_alpha)
dest[ALPHA_I_PIX] = (src[alpha_pix] <= (random()&255)) ? 0 : 255;*/
color = &quantobj->cmap[index];
re = r - color->red;
ge = g - color->green;
be = b - color->blue;
if (odd_row)
{
*(--rpr) += fs_err1[re];
*(--gpr) += fs_err1[ge];
*(--bpr) += fs_err1[be];
*rnr-- += fs_err2[re];
*gnr-- += fs_err2[ge];
*bnr-- += fs_err2[be];
*rnr += fs_err3[re];
*gnr += fs_err3[ge];
*bnr += fs_err3[be];
*(rnr-1) = fs_err4[re];
*(gnr-1) = fs_err4[ge];
*(bnr-1) = fs_err4[be];
}
else
{
*(++rpr) += fs_err1[re];
*(++gpr) += fs_err1[ge];
*(++bpr) += fs_err1[be];
*rnr++ += fs_err2[re];
*gnr++ += fs_err2[ge];
*bnr++ += fs_err2[be];
*rnr += fs_err3[re];
*gnr += fs_err3[ge];
*bnr += fs_err3[be];
*(rnr+1) = fs_err4[re];
*(gnr+1) = fs_err4[ge];
*(bnr+1) = fs_err4[be];
}
dest += step_dest;
src += step_src;
}
tmp = red_n_row;
red_n_row = red_p_row;
red_p_row = tmp;
tmp = grn_n_row;
grn_n_row = grn_p_row;
grn_p_row = tmp;
tmp = blu_n_row;
blu_n_row = blu_p_row;
blu_p_row = tmp;
odd_row = !odd_row;
pixel_region_set_row (&destPR, 0, row, width, dest_buf);
}
g_free (error_limiter - 255);
g_free (red_n_row);
g_free (red_p_row);
g_free (grn_n_row);
g_free (grn_p_row);
g_free (blu_n_row);
g_free (blu_p_row);
g_free (src_buf);
g_free (dest_buf);
}
static void
delete_median_cut (QuantizeObj *quantobj)
{
g_free (quantobj->histogram);
g_free (quantobj);
}
/**************************************************************/
static QuantizeObj*
initialize_median_cut (int type,
int num_colors,
int dither_type,
int palette_type)
{
QuantizeObj * quantobj;
/* Initialize the data structures */
quantobj = g_malloc (sizeof (QuantizeObj));
if (type == GRAY && palette_type == MAKE_PALETTE)
quantobj->histogram = g_malloc (sizeof (ColorFreq) * 256);
else
quantobj->histogram = g_malloc (sizeof (ColorFreq) *
HIST_R_ELEMS *
HIST_G_ELEMS *
HIST_B_ELEMS);
quantobj->desired_number_of_colors = num_colors;
switch (type)
{
case GRAY:
switch (palette_type)
{
case MAKE_PALETTE:
quantobj->first_pass = median_cut_pass1_gray;
break;
case WEB_PALETTE:
quantobj->first_pass = webpal_pass1;
break;
case CUSTOM_PALETTE:
quantobj->first_pass = custompal_pass1;
needs_quantize=TRUE;
break;
case MONO_PALETTE:
default:
quantobj->first_pass = monopal_pass1;
}
if (palette_type == WEB_PALETTE ||
palette_type == MONO_PALETTE || palette_type == CUSTOM_PALETTE)
switch (dither_type)
{
case NODITHER:
quantobj->second_pass = median_cut_pass2_no_dither_rgb;
break;
case FSDITHER:
quantobj->second_pass = median_cut_pass2_fs_dither_rgb;
break;
}
else
switch (dither_type)
{
case NODITHER:
quantobj->second_pass = median_cut_pass2_no_dither_gray;
break;
case FSDITHER:
quantobj->second_pass = median_cut_pass2_fs_dither_gray;
break;
}
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;
needs_quantize=TRUE;
break;
case CUSTOM_PALETTE:
quantobj->first_pass = custompal_pass1;
needs_quantize=TRUE;
break;
case MONO_PALETTE:
default:
quantobj->first_pass = monopal_pass1;
}
switch (dither_type)
{
case NODITHER:
quantobj->second_pass = median_cut_pass2_no_dither_rgb;
break;
case FSDITHER:
quantobj->second_pass = median_cut_pass2_fs_dither_rgb;
break;
case NODESTRUCTDITHER:
quantobj->second_pass = median_cut_pass2_nodestruct_dither_rgb;
break;
}
break;
}
quantobj->delete_func = delete_median_cut;
return quantobj;
}