/* GDK - The GIMP Drawing Kit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ /* Color Context module * Copyright 1994,1995 John L. Cwikla * Copyright (C) 1997 by Ripley Software Development * Copyright (C) 1997 by Federico Mena (port to Gtk/Gdk) */ /* Copyright 1994,1995 John L. Cwikla * * Permission to use, copy, modify, distribute, and sell this software * and its documentation for any purpose is hereby granted without fee, * provided that the above copyright notice appears in all copies and that * both that copyright notice and this permission notice appear in * supporting documentation, and that the name of John L. Cwikla or * Wolfram Research, Inc not be used in advertising or publicity * pertaining to distribution of the software without specific, written * prior permission. John L. Cwikla and Wolfram Research, Inc make no * representations about the suitability of this software for any * purpose. It is provided "as is" without express or implied warranty. * * John L. Cwikla and Wolfram Research, Inc disclaim all warranties with * regard to this software, including all implied warranties of * merchantability and fitness, in no event shall John L. Cwikla or * Wolfram Research, Inc be liable for any special, indirect or * consequential damages or any damages whatsoever resulting from loss of * use, data or profits, whether in an action of contract, negligence or * other tortious action, arising out of or in connection with the use or * performance of this software. * * Author: * John L. Cwikla * X Programmer * Wolfram Research Inc. * * cwikla@wri.com */ /* * Modified by the GTK+ Team and others 1997-1999. See the AUTHORS * file for a list of people on the GTK+ Team. See the ChangeLog * files for a list of changes. These files are distributed with * GTK+ at ftp://ftp.gtk.org/pub/gtk/. */ #include #include #include #include "gdk.h" #include "gdkprivate.h" #include "gdkx.h" #define MAX_IMAGE_COLORS 256 static guint hash_color (gconstpointer key) { const GdkColor *color = key; return (color->red * 33023 + color->green * 30013 + color->blue * 27011); } static gint compare_colors (gconstpointer a, gconstpointer b) { const GdkColor *aa = a; const GdkColor *bb = b; return ((aa->red == bb->red) && (aa->green == bb->green) && (aa->blue == bb->blue)); } static void free_hash_entry (gpointer key, gpointer value, gpointer user_data) { g_free (key); /* key and value are the same GdkColor */ } static int pixel_sort (const void *a, const void *b) { return ((GdkColor *) a)->pixel - ((GdkColor *) b)->pixel; } /* XXX: This function does an XQueryColors() the hard way, because there is * no corresponding function in Gdk. */ static void my_x_query_colors (GdkColormap *colormap, GdkColor *colors, gint ncolors) { XColor *xcolors; gint i; xcolors = g_new (XColor, ncolors); for (i = 0; i < ncolors; i++) xcolors[i].pixel = colors[i].pixel; XQueryColors (gdk_display, GDK_COLORMAP_XCOLORMAP (colormap), xcolors, ncolors); for (i = 0; i < ncolors; i++) { colors[i].red = xcolors[i].red; colors[i].green = xcolors[i].green; colors[i].blue = xcolors[i].blue; } g_free (xcolors); } static void query_colors (GdkColorContext *cc) { gint i; GdkColorContextPrivate *ccp = (GdkColorContextPrivate *) cc; cc->cmap = g_new (GdkColor, cc->num_colors); for (i = 0; i < cc->num_colors; i++) cc->cmap[i].pixel = cc->clut ? cc->clut[i] : ccp->std_cmap.base_pixel + i; my_x_query_colors (cc->colormap, cc->cmap, cc->num_colors); qsort (cc->cmap, cc->num_colors, sizeof (GdkColor), pixel_sort); } static void init_bw (GdkColorContext *cc) { GdkColor color; g_warning ("init_bw: failed to allocate colors, falling back to black and white"); cc->mode = GDK_CC_MODE_BW; color.red = color.green = color.blue = 0; if (!gdk_color_alloc (cc->colormap, &color)) cc->black_pixel = 0; else cc->black_pixel = color.pixel; color.red = color.green = color.blue = 0xffff; if (!gdk_color_alloc (cc->colormap, &color)) cc->white_pixel = cc->black_pixel ? 0 : 1; else cc->white_pixel = color.pixel; cc->num_colors = 2; } static void init_gray (GdkColorContext *cc) { GdkColorContextPrivate *ccp = (GdkColorContextPrivate *) cc; GdkColor *clrs, *cstart; gint i; gdouble dinc; cc->num_colors = GDK_VISUAL_XVISUAL (cc->visual)->map_entries; cc->clut = g_new (gulong, cc->num_colors); cstart = g_new (GdkColor, cc->num_colors); retrygray: dinc = 65535.0 / (cc->num_colors - 1); clrs = cstart; for (i = 0; i < cc->num_colors; i++) { clrs->red = clrs->green = clrs->blue = dinc * i; if (!gdk_color_alloc (cc->colormap, clrs)) { gdk_colors_free (cc->colormap, cc->clut, i, 0); cc->num_colors /= 2; if (cc->num_colors > 1) goto retrygray; else { g_free (cc->clut); cc->clut = NULL; init_bw (cc); g_free (cstart); return; } } cc->clut[i] = clrs++->pixel; } g_free (cstart); /* XXX: is this the right thing to do? */ ccp->std_cmap.colormap = GDK_COLORMAP_XCOLORMAP (cc->colormap); ccp->std_cmap.base_pixel = 0; ccp->std_cmap.red_max = cc->num_colors - 1; ccp->std_cmap.green_max = 0; ccp->std_cmap.blue_max = 0; ccp->std_cmap.red_mult = 1; ccp->std_cmap.green_mult = 0; ccp->std_cmap.blue_mult = 0; cc->white_pixel = WhitePixel (ccp->xdisplay, gdk_screen); cc->black_pixel = BlackPixel (ccp->xdisplay, gdk_screen); query_colors (cc); cc->mode = GDK_CC_MODE_MY_GRAY; } static void init_color (GdkColorContext *cc) { GdkColorContextPrivate *ccp = (GdkColorContextPrivate *) cc; gint cubeval; cubeval = 1; while ((cubeval * cubeval * cubeval) < GDK_VISUAL_XVISUAL (cc->visual)->map_entries) cubeval++; cubeval--; cc->num_colors = cubeval * cubeval * cubeval; ccp->std_cmap.red_max = cubeval - 1; ccp->std_cmap.green_max = cubeval - 1; ccp->std_cmap.blue_max = cubeval - 1; ccp->std_cmap.red_mult = cubeval * cubeval; ccp->std_cmap.green_mult = cubeval; ccp->std_cmap.blue_mult = 1; ccp->std_cmap.base_pixel = 0; cc->white_pixel = WhitePixel (ccp->xdisplay, gdk_screen); cc->black_pixel = BlackPixel (ccp->xdisplay, gdk_screen); cc->num_colors = DisplayCells (ccp->xdisplay, gdk_screen); /* a CLUT for storing allocated pixel indices */ cc->max_colors = cc->num_colors; cc->clut = g_new (gulong, cc->max_colors); for (cubeval = 0; cubeval < cc->max_colors; cubeval++) cc->clut[cubeval] = cubeval; query_colors (cc); cc->mode = GDK_CC_MODE_STD_CMAP; } static void init_true_color (GdkColorContext *cc) { GdkColorContextPrivate *ccp = (GdkColorContextPrivate *) cc; gulong rmask, gmask, bmask; cc->mode = GDK_CC_MODE_TRUE; /* Red */ rmask = cc->masks.red = cc->visual->red_mask; cc->shifts.red = 0; cc->bits.red = 0; while (!(rmask & 1)) { rmask >>= 1; cc->shifts.red++; } while (rmask & 1) { rmask >>= 1; cc->bits.red++; } /* Green */ gmask = cc->masks.green = cc->visual->green_mask; cc->shifts.green = 0; cc->bits.green = 0; while (!(gmask & 1)) { gmask >>= 1; cc->shifts.green++; } while (gmask & 1) { gmask >>= 1; cc->bits.green++; } /* Blue */ bmask = cc->masks.blue = cc->visual->blue_mask; cc->shifts.blue = 0; cc->bits.blue = 0; while (!(bmask & 1)) { bmask >>= 1; cc->shifts.blue++; } while (bmask & 1) { bmask >>= 1; cc->bits.blue++; } cc->num_colors = (cc->visual->red_mask | cc->visual->green_mask | cc->visual->blue_mask) + 1; cc->white_pixel = WhitePixel (ccp->xdisplay, gdk_screen); cc->black_pixel = BlackPixel (ccp->xdisplay, gdk_screen); } static void init_direct_color (GdkColorContext *cc) { gint n, count; GdkColor *clrs, *cstart; gulong rval, gval, bval; gulong *rtable; gulong *gtable; gulong *btable; gdouble dinc; init_true_color (cc); /* for shift stuff */ rval = cc->visual->red_mask >> cc->shifts.red; gval = cc->visual->green_mask >> cc->shifts.green; bval = cc->visual->blue_mask >> cc->shifts.blue; rtable = g_new (gulong, rval + 1); gtable = g_new (gulong, gval + 1); btable = g_new (gulong, bval + 1); cc->max_entry = MAX (rval, gval); cc->max_entry = MAX (cc->max_entry, bval); cstart = g_new (GdkColor, cc->max_entry + 1); cc->clut = g_new (gulong, cc->max_entry + 1); retrydirect: for (n = 0; n < rval; n++) rtable[n] = rval ? (65535.0 / rval * n) : 0; for (n = 0; n < gval; n++) gtable[n] = gval ? (65535.0 / gval * n) : 0; for (n = 0; n < bval; n++) btable[n] = bval ? (65535.0 / bval * n) : 0; cc->max_entry = MAX (rval, gval); cc->max_entry = MAX (cc->max_entry, bval); count = 0; clrs = cstart; cc->num_colors = (rval + 1) * (gval + 1) * (bval + 1); for (n = 0; n < cc->max_entry; n++) { dinc = (double) n / cc->max_entry; clrs->red = rtable[(int) (dinc * rval)]; clrs->green = gtable[(int) (dinc * gval)]; clrs->blue = btable[(int) (dinc * bval)]; if (gdk_color_alloc (cc->colormap, clrs)) { cc->clut[count++] = clrs->pixel; clrs++; } else { gdk_colors_free (cc->colormap, cc->clut, count, 0); rval >>= 1; gval >>= 1; bval >>= 1; cc->masks.red = (cc->masks.red >> 1) & cc->visual->red_mask; cc->masks.green = (cc->masks.green >> 1) & cc->visual->green_mask; cc->masks.blue = (cc->masks.blue >> 1) & cc->visual->blue_mask; cc->shifts.red++; cc->shifts.green++; cc->shifts.blue++; cc->bits.red--; cc->bits.green--; cc->bits.blue--; cc->num_colors = (rval + 1) * (gval + 1) * (bval + 1); if (cc->num_colors >1) goto retrydirect; else { g_free (cc->clut); cc->clut = NULL; init_bw (cc); break; } } } /* Update allocated color count; original num_colors is max_entry, which * is not necessarily the same as the really allocated number of colors. */ cc->num_colors = count; g_free (rtable); g_free (gtable); g_free (btable); g_free (cstart); } static void init_palette (GdkColorContext *cc) { /* restore correct mode for this cc */ switch (cc->visual->type) { case GDK_VISUAL_STATIC_GRAY: case GDK_VISUAL_GRAYSCALE: if (GDK_VISUAL_XVISUAL (cc->visual)->map_entries == 2) cc->mode = GDK_CC_MODE_BW; else cc->mode = GDK_CC_MODE_MY_GRAY; break; case GDK_VISUAL_TRUE_COLOR: case GDK_VISUAL_DIRECT_COLOR: cc->mode = GDK_CC_MODE_TRUE; break; case GDK_VISUAL_STATIC_COLOR: case GDK_VISUAL_PSEUDO_COLOR: cc->mode = GDK_CC_MODE_STD_CMAP; break; default: cc->mode = GDK_CC_MODE_UNDEFINED; break; } /* previous palette */ if (cc->num_palette) g_free (cc->palette); if (cc->fast_dither) g_free (cc->fast_dither); /* clear hash table if present */ if (cc->color_hash) { /* XXX: quick-and-dirty way to remove everything */ g_hash_table_destroy (cc->color_hash); cc->color_hash = g_hash_table_new (hash_color, compare_colors); } cc->palette = NULL; cc->num_palette = 0; cc->fast_dither = NULL; } GdkColorContext * gdk_color_context_new (GdkVisual *visual, GdkColormap *colormap) { GdkColorContextPrivate *ccp; gint use_private_colormap = FALSE; /* XXX: maybe restore full functionality later? */ GdkColorContext *cc; gint retry_count; GdkColormap *default_colormap; g_assert (visual != NULL); g_assert (colormap != NULL); ccp = g_new (GdkColorContextPrivate, 1); cc = (GdkColorContext *) ccp; ccp->xdisplay = gdk_display; cc->visual = visual; cc->colormap = colormap; cc->clut = NULL; cc->cmap = NULL; cc->mode = GDK_CC_MODE_UNDEFINED; cc->need_to_free_colormap = FALSE; cc->color_hash = NULL; cc->palette = NULL; cc->num_palette = 0; cc->fast_dither = NULL; default_colormap = gdk_colormap_get_system (); retry_count = 0; while (retry_count < 2) { /* Only create a private colormap if the visual found isn't equal * to the default visual and we don't have a private colormap, * -or- if we are instructed to create a private colormap (which * never is the case for XmHTML). */ if (use_private_colormap || ((cc->visual != gdk_visual_get_system ()) /* default visual? */ && (GDK_COLORMAP_XCOLORMAP (colormap) == GDK_COLORMAP_XCOLORMAP (default_colormap)))) { g_warning ("gdk_color_context_new: non-default visual detected, " "using private colormap"); cc->colormap = gdk_colormap_new (cc->visual, FALSE); cc->need_to_free_colormap = (GDK_COLORMAP_XCOLORMAP (colormap) != GDK_COLORMAP_XCOLORMAP (default_colormap)); } switch (visual->type) { case GDK_VISUAL_STATIC_GRAY: case GDK_VISUAL_GRAYSCALE: GDK_NOTE (COLOR_CONTEXT, g_message ("gdk_color_context_new: visual class is %s\n", (visual->type == GDK_VISUAL_STATIC_GRAY) ? "GDK_VISUAL_STATIC_GRAY" : "GDK_VISUAL_GRAYSCALE")); if (GDK_VISUAL_XVISUAL (cc->visual)->map_entries == 2) init_bw (cc); else init_gray (cc); break; case GDK_VISUAL_TRUE_COLOR: /* shifts */ GDK_NOTE (COLOR_CONTEXT, g_message ("gdk_color_context_new: visual class is GDK_VISUAL_TRUE_COLOR\n")); init_true_color (cc); break; case GDK_VISUAL_DIRECT_COLOR: /* shifts and fake CLUT */ GDK_NOTE (COLOR_CONTEXT, g_message ("gdk_color_context_new: visual class is GDK_VISUAL_DIRECT_COLOR\n")); init_direct_color (cc); break; case GDK_VISUAL_STATIC_COLOR: case GDK_VISUAL_PSEUDO_COLOR: GDK_NOTE (COLOR_CONTEXT, g_message ("gdk_color_context_new: visual class is %s\n", (visual->type == GDK_VISUAL_STATIC_COLOR) ? "GDK_VISUAL_STATIC_COLOR" : "GDK_VISUAL_PSEUDO_COLOR")); init_color (cc); break; default: g_assert_not_reached (); } if ((cc->mode == GDK_CC_MODE_BW) && (cc->visual->depth > 1)) { use_private_colormap = TRUE; retry_count++; } else break; } /* no. of colors allocated yet */ cc->num_allocated = 0; GDK_NOTE (COLOR_CONTEXT, g_message ("gdk_color_context_new: screen depth is %i, no. of colors is %i\n", cc->visual->depth, cc->num_colors)); /* check if we need to initialize a hash table */ if ((cc->mode == GDK_CC_MODE_STD_CMAP) || (cc->mode == GDK_CC_MODE_UNDEFINED)) cc->color_hash = g_hash_table_new (hash_color, compare_colors); return (GdkColorContext *) cc; } GdkColorContext * gdk_color_context_new_mono (GdkVisual *visual, GdkColormap *colormap) { GdkColorContextPrivate *ccp; GdkColorContext *cc; g_assert (visual != NULL); g_assert (colormap != NULL); cc = g_new (GdkColorContext, 1); ccp = (GdkColorContextPrivate *) cc; ccp->xdisplay = gdk_display; cc->visual = visual; cc->colormap = colormap; cc->clut = NULL; cc->cmap = NULL; cc->mode = GDK_CC_MODE_UNDEFINED; cc->need_to_free_colormap = FALSE; init_bw (cc); return (GdkColorContext *) cc; } /* This doesn't currently free black/white, hmm... */ void gdk_color_context_free (GdkColorContext *cc) { g_assert (cc != NULL); if ((cc->visual->type == GDK_VISUAL_STATIC_COLOR) || (cc->visual->type == GDK_VISUAL_PSEUDO_COLOR)) { gdk_colors_free (cc->colormap, cc->clut, cc->num_allocated, 0); g_free (cc->clut); } else if (cc->clut != NULL) { gdk_colors_free (cc->colormap, cc->clut, cc->num_colors, 0); g_free (cc->clut); } if (cc->cmap != NULL) g_free (cc->cmap); if (cc->need_to_free_colormap) gdk_colormap_unref (cc->colormap); /* free any palette that has been associated with this GdkColorContext */ init_palette (cc); if (cc->color_hash) { g_hash_table_foreach (cc->color_hash, free_hash_entry, NULL); g_hash_table_destroy (cc->color_hash); } g_free (cc); } gulong gdk_color_context_get_pixel (GdkColorContext *cc, gushort red, gushort green, gushort blue, gint *failed) { GdkColorContextPrivate *ccp = (GdkColorContextPrivate *) cc; g_assert (cc != NULL); g_assert (failed != NULL); *failed = FALSE; switch (cc->mode) { case GDK_CC_MODE_BW: { gdouble value; value = (red / 65535.0 * 0.30 + green / 65535.0 * 0.59 + blue / 65535.0 * 0.11); if (value > 0.5) return cc->white_pixel; return cc->black_pixel; } case GDK_CC_MODE_MY_GRAY: { gulong ired, igreen, iblue; red = red * 0.30 + green * 0.59 + blue * 0.11; green = 0; blue = 0; if ((ired = red * (ccp->std_cmap.red_max + 1) / 0xffff) > ccp->std_cmap.red_max) ired = ccp->std_cmap.red_max; ired *= ccp->std_cmap.red_mult; if ((igreen = green * (ccp->std_cmap.green_max + 1) / 0xffff) > ccp->std_cmap.green_max) igreen = ccp->std_cmap.green_max; igreen *= ccp->std_cmap.green_mult; if ((iblue = blue * (ccp->std_cmap.blue_max + 1) / 0xffff) > ccp->std_cmap.blue_max) iblue = ccp->std_cmap.blue_max; iblue *= ccp->std_cmap.blue_mult; if (cc->clut != NULL) return cc->clut[ccp->std_cmap.base_pixel + ired + igreen + iblue]; return ccp->std_cmap.base_pixel + ired + igreen + iblue; } case GDK_CC_MODE_TRUE: { gulong ired, igreen, iblue; if (cc->clut == NULL) { red >>= 16 - cc->bits.red; green >>= 16 - cc->bits.green; blue >>= 16 - cc->bits.blue; ired = (red << cc->shifts.red) & cc->masks.red; igreen = (green << cc->shifts.green) & cc->masks.green; iblue = (blue << cc->shifts.blue) & cc->masks.blue; return ired | igreen | iblue; } ired = cc->clut[red * cc->max_entry / 65535] & cc->masks.red; igreen = cc->clut[green * cc->max_entry / 65535] & cc->masks.green; iblue = cc->clut[blue * cc->max_entry / 65535] & cc->masks.blue; return ired | igreen | iblue; } case GDK_CC_MODE_PALETTE: return gdk_color_context_get_pixel_from_palette (cc, &red, &green, &blue, failed); case GDK_CC_MODE_STD_CMAP: default: { GdkColor color; GdkColor *result; color.red = red; color.green = green; color.blue = blue; result = g_hash_table_lookup (cc->color_hash, &color); if (!result) { color.red = red; color.green = green; color.blue = blue; color.pixel = 0; if (!gdk_color_alloc (cc->colormap, &color)) *failed = TRUE; else { GdkColor *cnew; /* XXX: the following comment comes directly from * XCC.c. I don't know if it is relevant for * gdk_color_alloc() as it is for XAllocColor() * - Federico */ /* * I can't figure this out entirely, but it *is* possible * that XAllocColor succeeds, even if the number of * allocations we've made exceeds the number of available * colors in the current colormap. And therefore it * might be necessary for us to resize the CLUT. */ if (cc->num_allocated == cc->max_colors) { cc->max_colors *= 2; GDK_NOTE (COLOR_CONTEXT, g_message ("gdk_color_context_get_pixel: " "resizing CLUT to %i entries\n", cc->max_colors)); cc->clut = g_realloc (cc->clut, cc->max_colors * sizeof (gulong)); } /* Key and value are the same color structure */ cnew = g_new (GdkColor, 1); *cnew = color; g_hash_table_insert (cc->color_hash, cnew, cnew); cc->clut[cc->num_allocated] = color.pixel; cc->num_allocated++; return color.pixel; } } return result->pixel; } } } void gdk_color_context_get_pixels (GdkColorContext *cc, gushort *reds, gushort *greens, gushort *blues, gint ncolors, gulong *colors, gint *nallocated) { gint i, k, idx; gint cmapsize, ncols = 0, nopen = 0, counter = 0; gint bad_alloc = FALSE; gint failed[MAX_IMAGE_COLORS], allocated[MAX_IMAGE_COLORS]; GdkColor defs[MAX_IMAGE_COLORS], cmap[MAX_IMAGE_COLORS]; #ifdef G_ENABLE_DEBUG gint exact_col = 0, subst_col = 0, close_col = 0, black_col = 0; #endif g_assert (cc != NULL); g_assert (reds != NULL); g_assert (greens != NULL); g_assert (blues != NULL); g_assert (colors != NULL); g_assert (nallocated != NULL); memset (defs, 0, MAX_IMAGE_COLORS * sizeof (GdkColor)); memset (failed, 0, MAX_IMAGE_COLORS * sizeof (gint)); memset (allocated, 0, MAX_IMAGE_COLORS * sizeof (gint)); /* Will only have a value if used by the progressive image loader */ ncols = *nallocated; *nallocated = 0; /* First allocate all pixels */ for (i = 0; i < ncolors; i++) { /* colors[i] is only zero if the pixel at that location hasn't * been allocated yet. This is a sanity check required for proper * color allocation by the progressive image loader */ if (colors[i] == 0) { defs[i].red = reds[i]; defs[i].green = greens[i]; defs[i].blue = blues[i]; colors[i] = gdk_color_context_get_pixel (cc, reds[i], greens[i], blues[i], &bad_alloc); /* successfully allocated, store it */ if (!bad_alloc) { defs[i].pixel = colors[i]; allocated[ncols++] = colors[i]; } else failed[nopen++] = i; } } *nallocated = ncols; /* all colors available, all done */ if ((ncols == ncolors) || (nopen == 0)) { GDK_NOTE (COLOR_CONTEXT, g_message ("gdk_color_context_get_pixels: got all %i colors; " "(%i colors allocated so far)\n", ncolors, cc->num_allocated)); return; } /* The fun part. We now try to allocate the colors we couldn't allocate * directly. The first step will map a color onto its nearest color * that has been allocated (either by us or someone else). If any colors * remain unallocated, we map these onto the colors that we have allocated * ourselves. */ /* read up to MAX_IMAGE_COLORS colors of the current colormap */ cmapsize = MIN (cc->num_colors, MAX_IMAGE_COLORS); /* see if the colormap has any colors to read */ if (cmapsize < 0) { g_warning ("gdk_color_context_get_pixels: oops! no colors available, " "your images will look *really* ugly."); return; } #ifdef G_ENABLE_DEBUG exact_col = ncols; #endif /* initialize pixels */ for (i = 0; i < cmapsize; i++) { cmap[i].pixel = i; cmap[i].red = cmap[i].green = cmap[i].blue = 0; } /* read the colormap */ my_x_query_colors (cc->colormap, cmap, cmapsize); /* get a close match for any unallocated colors */ counter = nopen; nopen = 0; idx = 0; do { gint d, j, mdist, close, ri, gi, bi; gint rd, gd, bd; i = failed[idx]; mdist = 0x1000000; close = -1; /* Store these vals. Small performance increase as this skips three * indexing operations in the loop code. */ ri = reds[i]; gi = greens[i]; bi = blues[i]; /* Walk all colors in the colormap and see which one is the * closest. Uses plain least squares. */ for (j = 0; (j < cmapsize) && (mdist != 0); j++) { /* Don't replace these by shifts; the sign may get clobbered */ rd = (ri - cmap[j].red) / 256; gd = (gi - cmap[j].green) / 256; bd = (bi - cmap[j].blue) / 256; d = rd * rd + gd * gd + bd * bd; if (d < mdist) { close = j; mdist = d; } } if (close != -1) { rd = cmap[close].red; gd = cmap[close].green; bd = cmap[close].blue; /* allocate */ colors[i] = gdk_color_context_get_pixel (cc, rd, gd, bd, &bad_alloc); /* store */ if (!bad_alloc) { defs[i] = cmap[close]; defs[i].pixel = colors[i]; allocated[ncols++] = colors[i]; #ifdef G_ENABLE_DEBUG close_col++; #endif } else failed[nopen++] = i; } else failed[nopen++] = i; /* deal with in next stage if allocation failed */ } while (++idx < counter); *nallocated = ncols; /* This is the maximum no. of allocated colors. See also the nopen == 0 * note above. */ if ((ncols == ncolors) || (nopen == 0)) { GDK_NOTE (COLOR_CONTEXT, g_message ("gdk_color_context_get_pixels: got %i colors, %i exact and " "%i close (%i colors allocated so far)\n", ncolors, exact_col, close_col, cc->num_allocated)); return; } /* Now map any remaining unallocated pixels into the colors we did get */ idx = 0; do { gint d, mdist, close, ri, gi, bi; gint j, rd, gd, bd; i = failed[idx]; mdist = 0x1000000; close = -1; /* store */ ri = reds[i]; gi = greens[i]; bi = blues[i]; /* search allocated colors */ for (j = 0; (j < ncols) && (mdist != 0); j++) { k = allocated[j]; /* Don't replace these by shifts; the sign may get clobbered */ rd = (ri - defs[k].red) / 256; gd = (gi - defs[k].green) / 256; bd = (bi - defs[k].blue) / 256; d = rd * rd + gd * gd + bd * bd; if (d < mdist) { close = k; mdist = d; } } if (close < 0) { /* too bad, map to black */ defs[i].pixel = cc->black_pixel; defs[i].red = defs[i].green = defs[i].blue = 0; #ifdef G_ENABLE_DEBUG black_col++; #endif } else { defs[i] = defs[close]; #ifdef G_ENABLE_DEBUG subst_col++; #endif } colors[i] = defs[i].pixel; } while (++idx < nopen); GDK_NOTE (COLOR_CONTEXT, g_message ("gdk_color_context_get_pixels: got %i colors, %i exact, %i close, " "%i substituted, %i to black (%i colors allocated so far)\n", ncolors, exact_col, close_col, subst_col, black_col, cc->num_allocated)); } void gdk_color_context_get_pixels_incremental (GdkColorContext *cc, gushort *reds, gushort *greens, gushort *blues, gint ncolors, gint *used, gulong *colors, gint *nallocated) { gint i, k, idx; gint cmapsize, ncols = 0, nopen = 0, counter = 0; gint bad_alloc = FALSE; gint failed[MAX_IMAGE_COLORS], allocated[MAX_IMAGE_COLORS]; GdkColor defs[MAX_IMAGE_COLORS], cmap[MAX_IMAGE_COLORS]; #ifdef G_ENABLE_DEBUG gint exact_col = 0, subst_col = 0, close_col = 0, black_col = 0; #endif g_assert (cc != NULL); g_assert (reds != NULL); g_assert (greens != NULL); g_assert (blues != NULL); g_assert (used != NULL); g_assert (colors != NULL); g_assert (nallocated != NULL); memset (defs, 0, MAX_IMAGE_COLORS * sizeof (GdkColor)); memset (failed, 0, MAX_IMAGE_COLORS * sizeof (gint)); memset (allocated, 0, MAX_IMAGE_COLORS * sizeof (gint)); /* Will only have a value if used by the progressive image loader */ ncols = *nallocated; *nallocated = 0; /* First allocate all pixels */ for (i = 0; i < ncolors; i++) { /* used[i] is only -1 if the pixel at that location hasn't * been allocated yet. This is a sanity check required for proper * color allocation by the progressive image loader. * When colors[i] == 0 it indicates the slot is available for * allocation. */ if (used[i] != FALSE) { if (colors[i] == 0) { defs[i].red = reds[i]; defs[i].green = greens[i]; defs[i].blue = blues[i]; colors[i] = gdk_color_context_get_pixel (cc, reds[i], greens[i], blues[i], &bad_alloc); /* successfully allocated, store it */ if (!bad_alloc) { defs[i].pixel = colors[i]; allocated[ncols++] = colors[i]; } else failed[nopen++] = i; } #ifdef DEBUG else GDK_NOTE (COLOR_CONTEXT, g_message ("gdk_color_context_get_pixels_incremental: " "pixel at slot %i already allocated, skipping\n", i)); #endif } } *nallocated = ncols; if ((ncols == ncolors) || (nopen == 0)) { GDK_NOTE (COLOR_CONTEXT, g_message ("gdk_color_context_get_pixels_incremental: got all %i colors " "(%i colors allocated so far)\n", ncolors, cc->num_allocated)); return; } cmapsize = MIN (cc->num_colors, MAX_IMAGE_COLORS); if (cmapsize < 0) { g_warning ("gdk_color_context_get_pixels_incremental: oops! " "No colors available images will look *really* ugly."); return; } #ifdef G_ENABLE_DEBUG exact_col = ncols; #endif /* initialize pixels */ for (i = 0; i < cmapsize; i++) { cmap[i].pixel = i; cmap[i].red = cmap[i].green = cmap[i].blue = 0; } /* read */ my_x_query_colors (cc->colormap, cmap, cmapsize); /* now match any unallocated colors */ counter = nopen; nopen = 0; idx = 0; do { gint d, j, mdist, close, ri, gi, bi; gint rd, gd, bd; i = failed[idx]; mdist = 0x1000000; close = -1; /* store */ ri = reds[i]; gi = greens[i]; bi = blues[i]; for (j = 0; (j < cmapsize) && (mdist != 0); j++) { /* Don't replace these by shifts; the sign may get clobbered */ rd = (ri - cmap[j].red) / 256; gd = (gi - cmap[j].green) / 256; bd = (bi - cmap[j].blue) / 256; d = rd * rd + gd * gd + bd * bd; if (d < mdist) { close = j; mdist = d; } } if (close != -1) { rd = cmap[close].red; gd = cmap[close].green; bd = cmap[close].blue; /* allocate */ colors[i] = gdk_color_context_get_pixel (cc, rd, gd, bd, &bad_alloc); /* store */ if (!bad_alloc) { defs[i] = cmap[close]; defs[i].pixel = colors[i]; allocated[ncols++] = colors[i]; #ifdef G_ENABLE_DEBUG close_col++; #endif } else failed[nopen++] = i; } else failed[nopen++] = i; /* deal with in next stage if allocation failed */ } while (++idx < counter); *nallocated = ncols; if ((ncols == ncolors) || (nopen == 0)) { GDK_NOTE (COLOR_CONTEXT, g_message ("gdk_color_context_get_pixels_incremental: " "got %i colors, %i exact and %i close " "(%i colors allocated so far)\n", ncolors, exact_col, close_col, cc->num_allocated)); return; } /* map remaining unallocated pixels into colors we did get */ idx = 0; do { gint d, mdist, close, ri, gi, bi; gint j, rd, gd, bd; i = failed[idx]; mdist = 0x1000000; close = -1; ri = reds[i]; gi = greens[i]; bi = blues[i]; /* search allocated colors */ for (j = 0; (j < ncols) && (mdist != 0); j++) { k = allocated[j]; /* downscale */ /* Don't replace these by shifts; the sign may get clobbered */ rd = (ri - defs[k].red) / 256; gd = (gi - defs[k].green) / 256; bd = (bi - defs[k].blue) / 256; d = rd * rd + gd * gd + bd * bd; if (d < mdist) { close = k; mdist = d; } } if (close < 0) { /* too bad, map to black */ defs[i].pixel = cc->black_pixel; defs[i].red = defs[i].green = defs[i].blue = 0; #ifdef G_ENABLE_DEBUG black_col++; #endif } else { defs[i] = defs[close]; #ifdef G_ENABLE_DEBUG subst_col++; #endif } colors[i] = defs[i].pixel; } while (++idx < nopen); GDK_NOTE (COLOR_CONTEXT, g_message ("gdk_color_context_get_pixels_incremental: " "got %i colors, %i exact, %i close, %i substituted, %i to black " "(%i colors allocated so far)\n", ncolors, exact_col, close_col, subst_col, black_col, cc->num_allocated)); } gint gdk_color_context_query_color (GdkColorContext *cc, GdkColor *color) { return gdk_color_context_query_colors (cc, color, 1); } gint gdk_color_context_query_colors (GdkColorContext *cc, GdkColor *colors, gint num_colors) { gint i; GdkColor *tc; g_assert (cc != NULL); g_assert (colors != NULL); switch (cc->mode) { case GDK_CC_MODE_BW: for (i = 0, tc = colors; i < num_colors; i++, tc++) { if (tc->pixel == cc->white_pixel) tc->red = tc->green = tc->blue = 65535; else tc->red = tc->green = tc->blue = 0; } break; case GDK_CC_MODE_TRUE: if (cc->clut == NULL) for (i = 0, tc = colors; i < num_colors; i++, tc++) { tc->red = ((tc->pixel & cc->masks.red) >> cc->shifts.red) << (16 - cc->bits.red); tc->green = ((tc->pixel & cc->masks.green) >> cc->shifts.green) << (16 - cc->bits.green); tc->blue = ((tc->pixel & cc->masks.blue) >> cc->shifts.blue) << (16 - cc->bits.blue); } else { my_x_query_colors (cc->colormap, colors, num_colors); return 1; } break; case GDK_CC_MODE_STD_CMAP: default: if (cc->cmap == NULL) { my_x_query_colors (cc->colormap, colors, num_colors); return 1; } else { gint first, last, half; gulong half_pixel; for (i = 0, tc = colors; i < num_colors; i++) { first = 0; last = cc->num_colors - 1; while (first <= last) { half = (first + last) / 2; half_pixel = cc->cmap[half].pixel; if (tc->pixel == half_pixel) { tc->red = cc->cmap[half].red; tc->green = cc->cmap[half].green; tc->blue = cc->cmap[half].blue; first = last + 1; /* false break */ } else { if (tc->pixel > half_pixel) first = half + 1; else last = half - 1; } } } return 1; } break; } return 1; } gint gdk_color_context_add_palette (GdkColorContext *cc, GdkColor *palette, gint num_palette) { gint i, j, erg; gushort r, g, b; gulong pixel[1]; g_assert (cc != NULL); /* initialize this palette (will also erase previous palette as well) */ init_palette (cc); /* restore previous mode if we aren't adding a new palette */ if (num_palette == 0) { /* GDK_CC_MODE_STD_CMAP uses a hash table, so we'd better initialize one */ /* XXX: here, the hash table is already initialized */ return 0; } /* Initialize a hash table for this palette (we need one for allocating * the pixels in the palette using the current settings) */ if (cc->color_hash == NULL) cc->color_hash = g_hash_table_new (hash_color, compare_colors); /* copy incoming palette */ cc->palette = g_new0(GdkColor, num_palette); j = 0; for (i = 0; i < num_palette; i++) { erg = 0; pixel[0] = 0; /* try to allocate this color */ r = palette[i].red; g = palette[i].green; b = palette[i].blue; gdk_color_context_get_pixels (cc, &r, &g, &b, 1, pixel, &erg); /* only store if we succeed */ if (erg) { /* store in palette */ cc->palette[j].red = r; cc->palette[j].green = g; cc->palette[j].blue = b; cc->palette[j].pixel = pixel[0]; /* move to next slot */ j++; } } /* resize to fit */ if (j != num_palette) cc->palette = g_realloc (cc->palette, j * sizeof (GdkColor)); /* clear the hash table, we don't use it when dithering */ if (cc->color_hash) { g_hash_table_destroy (cc->color_hash); cc->color_hash = NULL; } /* store real palette size */ cc->num_palette = j; /* switch to palette mode */ cc->mode = GDK_CC_MODE_PALETTE; /* sort palette */ qsort (cc->palette, cc->num_palette, sizeof (GdkColor), pixel_sort); cc->fast_dither = NULL; return j; } void gdk_color_context_init_dither (GdkColorContext *cc) { gint rr, gg, bb, err, erg, erb; gint success = FALSE; g_assert (cc != NULL); /* now we can initialize the fast dither matrix */ if (cc->fast_dither == NULL) cc->fast_dither = g_new (GdkColorContextDither, 1); /* Fill it. We ignore unsuccessful allocations, they are just mapped * to black instead */ for (rr = 0; rr < 32; rr++) for (gg = 0; gg < 32; gg++) for (bb = 0; bb < 32; bb++) { err = (rr << 3) | (rr >> 2); erg = (gg << 3) | (gg >> 2); erb = (bb << 3) | (bb >> 2); cc->fast_dither->fast_rgb[rr][gg][bb] = gdk_color_context_get_index_from_palette (cc, &err, &erg, &erb, &success); cc->fast_dither->fast_err[rr][gg][bb] = err; cc->fast_dither->fast_erg[rr][gg][bb] = erg; cc->fast_dither->fast_erb[rr][gg][bb] = erb; } } void gdk_color_context_free_dither (GdkColorContext *cc) { g_assert (cc != NULL); if (cc->fast_dither) g_free (cc->fast_dither); cc->fast_dither = NULL; } gulong gdk_color_context_get_pixel_from_palette (GdkColorContext *cc, gushort *red, gushort *green, gushort *blue, gint *failed) { gulong pixel = 0; gint dif, dr, dg, db, j = -1; gint mindif = 0x7fffffff; gint err = 0, erg = 0, erb = 0; gint i; g_assert (cc != NULL); g_assert (red != NULL); g_assert (green != NULL); g_assert (blue != NULL); g_assert (failed != NULL); *failed = FALSE; for (i = 0; i < cc->num_palette; i++) { dr = *red - cc->palette[i].red; dg = *green - cc->palette[i].green; db = *blue - cc->palette[i].blue; dif = dr * dr + dg * dg + db * db; if (dif < mindif) { mindif = dif; j = i; pixel = cc->palette[i].pixel; err = dr; erg = dg; erb = db; if (mindif == 0) break; } } /* we failed to map onto a color */ if (j == -1) *failed = TRUE; else { *red = ABS (err); *green = ABS (erg); *blue = ABS (erb); } return pixel; } guchar gdk_color_context_get_index_from_palette (GdkColorContext *cc, gint *red, gint *green, gint *blue, gint *failed) { gint dif, dr, dg, db, j = -1; gint mindif = 0x7fffffff; gint err = 0, erg = 0, erb = 0; gint i; g_assert (cc != NULL); g_assert (red != NULL); g_assert (green != NULL); g_assert (blue != NULL); g_assert (failed != NULL); *failed = FALSE; for (i = 0; i < cc->num_palette; i++) { dr = *red - cc->palette[i].red; dg = *green - cc->palette[i].green; db = *blue - cc->palette[i].blue; dif = dr * dr + dg * dg + db * db; if (dif < mindif) { mindif = dif; j = i; err = dr; erg = dg; erb = db; if (mindif == 0) break; } } /* we failed to map onto a color */ if (j == -1) { *failed = TRUE; j = 0; } else { /* return error fractions */ *red = err; *green = erg; *blue = erb; } return j; }