gtk/gdk/gdkcc.c

1699 lines
37 KiB
C
Raw Normal View History

/* 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., 675 Mass Ave, Cambridge, MA 02139, 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
*/
/* NOTES:
*
* - When a CC is destroyed, remember to destroy the hash table properly.
*/
#include <X11/Xlib.h>
#include <stdlib.h>
#include <string.h>
#include "gdk.h"
#include "gdkprivate.h"
#include "gdkx.h"
#define MAX_IMAGE_COLORS 256
static guint
hash_color(gpointer key)
{
GdkColor *color = key;
return (color->red * 33023 + color->green * 30013 + color->blue * 27011);
}
static gint
compare_colors(gpointer a, gpointer b)
{
GdkColor *aa = a;
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(GdkColorContextPrivate *cc)
{
gint i;
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] : cc->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(GdkColorContextPrivate *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(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? */
cc->std_cmap.colormap = GDK_COLORMAP_XCOLORMAP(cc->colormap);
cc->std_cmap.base_pixel = 0;
cc->std_cmap.red_max = cc->num_colors - 1;
cc->std_cmap.green_max = 0;
cc->std_cmap.blue_max = 0;
cc->std_cmap.red_mult = 1;
cc->std_cmap.green_mult = 0;
cc->std_cmap.blue_mult = 0;
cc->white_pixel = WhitePixel(cc->xdisplay, gdk_screen);
cc->black_pixel = BlackPixel(cc->xdisplay, gdk_screen);
query_colors(cc);
cc->mode = GDK_CC_MODE_MY_GRAY;
}
static void
init_color(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;
cc->std_cmap.red_max = cubeval - 1;
cc->std_cmap.green_max = cubeval - 1;
cc->std_cmap.blue_max = cubeval - 1;
cc->std_cmap.red_mult = cubeval * cubeval;
cc->std_cmap.green_mult = cubeval;
cc->std_cmap.blue_mult = 1;
cc->std_cmap.base_pixel = 0;
cc->white_pixel = WhitePixel(cc->xdisplay, gdk_screen);
cc->black_pixel = BlackPixel(cc->xdisplay, gdk_screen);
cc->num_colors = DisplayCells(cc->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(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(cc->xdisplay, gdk_screen);
cc->black_pixel = BlackPixel(cc->xdisplay, gdk_screen);
}
static void
init_direct_color(GdkColorContextPrivate *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(GdkColorContextPrivate *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)
{
gint use_private_colormap = FALSE; /* XXX: maybe restore full functionality later? */
GdkColorContextPrivate *cc;
gint retry_count;
GdkColormap *default_colormap;
g_assert(visual != NULL);
g_assert(colormap != NULL);
cc = g_new(GdkColorContextPrivate, 1);
cc->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:
if (gdk_debug_level >= 1)
g_print("gdk_color_context_new: visual class is %s",
(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 */
if (gdk_debug_level >= 1)
g_print("gdk_color_context_new: visual class is "
"GDK_VISUAL_TRUE_COLOR");
init_true_color(cc);
break;
case GDK_VISUAL_DIRECT_COLOR: /* shifts and fake CLUT */
if (gdk_debug_level >= 1)
g_print("gdk_color_context_new: visual class is "
"GDK_VISUAL_DIRECT_COLOR");
init_direct_color(cc);
break;
case GDK_VISUAL_STATIC_COLOR:
case GDK_VISUAL_PSEUDO_COLOR:
if (gdk_debug_level >= 1)
g_print("gdk_color_context_new: visual class is %s",
(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;
if (gdk_debug_level >= 1)
g_print("gdk_color_context_new: screen depth is %i, no. of colors is %i",
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 *cc;
g_assert(visual != NULL);
g_assert(colormap != NULL);
cc = g_new(GdkColorContextPrivate, 1);
cc->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)
{
GdkColorContextPrivate *ccp;
g_assert(cc != NULL);
ccp = (GdkColorContextPrivate *) cc;
if ((ccp->visual->type == GDK_VISUAL_STATIC_COLOR)
|| (ccp->visual->type == GDK_VISUAL_PSEUDO_COLOR)) {
gdk_colors_free(ccp->colormap, ccp->clut, ccp->num_allocated, 0);
g_free(ccp->clut);
} else if (ccp->clut != NULL) {
gdk_colors_free(ccp->colormap, ccp->clut, ccp->num_colors, 0);
g_free(ccp->clut);
}
if (ccp->cmap != NULL)
g_free(ccp->cmap);
if (ccp->need_to_free_colormap)
gdk_colormap_destroy(ccp->colormap);
/* free any palette that has been associated with this GdkColorContext */
init_palette(ccp);
if (ccp->color_hash) {
g_hash_table_foreach(ccp->color_hash,
free_hash_entry,
NULL);
g_hash_table_destroy(ccp->color_hash);
}
g_free(cc);
}
gulong
gdk_color_context_get_pixel(GdkColorContext *cc,
gushort red,
gushort green,
gushort blue,
gint *failed)
{
GdkColorContextPrivate *ccp;
g_assert(cc != NULL);
g_assert(failed != NULL);
ccp = (GdkColorContextPrivate *) cc;
*failed = FALSE;
switch (ccp->mode) {
case GDK_CC_MODE_BW: {
gdouble value;
red <<= 8;
green <<= 8;
blue <<= 8;
value = red / 65535.0 * 0.30
+ green / 65535.0 * 0.59
+ blue / 65535.0 * 0.11;
if (value > 0.5)
return ccp->white_pixel;
return ccp->black_pixel;
}
case GDK_CC_MODE_MY_GRAY: {
gulong ired, igreen, iblue;
red <<= 8;
green <<= 8;
blue <<= 8;
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 (ccp->clut != NULL)
return ccp->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;
red <<= 8;
green <<= 8;
blue <<= 8;
if (ccp->clut == NULL) {
red >>= 16 - ccp->bits.red;
green >>= 16 - ccp->bits.green;
blue >>= 16 - ccp->bits.blue;
ired = (red << ccp->shifts.red) & ccp->masks.red;
igreen = (green << ccp->shifts.green) & ccp->masks.green;
iblue = (blue << ccp->shifts.blue) & ccp->masks.blue;
return ired | igreen | iblue;
}
ired = ccp->clut[red * ccp->max_entry / 65535] & ccp->masks.red;
igreen = ccp->clut[green * ccp->max_entry / 65535] & ccp->masks.green;
iblue = ccp->clut[blue * ccp->max_entry / 65535] & ccp->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;
red <<= 8;
green <<= 8;
blue <<= 8;
color.red = red;
color.green = green;
color.blue = blue;
result = g_hash_table_lookup(ccp->color_hash, &color);
if (!result) {
color.red = red;
color.green = green;
color.blue = blue;
color.pixel = 0;
if (!gdk_color_alloc(ccp->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 (ccp->num_allocated == ccp->max_colors) {
ccp->max_colors *= 2;
if (gdk_debug_level >= 1)
g_print("gdk_color_context_get_pixel: "
"resizing CLUT to %i entries",
ccp->max_colors);
ccp->clut = g_realloc(ccp->clut,
ccp->max_colors * sizeof(gulong));
}
/* Key and value are the same color structure */
cnew = g_new(GdkColor, 1);
*cnew = color;
g_hash_table_insert(ccp->color_hash, cnew, cnew);
ccp->clut[ccp->num_allocated] = color.pixel;
ccp->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)
{
GdkColorContextPrivate *ccp;
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];
gint exact_col = 0, subst_col = 0, close_col = 0, black_col = 0;
g_assert(cc != NULL);
g_assert(reds != NULL);
g_assert(greens != NULL);
g_assert(blues != NULL);
g_assert(colors != NULL);
g_assert(nallocated != NULL);
ccp = (GdkColorContextPrivate *) cc;
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)) {
if (gdk_debug_level >= 1)
g_print("gdk_color_context_get_pixels: got all %i colors; "
"(%i colors allocated so far", ncolors, ccp->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(ccp->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 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(ccp->colormap, cmap, cmapsize);
/* speedup: downscale here instead of in the matching code */
for (i = 0; i < cmapsize; i++) {
cmap[i].red >>= 8;
cmap[i].green >>= 8;
cmap[i].blue >>= 8;
}
/* 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 = 1000000;
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++) {
rd = ri - cmap[j].red;
gd = gi - cmap[j].green;
bd = bi - cmap[j].blue;
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 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)) {
if (gdk_debug_level >= 1)
g_print("gdk_color_context_get_pixels: got %i colors, %i exact and "
"%i close (%i colors allocated so far)",
ncolors, exact_col, close_col, ccp->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 = 1000000;
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];
rd = ri - defs[k].red;
gd = gi - defs[k].green;
bd = bi - defs[k].blue;
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 = ccp->black_pixel;
defs[i].red = defs[i].green = defs[i].blue = 0;
#ifdef DEBUG
black_col++;
#endif
} else {
defs[i] = defs[close];
#ifdef DEBUG
subst_col++;
#endif
}
colors[i] = defs[i].pixel;
} while (++idx < nopen);
if (gdk_debug_level >= 1)
g_print("gdk_color_context_get_pixels: got %i colors, %i exact, %i close, "
"%i substituted, %i to black (%i colors allocated so far)",
ncolors, exact_col, close_col, subst_col, black_col, ccp->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)
{
GdkColorContextPrivate *ccp;
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];
gint exact_col = 0, subst_col = 0, close_col = 0, black_col = 0;
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);
ccp = (GdkColorContextPrivate *) cc;
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
if (gdk_debug_level >= 1)
g_print("gdk_color_context_get_pixels_incremental: "
"pixel at slot %i already allocated, skipping", i);
#endif
}
}
*nallocated = ncols;
if ((ncols == ncolors) || (nopen == 0)) {
if (gdk_debug_level >= 1)
g_print("gdk_color_context_get_pixels_incremental: got all %i colors "
"(%i colors allocated so far)",
ncolors, ccp->num_allocated);
return;
}
cmapsize = MIN(ccp->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 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 and downscale */
my_x_query_colors(ccp->colormap, cmap, cmapsize);
for (i = 0; i < cmapsize; i++) {
cmap[i].red >>= 8;
cmap[i].green >>= 8;
cmap[i].blue >>= 8;
}
/* 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 = 1000000;
close = -1;
/* store */
ri = reds[i];
gi = greens[i];
bi = blues[i];
for (j = 0; (j < cmapsize) && (mdist != 0); j++) {
rd = ri - cmap[j].red;
gd = gi - cmap[j].green;
bd = bi - cmap[j].blue;
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 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)) {
if (gdk_debug_level >= 1)
g_print("gdk_color_context_get_pixels_incremental: "
"got %i colors, %i exact and %i close "
"(%i colors allocated so far)",
ncolors, exact_col, close_col, ccp->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 = 1000000;
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 */
rd = ri - defs[k].red;
gd = gi - defs[k].green;
bd = bi - defs[k].blue;
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 = ccp->black_pixel;
defs[i].red = defs[i].green = defs[i].blue = 0;
#ifdef DEBUG
black_col++;
#endif
} else {
defs[i] = defs[close];
#ifdef DEBUG
subst_col++;
#endif
}
colors[i] = defs[i].pixel;
} while (++idx < nopen);
if (gdk_debug_level >= 1)
g_print("gdk_color_context_get_pixels_incremental: "
"got %i colors, %i exact, %i close, %i substituted, %i to black "
"(%i colors allocated so far)",
ncolors, exact_col, close_col, subst_col, black_col, ccp->num_allocated);
}
gint
gdk_color_context_get_num_colors(GdkColorContext *cc)
{
g_assert(cc != NULL);
return ((GdkColorContextPrivate *) cc)->num_colors;
}
gint
gdk_color_context_get_depth (GdkColorContext *cc)
{
GdkColorContextPrivate *ccp = (GdkColorContextPrivate *) cc;
g_assert (cc != NULL);
return ccp->visual->depth;
}
GdkColorContextMode
gdk_color_context_get_mode (GdkColorContext *cc)
{
g_assert (cc != NULL);
return ((GdkColorContextPrivate *) cc)->mode;
}
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)
{
GdkColorContextPrivate *ccp;
gint i;
GdkColor *tc;
g_assert(cc != NULL);
g_assert(colors != NULL);
ccp = (GdkColorContextPrivate *) cc;
switch (ccp->mode) {
case GDK_CC_MODE_BW:
for (i = 0, tc = colors; i < num_colors; i++, tc++) {
if (tc->pixel == ccp->white_pixel)
tc->red = tc->green = tc->blue = 65535;
else
tc->red = tc->green = tc->blue = 0;
}
break;
case GDK_CC_MODE_TRUE:
if (ccp->clut == NULL)
for (i = 0, tc = colors; i < num_colors; i++, tc++) {
tc->red = (tc->pixel & ccp->masks.red) * 65535 / ccp->masks.red;
tc->green = (tc->pixel & ccp->masks.green) * 65535 / ccp->masks.green;
tc->blue = (tc->pixel & ccp->masks.blue) * 65535 / ccp->masks.blue;
}
else {
my_x_query_colors(ccp->colormap, colors, num_colors);
return 1;
}
break;
case GDK_CC_MODE_STD_CMAP:
default:
if (ccp->cmap == NULL) {
my_x_query_colors(ccp->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 = ccp->num_colors - 1;
while (first <= last) {
half = (first + last) / 2;
half_pixel = ccp->cmap[half].pixel;
if (tc->pixel == half_pixel) {
tc->red = ccp->cmap[half].red;
tc->green = ccp->cmap[half].green;
tc->blue = ccp->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)
{
GdkColorContextPrivate *ccp;
gint i, j, erg;
gushort r, g, b;
gulong pixel[1];
g_assert(cc != NULL);
ccp = (GdkColorContextPrivate *) cc;
/* initialize this palette (will also erase previous palette as well) */
init_palette(ccp);
/* 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 (ccp->color_hash == NULL)
ccp->color_hash = g_hash_table_new(hash_color, compare_colors);
/* copy incoming palette */
ccp->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 */
ccp->palette[j].red = r;
ccp->palette[j].green = g;
ccp->palette[j].blue = b;
ccp->palette[j].pixel = pixel[0];
/* move to next slot */
j++;
}
}
/* resize to fit */
if (j != num_palette)
ccp->palette = g_realloc(ccp->palette, j * sizeof(GdkColor));
/* clear the hash table, we don't use it when dithering */
if (ccp->color_hash) {
g_hash_table_destroy(ccp->color_hash);
ccp->color_hash = NULL;
}
/* store real palette size */
ccp->num_palette = j;
/* switch to palette mode */
ccp->mode = GDK_CC_MODE_PALETTE;
/* sort palette */
qsort(ccp->palette, ccp->num_palette, sizeof(GdkColor), pixel_sort);
ccp->fast_dither = NULL;
return j;
}
void
gdk_color_context_init_dither(GdkColorContext *cc)
{
GdkColorContextPrivate *ccp;
gint rr, gg, bb, err, erg, erb;
gint success = FALSE;
g_assert(cc != NULL);
ccp = (GdkColorContextPrivate *) cc;
/* now we can initialize the fast dither matrix */
if(ccp->fast_dither == NULL)
ccp->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);
ccp->fast_dither->fast_rgb[rr][gg][bb] =
gdk_color_context_get_index_from_palette(cc, &err, &erg, &erb, &success);
ccp->fast_dither->fast_err[rr][gg][bb] = err;
ccp->fast_dither->fast_erg[rr][gg][bb] = erg;
ccp->fast_dither->fast_erb[rr][gg][bb] = erb;
}
}
void
gdk_color_context_free_dither(GdkColorContext *cc)
{
GdkColorContextPrivate *ccp;
g_assert(cc != NULL);
ccp = (GdkColorContextPrivate *) cc;
if (ccp->fast_dither)
g_free(ccp->fast_dither);
ccp->fast_dither = NULL;
}
gulong
gdk_color_context_get_pixel_from_palette(GdkColorContext *cc,
gushort *red,
gushort *green,
gushort *blue,
gint *failed)
{
GdkColorContextPrivate *ccp;
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);
ccp = (GdkColorContextPrivate *) cc;
*failed = FALSE;
for (i = 0; i < ccp->num_palette; i++) {
dr = *red - ccp->palette[i].red;
dg = *green - ccp->palette[i].green;
db = *blue - ccp->palette[i].blue;
dif = dr * dr + dg * dg + db * db;
if (dif < mindif) {
mindif = dif;
j = i;
pixel = ccp->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)
{
GdkColorContextPrivate *ccp;
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);
ccp = (GdkColorContextPrivate *) cc;
*failed = FALSE;
for (i = 0; i < ccp->num_palette; i++) {
dr = *red - ccp->palette[i].red;
dg = *green - ccp->palette[i].green;
db = *blue - ccp->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;
}