| /**************************************************************************** |
| ** |
| ** Copyright (C) 2018 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the plugins of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include <QVarLengthArray> |
| |
| #include <private/qguiapplication_p.h> |
| |
| #include "qcolormap_x11_p.h" |
| #include "qxcbnativepainting.h" |
| #include "qt_x11_p.h" |
| |
| QT_BEGIN_NAMESPACE |
| |
| class QXcbColormapPrivate |
| { |
| public: |
| QXcbColormapPrivate() |
| : ref(1), mode(QXcbColormap::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; |
| |
| QXcbColormap::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 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(QXcbColormapPrivate *d, int screen) |
| { |
| Display *display = X11->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 == QXcbColormap::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(QXcbColormapPrivate *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(X11->display, d->colormap, &xcolor)) |
| d->pixels[g] = xcolor.pixel; |
| } |
| } |
| |
| query_colormap(d, screen); |
| } |
| |
| static void init_indexed(QXcbColormapPrivate *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(X11->display, d->colormap, &xcolor)) |
| d->pixels[x] = xcolor.pixel; |
| } |
| } |
| } |
| } |
| |
| query_colormap(d, screen); |
| } |
| |
| static void init_direct(QXcbColormapPrivate *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 QXcbColormap **cmaps = 0; |
| |
| void QXcbColormap::initialize() |
| { |
| Display *display = X11->display; |
| const int screens = ScreenCount(display); |
| |
| cmaps = new QXcbColormap*[screens]; |
| |
| for (int i = 0; i < screens; ++i) { |
| cmaps[i] = new QXcbColormap; |
| QXcbColormapPrivate * 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 (!X11->custom_cmap) { |
| XStandardColormap *stdcmap = 0; |
| int ncmaps = 0; |
| |
| #if QT_CONFIG(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 QXcbColormap::cleanup() |
| { |
| Display *display = X11->display; |
| const int screens = ScreenCount(display); |
| |
| for (int i = 0; i < screens; ++i) |
| delete cmaps[i]; |
| |
| delete [] cmaps; |
| cmaps = 0; |
| } |
| |
| |
| QXcbColormap QXcbColormap::instance(int screen) |
| { |
| if (screen == -1) |
| screen = QXcbX11Info::appScreen(); |
| return *cmaps[screen]; |
| } |
| |
| /*! \internal |
| Constructs a new colormap. |
| */ |
| QXcbColormap::QXcbColormap() |
| : d(new QXcbColormapPrivate) |
| {} |
| |
| QXcbColormap::QXcbColormap(const QXcbColormap &colormap) |
| :d (colormap.d) |
| { d->ref.ref(); } |
| |
| QXcbColormap::~QXcbColormap() |
| { |
| if (!d->ref.deref()) { |
| if (!d->defaultColormap) |
| XFreeColormap(X11->display, d->colormap); |
| delete d; |
| } |
| } |
| |
| QXcbColormap::Mode QXcbColormap::mode() const |
| { return d->mode; } |
| |
| int QXcbColormap::depth() const |
| { return d->depth; } |
| |
| int QXcbColormap::size() const |
| { |
| return (d->mode == Gray |
| ? d->r_max |
| : (d->mode == Indexed |
| ? d->r_max * d->g_max * d->b_max |
| : -1)); |
| } |
| |
| uint QXcbColormap::pixel(const QColor &color) const |
| { |
| const QRgba64 rgba64 = color.rgba64(); |
| // XXX We emulate the raster engine here by getting the |
| // 8-bit values, but we could instead use the 16-bit |
| // values for slightly better color accuracy |
| const uint r = (rgba64.red8() * d->r_max) >> 8; |
| const uint g = (rgba64.green8() * d->g_max) >> 8; |
| const uint b = (rgba64.blue8() * d->b_max) >> 8; |
| 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 QXcbColormap::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> QXcbColormap::colormap() const |
| { return d->colors; } |
| |
| QXcbColormap &QXcbColormap::operator=(const QXcbColormap &colormap) |
| { |
| qAtomicAssign(d, colormap.d); |
| return *this; |
| } |
| |
| QT_END_NAMESPACE |