/**************************************************************************** ** ** 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 #include #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; QList colors; QList 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 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 QList QXcbColormap::colormap() const { return d->colors; } QXcbColormap &QXcbColormap::operator=(const QXcbColormap &colormap) { qAtomicAssign(d, colormap.d); return *this; } QT_END_NAMESPACE