/**************************************************************************** | |
** | |
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). | |
** All rights reserved. | |
** Contact: Nokia Corporation (qt-info@nokia.com) | |
** | |
** This file is part of the QtGui module of the Qt Toolkit. | |
** | |
** $QT_BEGIN_LICENSE:LGPL$ | |
** GNU Lesser General Public License Usage | |
** This file may be used under the terms of the GNU Lesser General Public | |
** License version 2.1 as published by the Free Software Foundation and | |
** appearing in the file LICENSE.LGPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU Lesser | |
** General Public License version 2.1 requirements will be met: | |
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. | |
** | |
** In addition, as a special exception, Nokia gives you certain additional | |
** rights. These rights are described in the Nokia Qt LGPL Exception | |
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. | |
** | |
** GNU General Public License Usage | |
** Alternatively, this file may be used under the terms of the GNU General | |
** Public License version 3.0 as published by the Free Software Foundation | |
** and appearing in the file LICENSE.GPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU General | |
** Public License version 3.0 requirements will be met: | |
** http://www.gnu.org/copyleft/gpl.html. | |
** | |
** Other Usage | |
** Alternatively, this file may be used in accordance with the terms and | |
** conditions contained in a signed written agreement between you and Nokia. | |
** | |
** | |
** | |
** | |
** | |
** $QT_END_LICENSE$ | |
** | |
****************************************************************************/ | |
#include "qcolormap.h" | |
#include "qapplication.h" | |
#include "qdebug.h" | |
#include "qdesktopwidget.h" | |
#include "qvarlengtharray.h" | |
#include "qx11info_x11.h" | |
#include <private/qt_x11_p.h> | |
#include <limits.h> | |
QT_BEGIN_NAMESPACE | |
class QColormapPrivate | |
{ | |
public: | |
QColormapPrivate() | |
: ref(1), mode(QColormap::Direct), depth(0), | |
colormap(0), defaultColormap(true), | |
visual(0), defaultVisual(true), | |
r_max(0), g_max(0), b_max(0), | |
r_shift(0), g_shift(0), b_shift(0) | |
{} | |
QAtomicInt ref; | |
QColormap::Mode mode; | |
int depth; | |
Colormap colormap; | |
bool defaultColormap; | |
Visual *visual; | |
bool defaultVisual; | |
int r_max; | |
int g_max; | |
int b_max; | |
uint r_shift; | |
uint g_shift; | |
uint b_shift; | |
QVector<QColor> colors; | |
QVector<int> pixels; | |
}; | |
static uint right_align(uint v) | |
{ | |
while (!(v & 0x1)) | |
v >>= 1; | |
return v; | |
} | |
static int lowest_bit(uint v) | |
{ | |
int i; | |
uint b = 1u; | |
for (i = 0; ((v & b) == 0u) && i < 32; ++i) | |
b <<= 1u; | |
return i == 32 ? -1 : i; | |
} | |
static int cube_root(int v) | |
{ | |
if (v == 1) | |
return 1; | |
// brute force algorithm | |
int i = 1; | |
for (;;) { | |
const int b = i * i * i; | |
if (b <= v) { | |
++i; | |
} else { | |
--i; | |
break; | |
} | |
} | |
return i; | |
} | |
static Visual *find_visual(Display *display, | |
int screen, | |
int visual_class, | |
int visual_id, | |
int *depth, | |
bool *defaultVisual) | |
{ | |
XVisualInfo *vi, rvi; | |
int count; | |
uint mask = VisualScreenMask; | |
rvi.screen = screen; | |
if (visual_class != -1) { | |
rvi.c_class = visual_class; | |
mask |= VisualClassMask; | |
} | |
if (visual_id != -1) { | |
rvi.visualid = visual_id; | |
mask |= VisualIDMask; | |
} | |
Visual *visual = DefaultVisual(display, screen); | |
*defaultVisual = true; | |
*depth = DefaultDepth(display, screen); | |
vi = XGetVisualInfo(display, mask, &rvi, &count); | |
if (vi) { | |
int best = 0; | |
for (int x = 0; x < count; ++x) { | |
if (vi[x].depth > vi[best].depth) | |
best = x; | |
} | |
if (best >= 0 && best <= count && vi[best].visualid != XVisualIDFromVisual(visual)) { | |
visual = vi[best].visual; | |
*defaultVisual = (visual == DefaultVisual(display, screen)); | |
*depth = vi[best].depth; | |
} | |
} | |
if (vi) | |
XFree((char *)vi); | |
return visual; | |
} | |
static void query_colormap(QColormapPrivate *d, int screen) | |
{ | |
Display *display = QX11Info::display(); | |
// query existing colormap | |
int q_colors = (((1u << d->depth) > 256u) ? 256u : (1u << d->depth)); | |
XColor queried[256]; | |
memset(queried, 0, sizeof(queried)); | |
for (int x = 0; x < q_colors; ++x) | |
queried[x].pixel = x; | |
XQueryColors(display, d->colormap, queried, q_colors); | |
d->colors.resize(q_colors); | |
for (int x = 0; x < q_colors; ++x) { | |
if (queried[x].red == 0 | |
&& queried[x].green == 0 | |
&& queried[x].blue == 0 | |
&& queried[x].pixel != BlackPixel(display, screen)) { | |
// unallocated color cell, skip it | |
continue; | |
} | |
d->colors[x] = QColor::fromRgbF(queried[x].red / float(USHRT_MAX), | |
queried[x].green / float(USHRT_MAX), | |
queried[x].blue / float(USHRT_MAX)); | |
} | |
// for missing colors, find the closest color in the existing colormap | |
Q_ASSERT(d->pixels.size()); | |
for (int x = 0; x < d->pixels.size(); ++x) { | |
if (d->pixels.at(x) != -1) | |
continue; | |
QRgb rgb; | |
if (d->mode == QColormap::Indexed) { | |
const int r = (x / (d->g_max * d->b_max)) % d->r_max; | |
const int g = (x / d->b_max) % d->g_max; | |
const int b = x % d->b_max; | |
rgb = qRgb((r * 0xff + (d->r_max - 1) / 2) / (d->r_max - 1), | |
(g * 0xff + (d->g_max - 1) / 2) / (d->g_max - 1), | |
(b * 0xff + (d->b_max - 1) / 2) / (d->b_max - 1)); | |
} else { | |
rgb = qRgb(x, x, x); | |
} | |
// find closest color | |
int mindist = INT_MAX, best = -1; | |
for (int y = 0; y < q_colors; ++y) { | |
int r = qRed(rgb) - (queried[y].red >> 8); | |
int g = qGreen(rgb) - (queried[y].green >> 8); | |
int b = qBlue(rgb) - (queried[y].blue >> 8); | |
int dist = (r * r) + (g * g) + (b * b); | |
if (dist < mindist) { | |
mindist = dist; | |
best = y; | |
} | |
} | |
Q_ASSERT(best >= 0 && best < q_colors); | |
if (d->visual->c_class & 1) { | |
XColor xcolor; | |
xcolor.red = queried[best].red; | |
xcolor.green = queried[best].green; | |
xcolor.blue = queried[best].blue; | |
xcolor.pixel = queried[best].pixel; | |
if (XAllocColor(display, d->colormap, &xcolor)) { | |
d->pixels[x] = xcolor.pixel; | |
} else { | |
// some weird stuff is going on... | |
d->pixels[x] = (qGray(rgb) < 127 | |
? BlackPixel(display, screen) | |
: WhitePixel(display, screen)); | |
} | |
} else { | |
d->pixels[x] = best; | |
} | |
} | |
} | |
static void init_gray(QColormapPrivate *d, int screen) | |
{ | |
d->pixels.resize(d->r_max); | |
for (int g = 0; g < d->g_max; ++g) { | |
const int gray = (g * 0xff + (d->r_max - 1) / 2) / (d->r_max - 1); | |
const QRgb rgb = qRgb(gray, gray, gray); | |
d->pixels[g] = -1; | |
if (d->visual->c_class & 1) { | |
XColor xcolor; | |
xcolor.red = qRed(rgb) * 0x101; | |
xcolor.green = qGreen(rgb) * 0x101; | |
xcolor.blue = qBlue(rgb) * 0x101; | |
xcolor.pixel = 0ul; | |
if (XAllocColor(QX11Info::display(), d->colormap, &xcolor)) | |
d->pixels[g] = xcolor.pixel; | |
} | |
} | |
query_colormap(d, screen); | |
} | |
static void init_indexed(QColormapPrivate *d, int screen) | |
{ | |
d->pixels.resize(d->r_max * d->g_max * d->b_max); | |
// create color cube | |
for (int x = 0, r = 0; r < d->r_max; ++r) { | |
for (int g = 0; g < d->g_max; ++g) { | |
for (int b = 0; b < d->b_max; ++b, ++x) { | |
const QRgb rgb = qRgb((r * 0xff + (d->r_max - 1) / 2) / (d->r_max - 1), | |
(g * 0xff + (d->g_max - 1) / 2) / (d->g_max - 1), | |
(b * 0xff + (d->b_max - 1) / 2) / (d->b_max - 1)); | |
d->pixels[x] = -1; | |
if (d->visual->c_class & 1) { | |
XColor xcolor; | |
xcolor.red = qRed(rgb) * 0x101; | |
xcolor.green = qGreen(rgb) * 0x101; | |
xcolor.blue = qBlue(rgb) * 0x101; | |
xcolor.pixel = 0ul; | |
if (XAllocColor(QX11Info::display(), d->colormap, &xcolor)) | |
d->pixels[x] = xcolor.pixel; | |
} | |
} | |
} | |
} | |
query_colormap(d, screen); | |
} | |
static void init_direct(QColormapPrivate *d, bool ownColormap) | |
{ | |
if (d->visual->c_class != DirectColor || !ownColormap) | |
return; | |
// preallocate 768 on the stack, so that we don't have to malloc | |
// for the common case (<= 24 bpp) | |
QVarLengthArray<XColor, 768> colorTable(d->r_max + d->g_max + d->b_max); | |
int i = 0; | |
for (int r = 0; r < d->r_max; ++r) { | |
colorTable[i].red = r << 8 | r; | |
colorTable[i].pixel = r << d->r_shift; | |
colorTable[i].flags = DoRed; | |
++i; | |
} | |
for (int g = 0; g < d->g_max; ++g) { | |
colorTable[i].green = g << 8 | g; | |
colorTable[i].pixel = g << d->g_shift; | |
colorTable[i].flags = DoGreen; | |
++i; | |
} | |
for (int b = 0; b < d->b_max; ++b) { | |
colorTable[i].blue = (b << 8 | b); | |
colorTable[i].pixel = b << d->b_shift; | |
colorTable[i].flags = DoBlue; | |
++i; | |
} | |
XStoreColors(X11->display, d->colormap, colorTable.data(), colorTable.count()); | |
} | |
static QColormap **cmaps = 0; | |
void QColormap::initialize() | |
{ | |
Display *display = QX11Info::display(); | |
const int screens = ScreenCount(display); | |
cmaps = new QColormap*[screens]; | |
for (int i = 0; i < screens; ++i) { | |
cmaps[i] = new QColormap; | |
QColormapPrivate * const d = cmaps[i]->d; | |
bool use_stdcmap = false; | |
int color_count = X11->color_count; | |
// defaults | |
d->depth = DefaultDepth(display, i); | |
d->colormap = DefaultColormap(display, i); | |
d->defaultColormap = true; | |
d->visual = DefaultVisual(display, i); | |
d->defaultVisual = true; | |
Visual *argbVisual = 0; | |
if (X11->visual && i == DefaultScreen(display)) { | |
// only use the outside colormap on the default screen | |
d->visual = find_visual(display, i, X11->visual->c_class, | |
XVisualIDFromVisual(X11->visual), | |
&d->depth, &d->defaultVisual); | |
} else if ((X11->visual_class != -1 && X11->visual_class >= 0 && X11->visual_class < 6) | |
|| (X11->visual_id != -1)) { | |
// look for a specific visual or type of visual | |
d->visual = find_visual(display, i, X11->visual_class, X11->visual_id, | |
&d->depth, &d->defaultVisual); | |
} else if (QApplication::colorSpec() == QApplication::ManyColor) { | |
// look for a TrueColor w/ a depth higher than 8bpp | |
d->visual = find_visual(display, i, TrueColor, -1, &d->depth, &d->defaultVisual); | |
if (d->depth <= 8) { | |
d->visual = DefaultVisual(display, i); | |
d->defaultVisual = true; | |
color_count = 216; | |
} | |
} else if (!X11->custom_cmap) { | |
XStandardColormap *stdcmap = 0; | |
int ncmaps = 0; | |
#ifndef QT_NO_XRENDER | |
if (X11->use_xrender) { | |
int nvi; | |
XVisualInfo templ; | |
templ.screen = i; | |
templ.depth = 32; | |
templ.c_class = TrueColor; | |
XVisualInfo *xvi = XGetVisualInfo(X11->display, VisualScreenMask | | |
VisualDepthMask | | |
VisualClassMask, &templ, &nvi); | |
for (int idx = 0; idx < nvi; ++idx) { | |
XRenderPictFormat *format = XRenderFindVisualFormat(X11->display, | |
xvi[idx].visual); | |
if (format->type == PictTypeDirect && format->direct.alphaMask) { | |
argbVisual = xvi[idx].visual; | |
break; | |
} | |
} | |
XFree(xvi); | |
} | |
#endif | |
if (XGetRGBColormaps(display, RootWindow(display, i), | |
&stdcmap, &ncmaps, XA_RGB_DEFAULT_MAP)) { | |
if (stdcmap) { | |
for (int c = 0; c < ncmaps; ++c) { | |
if (!stdcmap[c].red_max || | |
!stdcmap[c].green_max || | |
!stdcmap[c].blue_max || | |
!stdcmap[c].red_mult || | |
!stdcmap[c].green_mult || | |
!stdcmap[c].blue_mult) | |
continue; // invalid stdcmap | |
XVisualInfo proto; | |
proto.visualid = stdcmap[c].visualid; | |
proto.screen = i; | |
int nvisuals = 0; | |
XVisualInfo *vi = XGetVisualInfo(display, VisualIDMask | VisualScreenMask, | |
&proto, &nvisuals); | |
if (vi) { | |
if (nvisuals > 0) { | |
use_stdcmap = true; | |
d->mode = ((vi[0].visual->c_class < StaticColor) | |
? Gray | |
: ((vi[0].visual->c_class < TrueColor) | |
? Indexed | |
: Direct)); | |
d->depth = vi[0].depth; | |
d->colormap = stdcmap[c].colormap; | |
d->defaultColormap = true; | |
d->visual = vi[0].visual; | |
d->defaultVisual = (d->visual == DefaultVisual(display, i)); | |
d->r_max = stdcmap[c].red_max + 1; | |
d->g_max = stdcmap[c].green_max + 1; | |
d->b_max = stdcmap[c].blue_max + 1; | |
if (d->mode == Direct) { | |
// calculate offsets | |
d->r_shift = lowest_bit(d->visual->red_mask); | |
d->g_shift = lowest_bit(d->visual->green_mask); | |
d->b_shift = lowest_bit(d->visual->blue_mask); | |
} else { | |
d->r_shift = 0; | |
d->g_shift = 0; | |
d->b_shift = 0; | |
} | |
} | |
XFree(vi); | |
} | |
break; | |
} | |
XFree(stdcmap); | |
} | |
} | |
} | |
if (!use_stdcmap) { | |
switch (d->visual->c_class) { | |
case StaticGray: | |
d->mode = Gray; | |
d->r_max = d->g_max = d->b_max = d->visual->map_entries; | |
break; | |
case XGrayScale: | |
d->mode = Gray; | |
// follow precedent set in libXmu... | |
if (color_count != 0) | |
d->r_max = d->g_max = d->b_max = color_count; | |
else if (d->visual->map_entries > 65000) | |
d->r_max = d->g_max = d->b_max = 4096; | |
else if (d->visual->map_entries > 4000) | |
d->r_max = d->g_max = d->b_max = 512; | |
else if (d->visual->map_entries > 250) | |
d->r_max = d->g_max = d->b_max = 12; | |
else | |
d->r_max = d->g_max = d->b_max = 4; | |
break; | |
case StaticColor: | |
d->mode = Indexed; | |
d->r_max = right_align(d->visual->red_mask) + 1; | |
d->g_max = right_align(d->visual->green_mask) + 1; | |
d->b_max = right_align(d->visual->blue_mask) + 1; | |
break; | |
case PseudoColor: | |
d->mode = Indexed; | |
// follow precedent set in libXmu... | |
if (color_count != 0) | |
d->r_max = d->g_max = d->b_max = cube_root(color_count); | |
else if (d->visual->map_entries > 65000) | |
d->r_max = d->g_max = d->b_max = 27; | |
else if (d->visual->map_entries > 4000) | |
d->r_max = d->g_max = d->b_max = 12; | |
else if (d->visual->map_entries > 250) | |
d->r_max = d->g_max = d->b_max = cube_root(d->visual->map_entries - 125); | |
else | |
d->r_max = d->g_max = d->b_max = cube_root(d->visual->map_entries); | |
break; | |
case TrueColor: | |
case DirectColor: | |
d->mode = Direct; | |
d->r_max = right_align(d->visual->red_mask) + 1; | |
d->g_max = right_align(d->visual->green_mask) + 1; | |
d->b_max = right_align(d->visual->blue_mask) + 1; | |
d->r_shift = lowest_bit(d->visual->red_mask); | |
d->g_shift = lowest_bit(d->visual->green_mask); | |
d->b_shift = lowest_bit(d->visual->blue_mask); | |
break; | |
} | |
} | |
bool ownColormap = false; | |
if (X11->colormap && i == DefaultScreen(display)) { | |
// only use the outside colormap on the default screen | |
d->colormap = X11->colormap; | |
d->defaultColormap = (d->colormap == DefaultColormap(display, i)); | |
} else if ((!use_stdcmap | |
&& (((d->visual->c_class & 1) && X11->custom_cmap) | |
|| d->visual != DefaultVisual(display, i))) | |
|| d->visual->c_class == DirectColor) { | |
// allocate custom colormap (we always do this when using DirectColor visuals) | |
d->colormap = | |
XCreateColormap(display, RootWindow(display, i), d->visual, | |
d->visual->c_class == DirectColor ? AllocAll : AllocNone); | |
d->defaultColormap = false; | |
ownColormap = true; | |
} | |
switch (d->mode) { | |
case Gray: | |
init_gray(d, i); | |
break; | |
case Indexed: | |
init_indexed(d, i); | |
break; | |
case Direct: | |
init_direct(d, ownColormap); | |
break; | |
} | |
QX11InfoData *screen = X11->screens + i; | |
screen->depth = d->depth; | |
screen->visual = d->visual; | |
screen->defaultVisual = d->defaultVisual; | |
screen->colormap = d->colormap; | |
screen->defaultColormap = d->defaultColormap; | |
screen->cells = screen->visual->map_entries; | |
if (argbVisual) { | |
X11->argbVisuals[i] = argbVisual; | |
X11->argbColormaps[i] = XCreateColormap(display, RootWindow(display, i), argbVisual, AllocNone); | |
} | |
// ### | |
// We assume that 8bpp == pseudocolor, but this is not | |
// always the case (according to the X server), so we need | |
// to make sure that our internal data is setup in a way | |
// that is compatible with our assumptions | |
if (screen->visual->c_class == TrueColor && screen->depth == 8 && screen->cells == 8) | |
screen->cells = 256; | |
} | |
} | |
void QColormap::cleanup() | |
{ | |
Display *display = QX11Info::display(); | |
const int screens = ScreenCount(display); | |
for (int i = 0; i < screens; ++i) | |
delete cmaps[i]; | |
delete [] cmaps; | |
cmaps = 0; | |
} | |
QColormap QColormap::instance(int screen) | |
{ | |
if (screen == -1) | |
screen = QX11Info::appScreen(); | |
return *cmaps[screen]; | |
} | |
/*! \internal | |
Constructs a new colormap. | |
*/ | |
QColormap::QColormap() | |
: d(new QColormapPrivate) | |
{} | |
QColormap::QColormap(const QColormap &colormap) | |
:d (colormap.d) | |
{ d->ref.ref(); } | |
QColormap::~QColormap() | |
{ | |
if (!d->ref.deref()) { | |
if (!d->defaultColormap) | |
XFreeColormap(QX11Info::display(), d->colormap); | |
delete d; | |
} | |
} | |
QColormap::Mode QColormap::mode() const | |
{ return d->mode; } | |
int QColormap::depth() const | |
{ return d->depth; } | |
int QColormap::size() const | |
{ | |
return (d->mode == Gray | |
? d->r_max | |
: (d->mode == Indexed | |
? d->r_max * d->g_max * d->b_max | |
: -1)); | |
} | |
uint QColormap::pixel(const QColor &color) const | |
{ | |
const QColor c = color.toRgb(); | |
const uint r = (c.ct.argb.red * d->r_max) >> 16; | |
const uint g = (c.ct.argb.green * d->g_max) >> 16; | |
const uint b = (c.ct.argb.blue * d->b_max) >> 16; | |
if (d->mode != Direct) { | |
if (d->mode == Gray) | |
return d->pixels.at((r * 30 + g * 59 + b * 11) / 100); | |
return d->pixels.at(r * d->g_max * d->b_max + g * d->b_max + b); | |
} | |
return (r << d->r_shift) + (g << d->g_shift) + (b << d->b_shift); | |
} | |
const QColor QColormap::colorAt(uint pixel) const | |
{ | |
if (d->mode != Direct) { | |
Q_ASSERT(pixel <= (uint)d->colors.size()); | |
return d->colors.at(pixel); | |
} | |
const int r = (((pixel & d->visual->red_mask) >> d->r_shift) << 8) / d->r_max; | |
const int g = (((pixel & d->visual->green_mask) >> d->g_shift) << 8) / d->g_max; | |
const int b = (((pixel & d->visual->blue_mask) >> d->b_shift) << 8) / d->b_max; | |
return QColor(r, g, b); | |
} | |
const QVector<QColor> QColormap::colormap() const | |
{ return d->colors; } | |
QColormap &QColormap::operator=(const QColormap &colormap) | |
{ | |
qAtomicAssign(d, colormap.d); | |
return *this; | |
} | |
QT_END_NAMESPACE |