Adding files related to the `screenhack` API for X11 hacks.
[screensavers] / screenhack / grabscreen.c
/* xscreensaver, Copyright (c) 1992-2016 Jamie Zawinski <jwz@jwz.org>
*
* 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 appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation. No representations are made about the suitability of this
* software for any purpose. It is provided "as is" without express or
* implied warranty.
*/
/* This file contains code for grabbing an image of the screen to hack its
bits. This is a little tricky, since doing this involves the need to tell
the difference between drawing on the actual root window, and on the fake
root window used by the screensaver, since at this level the illusion
breaks down...
The hacks themselves use utils/grabclient.c to invoke the
"xscreensaver-getimage" program as a sub-process.
On "real" X11 systems:
"driver/xscreensaver-getimage" runs the code in this file to grab
the X11 root window image as a Pixmap.
On MacOS systems running X11, which nobody does any more:
"driver/xscreensaver-getimage" runs the Perl script
"driver/xscreensaver-getimage-desktop", which in turn runs the MacOS
program "/usr/sbin/screencapture" to get the Mac desktop image as a
PNG file.
On MacOS systems running the native Cocoa build, or on iOS or Android
systems:
"driver/xscreensaver-getimage" is not used. Instead, each saver's
"utils/grabclient.c" links against "OSX/grabclient-osx.m",
"OSX/grabclient-ios.m" or "jwxyz/jwxyz-android.c" to grab
screenshots directly without invoking a sub-process to do it.
See the comment at the top of utils/grabclient.c for a more detailed
explanation.
*/
#include "utils.h"
#include "yarandom.h"
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#ifdef HAVE_XMU
# ifndef VMS
# include <X11/Xmu/WinUtil.h>
# else /* VMS */
# include <Xmu/WinUtil.h>
# endif /* VMS */
#endif
#include "usleep.h"
#include "colors.h"
#include "grabscreen.h"
#include "visual.h"
#include "resources.h"
#include "vroot.h"
#undef RootWindowOfScreen
#undef RootWindow
#undef DefaultRootWindow
#ifdef HAVE_READ_DISPLAY_EXTENSION
# include <X11/extensions/readdisplay.h>
static Bool read_display (Screen *, Window, Pixmap, Bool);
#endif /* HAVE_READ_DISPLAY_EXTENSION */
static void copy_default_colormap_contents (Screen *, Colormap, Visual *);
#ifdef HAVE_READ_DISPLAY_EXTENSION
static void allocate_cubic_colormap (Screen *, Window, Visual *);
void remap_image (Screen *, Window, Colormap, XImage *);
#endif
static Bool
MapNotify_event_p (Display *dpy, XEvent *event, XPointer window)
{
return (event->xany.type == MapNotify &&
event->xvisibility.window == (Window) window);
}
extern char *progname;
Bool grab_verbose_p = False;
void
grabscreen_verbose(void)
{
grab_verbose_p = True;
}
static void
raise_window(Display *dpy, Window window, Bool dont_wait)
{
if (grab_verbose_p)
fprintf(stderr, "%s: raising window 0x%08lX (%s)\n",
progname, (unsigned long) window,
(dont_wait ? "not waiting" : "waiting"));
if (! dont_wait)
{
XWindowAttributes xgwa;
XSizeHints hints;
long supplied = 0;
memset(&hints, 0, sizeof(hints));
XGetWMNormalHints(dpy, window, &hints, &supplied);
XGetWindowAttributes (dpy, window, &xgwa);
hints.x = xgwa.x;
hints.y = xgwa.y;
hints.width = xgwa.width;
hints.height = xgwa.height;
hints.flags |= (PPosition|USPosition|PSize|USSize);
XSetWMNormalHints(dpy, window, &hints);
XSelectInput (dpy, window, (xgwa.your_event_mask | StructureNotifyMask));
}
XMapRaised(dpy, window);
if (! dont_wait)
{
XEvent event;
XIfEvent (dpy, &event, MapNotify_event_p, (XPointer) window);
XSync (dpy, True);
}
}
static Bool
xscreensaver_window_p (Display *dpy, Window window)
{
Atom type;
int format;
unsigned long nitems, bytesafter;
unsigned char *version;
if (XGetWindowProperty (dpy, window,
XInternAtom (dpy, "_SCREENSAVER_VERSION", False),
0, 1, False, XA_STRING,
&type, &format, &nitems, &bytesafter,
&version)
== Success
&& type != None)
return True;
return False;
}
/* Whether the given window is:
- the real root window;
- a direct child of the root window;
- a direct child of the window manager's decorations.
*/
Bool
top_level_window_p (Screen *screen, Window window)
{
Display *dpy = DisplayOfScreen (screen);
Window root, parent, *kids;
unsigned int nkids;
if (!XQueryTree (dpy, window, &root, &parent, &kids, &nkids))
return False;
if (window == root)
return True;
/* If our direct parent is the real root window, then yes. */
if (parent == root)
return True;
else
{
Atom type = None;
int format;
unsigned long nitems, bytesafter;
unsigned char *data;
/* If our direct parent has the WM_STATE property, then it is a
window manager decoration -- yes.
*/
if (XGetWindowProperty (dpy, window,
XInternAtom (dpy, "WM_STATE", True),
0, 0, False, AnyPropertyType,
&type, &format, &nitems, &bytesafter,
(unsigned char **) &data)
== Success
&& type != None)
return True;
}
/* Else, no. We're deep in a tree somewhere.
*/
return False;
}
static Bool error_handler_hit_p = False;
static XErrorHandler old_ehandler = 0;
static int
BadWindow_ehandler (Display *dpy, XErrorEvent *error)
{
error_handler_hit_p = True;
if (error->error_code == BadWindow || error->error_code == BadDrawable)
return 0;
else if (!old_ehandler)
{
abort();
return 0;
}
else
return (*old_ehandler) (dpy, error);
}
/* XCopyArea seems not to work right on SGI O2s if you draw in SubwindowMode
on a window whose depth is not the maximal depth of the screen? Or
something. Anyway, things don't work unless we: use SubwindowMode for
the real root window (or a legitimate virtual root window); but do not
use SubwindowMode for the xscreensaver window. I make no attempt to
explain.
*/
Bool
use_subwindow_mode_p(Screen *screen, Window window)
{
if (window != VirtualRootWindowOfScreen(screen))
return False;
else if (xscreensaver_window_p(DisplayOfScreen(screen), window))
return False;
else
return True;
}
/* Install the colormaps of all visible windows, deepest first.
This should leave the colormaps of the topmost windows installed
(if only N colormaps can be installed at a time, then only the
topmost N windows will be shown in the right colors.)
*/
static void
install_screen_colormaps (Screen *screen)
{
unsigned int i;
Display *dpy = DisplayOfScreen (screen);
Window real_root;
Window parent, *kids = 0;
unsigned int nkids = 0;
XSync (dpy, False);
old_ehandler = XSetErrorHandler (BadWindow_ehandler);
error_handler_hit_p = False;
real_root = XRootWindowOfScreen (screen); /* not vroot */
if (XQueryTree (dpy, real_root, &real_root, &parent, &kids, &nkids))
for (i = 0; i < nkids; i++)
{
XWindowAttributes xgwa;
Window client;
#ifdef HAVE_XMU
/* #### need to put XmuClientWindow() in xmu.c, sigh... */
if (! (client = XmuClientWindow (dpy, kids[i])))
#endif
client = kids[i];
xgwa.colormap = 0;
XGetWindowAttributes (dpy, client, &xgwa);
if (xgwa.colormap && xgwa.map_state == IsViewable)
XInstallColormap (dpy, xgwa.colormap);
}
XInstallColormap (dpy, DefaultColormapOfScreen (screen));
XSync (dpy, False);
XSetErrorHandler (old_ehandler);
XSync (dpy, False);
if (kids)
XFree ((char *) kids);
}
void
grab_screen_image_internal (Screen *screen, Window window)
{
Display *dpy = DisplayOfScreen (screen);
XWindowAttributes xgwa;
Window real_root;
Bool root_p;
Bool saver_p;
Bool grab_mouse_p = False;
int unmap_time = 0;
real_root = XRootWindowOfScreen (screen); /* not vroot */
root_p = (window == real_root);
saver_p = xscreensaver_window_p (dpy, window);
XGetWindowAttributes (dpy, window, &xgwa);
screen = xgwa.screen;
if (saver_p)
/* I think this is redundant, but just to be safe... */
root_p = False;
if (saver_p)
/* The only time grabbing the mouse is important is if this program
is being run while the saver is locking the screen. */
grab_mouse_p = True;
if (!root_p)
{
double unmap = 0;
if (saver_p)
{
unmap = get_float_resource(dpy, "grabRootDelay", "Seconds");
if (unmap <= 0.00001 || unmap > 20) unmap = 2.5;
}
else
{
unmap = get_float_resource(dpy, "grabWindowDelay", "Seconds");
if (unmap <= 0.00001 || unmap > 20) unmap = 0.66;
}
unmap_time = unmap * 100000;
}
if (grab_verbose_p)
{
fprintf(stderr,
"\n%s: window 0x%08lX root: %d saver: %d grab: %d wait: %.1f\n",
progname, (unsigned long) window,
root_p, saver_p, grab_mouse_p, ((double)unmap_time)/1000000.0);
fprintf(stderr, "%s: ", progname);
describe_visual(stderr, screen, xgwa.visual, False);
fprintf (stderr, "\n");
}
if (!root_p && !top_level_window_p (screen, window))
{
if (grab_verbose_p)
fprintf (stderr, "%s: not a top-level window: 0x%08lX: not grabbing\n",
progname, (unsigned long) window);
return;
}
if (!root_p)
XSetWindowBackgroundPixmap (dpy, window, None);
if (grab_mouse_p)
{
/* prevent random viewer of the screen saver (locker) from messing
with windows. We don't check whether it succeeded, because what
are our options, really... */
XGrabPointer (dpy, real_root, True, ButtonPressMask|ButtonReleaseMask,
GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
XGrabKeyboard (dpy, real_root, True, GrabModeSync, GrabModeAsync,
CurrentTime);
}
if (unmap_time > 0)
{
XUnmapWindow (dpy, window);
install_screen_colormaps (screen);
XSync (dpy, True);
usleep(unmap_time); /* wait for everyone to swap in and handle exposes */
}
if (!root_p)
{
#ifdef HAVE_READ_DISPLAY_EXTENSION
if (! read_display(screen, window, 0, saver_p))
#endif /* HAVE_READ_DISPLAY_EXTENSION */
{
#ifdef HAVE_READ_DISPLAY_EXTENSION
if (grab_verbose_p)
fprintf(stderr, "%s: read_display() failed\n", progname);
#endif /* HAVE_READ_DISPLAY_EXTENSION */
copy_default_colormap_contents (screen, xgwa.colormap, xgwa.visual);
raise_window(dpy, window, saver_p);
/* Generally it's bad news to call XInstallColormap() explicitly,
but this file does a lot of sleazy stuff already... This is to
make sure that the window's colormap is installed, even in the
case where the window is OverrideRedirect. */
if (xgwa.colormap) XInstallColormap (dpy, xgwa.colormap);
XSync (dpy, False);
}
}
else /* root_p */
{
Pixmap pixmap;
pixmap = XCreatePixmap(dpy, window, xgwa.width, xgwa.height, xgwa.depth);
#ifdef HAVE_READ_DISPLAY_EXTENSION
if (! read_display(screen, window, pixmap, True))
#endif
{
Window real_root = XRootWindowOfScreen (screen); /* not vroot */
XGCValues gcv;
GC gc;
#ifdef HAVE_READ_DISPLAY_EXTENSION
if (grab_verbose_p)
fprintf(stderr, "%s: read_display() failed\n", progname);
#endif /* HAVE_READ_DISPLAY_EXTENSION */
copy_default_colormap_contents (screen, xgwa.colormap, xgwa.visual);
gcv.function = GXcopy;
gcv.subwindow_mode = IncludeInferiors;
gc = XCreateGC (dpy, window, GCFunction | GCSubwindowMode, &gcv);
XCopyArea (dpy, real_root, pixmap, gc,
xgwa.x, xgwa.y, xgwa.width, xgwa.height, 0, 0);
XFreeGC (dpy, gc);
}
XSetWindowBackgroundPixmap (dpy, window, pixmap);
XFreePixmap (dpy, pixmap);
}
if (grab_verbose_p)
fprintf (stderr, "%s: grabbed %d bit screen image to %swindow.\n",
progname, xgwa.depth,
(root_p ? "real root " : ""));
if (grab_mouse_p)
{
XUngrabPointer (dpy, CurrentTime);
XUngrabKeyboard (dpy, CurrentTime);
}
XSync (dpy, True);
}
/* When we are grabbing and manipulating a screen image, it's important that
we use the same colormap it originally had. So, if the screensaver was
started with -install, we need to copy the contents of the default colormap
into the screensaver's colormap.
*/
static void
copy_default_colormap_contents (Screen *screen,
Colormap to_cmap,
Visual *to_visual)
{
Display *dpy = DisplayOfScreen (screen);
Visual *from_visual = DefaultVisualOfScreen (screen);
Colormap from_cmap = XDefaultColormapOfScreen (screen);
XColor *old_colors, *new_colors;
unsigned long *pixels;
XVisualInfo vi_in, *vi_out;
int out_count;
int from_cells, to_cells, max_cells, got_cells;
int i;
if (from_cmap == to_cmap)
return;
vi_in.screen = XScreenNumberOfScreen (screen);
vi_in.visualid = XVisualIDFromVisual (from_visual);
vi_out = XGetVisualInfo (dpy, VisualScreenMask|VisualIDMask,
&vi_in, &out_count);
if (! vi_out) abort ();
from_cells = vi_out [0].colormap_size;
XFree ((char *) vi_out);
vi_in.screen = XScreenNumberOfScreen (screen);
vi_in.visualid = XVisualIDFromVisual (to_visual);
vi_out = XGetVisualInfo (dpy, VisualScreenMask|VisualIDMask,
&vi_in, &out_count);
if (! vi_out) abort ();
to_cells = vi_out [0].colormap_size;
XFree ((char *) vi_out);
max_cells = (from_cells > to_cells ? to_cells : from_cells);
old_colors = (XColor *) calloc (sizeof (XColor), max_cells);
new_colors = (XColor *) calloc (sizeof (XColor), max_cells);
pixels = (unsigned long *) calloc (sizeof (unsigned long), max_cells);
for (i = 0; i < max_cells; i++)
old_colors[i].pixel = i;
XQueryColors (dpy, from_cmap, old_colors, max_cells);
got_cells = max_cells;
allocate_writable_colors (screen, to_cmap, pixels, &got_cells);
if (grab_verbose_p && got_cells != max_cells)
fprintf(stderr, "%s: got only %d of %d cells\n", progname,
got_cells, max_cells);
if (got_cells <= 0) /* we're screwed */
;
else if (got_cells == max_cells && /* we're golden */
from_cells == to_cells)
XStoreColors (dpy, to_cmap, old_colors, got_cells);
else /* try to cope... */
{
for (i = 0; i < got_cells; i++)
{
XColor *c = old_colors + i;
int j;
for (j = 0; j < got_cells; j++)
if (pixels[j] == c->pixel)
{
/* only store this color value if this is one of the pixels
we were able to allocate. */
XStoreColors (dpy, to_cmap, c, 1);
break;
}
}
}
if (grab_verbose_p)
fprintf(stderr, "%s: installing copy of default colormap\n", progname);
free (old_colors);
free (new_colors);
free (pixels);
}
\f
/* The SGI ReadDisplay extension.
This extension lets you get back a 24-bit image of the screen, taking into
account the colors with which all windows are *currently* displayed, even
if those windows have different visuals. Without this extension, presence
of windows with different visuals or colormaps will result in technicolor
when one tries to grab the screen image.
*/
#ifdef HAVE_READ_DISPLAY_EXTENSION
static Bool
read_display (Screen *screen, Window window, Pixmap into_pixmap,
Bool dont_wait)
{
Display *dpy = DisplayOfScreen (screen);
XWindowAttributes xgwa;
int rd_event_base = 0;
int rd_error_base = 0;
unsigned long hints = 0;
XImage *image = 0;
XGCValues gcv;
int class;
GC gc;
Bool remap_p = False;
/* Check to see if the server supports the extension, and bug out if not.
*/
if (! XReadDisplayQueryExtension (dpy, &rd_event_base, &rd_error_base))
{
if (grab_verbose_p)
fprintf(stderr, "%s: no XReadDisplay extension\n", progname);
return False;
}
/* If this isn't a visual we know how to handle, bug out. We handle:
= TrueColor in depths 8, 12, 15, 16, and 32;
= PseudoColor and DirectColor in depths 8 and 12.
*/
XGetWindowAttributes(dpy, window, &xgwa);
class = visual_class (screen, xgwa.visual);
if (class == TrueColor)
{
if (xgwa.depth != 8 && xgwa.depth != 12 && xgwa.depth != 15 &&
xgwa.depth != 16 && xgwa.depth != 24 && xgwa.depth != 32)
{
if (grab_verbose_p)
fprintf(stderr, "%s: TrueColor depth %d unsupported\n",
progname, xgwa.depth);
return False;
}
}
else if (class == PseudoColor || class == DirectColor)
{
if (xgwa.depth != 8 && xgwa.depth != 12)
{
if (grab_verbose_p)
fprintf(stderr, "%s: Pseudo/DirectColor depth %d unsupported\n",
progname, xgwa.depth);
return False;
}
else
/* Allocate a TrueColor-like spread of colors for the image. */
remap_p = True;
}
/* Try and read the screen.
*/
hints = (XRD_TRANSPARENT | XRD_READ_POINTER);
image = XReadDisplay (dpy, window, xgwa.x, xgwa.y, xgwa.width, xgwa.height,
hints, &hints);
if (!image)
{
if (grab_verbose_p)
fprintf(stderr, "%s: XReadDisplay() failed\n", progname);
return False;
}
if (!image->data)
{
if (grab_verbose_p)
fprintf(stderr, "%s: XReadDisplay() returned no data\n", progname);
XDestroyImage(image);
return False;
}
/* XReadDisplay tends to LIE about the depth of the image it read.
It is returning an XImage which has `depth' and `bits_per_pixel'
confused!
That is, on a 24-bit display, where all visuals claim depth 24, and
where XGetImage would return an XImage with depth 24, and where
XPutImage will get a BadMatch with images that are not depth 24,
XReadDisplay is returning images with depth 32! Fuckwits!
So if the visual is of depth 24, but the image came back as depth 32,
hack it to be 24 lest we get a BadMatch from XPutImage.
I wonder what happens on an 8-bit SGI... Probably it still returns
an image claiming depth 32? Certainly it can't be 8. So, let's just
smash it to 32...
*/
if (image->depth == 32 /* && xgwa.depth == 24 */ )
image->depth = 24;
/* If the visual of the window/pixmap into which we're going to draw is
less deep than the screen itself, then we need to convert the grabbed bits
to match the depth by clipping off the less significant bit-planes of each
color component.
*/
if (image->depth > xgwa.depth)
{
int x, y;
/* We use the same image->data in both images -- that's ok, because
since we're reading from B and writing to A, and B uses more bytes
per pixel than A, the write pointer won't overrun the read pointer.
*/
XImage *image2 = XCreateImage (dpy, xgwa.visual, xgwa.depth,
ZPixmap, 0, image->data,
xgwa.width, xgwa.height,
8, 0);
if (!image2)
{
if (grab_verbose_p)
fprintf(stderr, "%s: out of memory?\n", progname);
return False;
}
if (grab_verbose_p)
fprintf(stderr, "%s: converting from depth %d to depth %d\n",
progname, image->depth, xgwa.depth);
for (y = 0; y < image->height; y++)
for (x = 0; x < image->width; x++)
{
/* #### really these shift values should be determined from the
mask values -- but that's a pain in the ass, and anyway,
this is an SGI-specific extension so hardcoding assumptions
about the SGI server's behavior isn't *too* heinous... */
unsigned long pixel = XGetPixel(image, x, y);
unsigned int r = (pixel & image->red_mask);
unsigned int g = (pixel & image->green_mask) >> 8;
unsigned int b = (pixel & image->blue_mask) >> 16;
if (xgwa.depth == 8)
pixel = ((r >> 5) | ((g >> 5) << 3) | ((b >> 6) << 6));
else if (xgwa.depth == 12)
pixel = ((r >> 4) | ((g >> 4) << 4) | ((b >> 4) << 8));
else if (xgwa.depth == 16 || xgwa.depth == 15)
/* Gah! I don't understand why these are in the other order. */
pixel = (((r >> 3) << 10) | ((g >> 3) << 5) | ((b >> 3)));
else
abort();
XPutPixel(image2, x, y, pixel);
}
image->data = 0;
XDestroyImage(image);
image = image2;
}
if (remap_p)
{
allocate_cubic_colormap (screen, window, xgwa.visual);
remap_image (screen, window, xgwa.colormap, image);
}
/* Now actually put the bits into the window or pixmap -- note the design
bogosity of this extension, where we've been forced to take 24 bit data
from the server to the client, and then push it back from the client to
the server, *without alteration*. We should have just been able to tell
the server, "put a screen image in this drawable", instead of having to
go through the intermediate step of converting it to an Image. Geez.
(Assuming that the window is of screen depth; we happen to handle less
deep windows, but that's beside the point.)
*/
gcv.function = GXcopy;
gc = XCreateGC (dpy, window, GCFunction, &gcv);
if (into_pixmap)
{
gcv.function = GXcopy;
gc = XCreateGC (dpy, into_pixmap, GCFunction, &gcv);
XPutImage (dpy, into_pixmap, gc, image, 0, 0, 0, 0,
xgwa.width, xgwa.height);
}
else
{
gcv.function = GXcopy;
gc = XCreateGC (dpy, window, GCFunction, &gcv);
/* Ok, now we'll be needing that window on the screen... */
raise_window(dpy, window, dont_wait);
/* Plop down the bits... */
XPutImage (dpy, window, gc, image, 0, 0, 0, 0, xgwa.width, xgwa.height);
}
XFreeGC (dpy, gc);
if (image->data)
{
free(image->data);
image->data = 0;
}
XDestroyImage(image);
return True;
}
#endif /* HAVE_READ_DISPLAY_EXTENSION */
#ifdef HAVE_READ_DISPLAY_EXTENSION
/* Makes and installs a colormap that makes a PseudoColor or DirectColor
visual behave like a TrueColor visual of the same depth.
#### Duplicated in driver/xscreensaver-getimage.c
*/
static void
allocate_cubic_colormap (Screen *screen, Window window, Visual *visual)
{
Display *dpy = DisplayOfScreen (screen);
XWindowAttributes xgwa;
Colormap cmap;
int nr, ng, nb, cells;
int r, g, b;
int depth;
XColor colors[4097];
int i;
XGetWindowAttributes (dpy, window, &xgwa);
cmap = xgwa.colormap;
depth = visual_depth (screen, visual);
switch (depth)
{
case 8: nr = 3; ng = 3; nb = 2; cells = 256; break;
case 12: nr = 4; ng = 4; nb = 4; cells = 4096; break;
default: abort(); break;
}
memset(colors, 0, sizeof(colors));
for (r = 0; r < (1 << nr); r++)
for (g = 0; g < (1 << ng); g++)
for (b = 0; b < (1 << nb); b++)
{
i = (r | (g << nr) | (b << (nr + ng)));
colors[i].pixel = i;
colors[i].flags = DoRed|DoGreen|DoBlue;
if (depth == 8)
{
colors[i].red = ((r << 13) | (r << 10) | (r << 7) |
(r << 4) | (r << 1));
colors[i].green = ((g << 13) | (g << 10) | (g << 7) |
(g << 4) | (g << 1));
colors[i].blue = ((b << 14) | (b << 12) | (b << 10) |
(b << 8) | (b << 6) | (b << 4) |
(b << 2) | b);
}
else
{
colors[i].red = (r << 12) | (r << 8) | (r << 4) | r;
colors[i].green = (g << 12) | (g << 8) | (g << 4) | g;
colors[i].blue = (b << 12) | (b << 8) | (b << 4) | b;
}
}
{
int j;
int allocated = 0;
int interleave = cells / 8; /* skip around, rather than allocating in
order, so that we get better coverage if
we can't allocated all of them. */
for (j = 0; j < interleave; j++)
for (i = 0; i < cells; i += interleave)
if (XAllocColor (dpy, cmap, &colors[i + j]))
allocated++;
if (grab_verbose_p)
fprintf (stderr, "%s: allocated %d of %d colors for cubic map\n",
progname, allocated, cells);
}
}
/* Find the pixel index that is closest to the given color
(using linear distance in RGB space -- which is far from the best way.)
#### Duplicated in driver/xscreensaver-getimage.c
*/
static unsigned long
find_closest_pixel (XColor *colors, int ncolors,
unsigned long r, unsigned long g, unsigned long b)
{
unsigned long distance = ~0;
int i, found = 0;
if (ncolors == 0)
abort();
for (i = 0; i < ncolors; i++)
{
unsigned long d;
int rd, gd, bd;
rd = r - colors[i].red;
gd = g - colors[i].green;
bd = b - colors[i].blue;
if (rd < 0) rd = -rd;
if (gd < 0) gd = -gd;
if (bd < 0) bd = -bd;
d = (rd << 1) + (gd << 2) + bd;
if (d < distance)
{
distance = d;
found = i;
if (distance == 0)
break;
}
}
return found;
}
/* Given an XImage with 8-bit or 12-bit RGB data, convert it to be
displayable with the given X colormap. The farther from a perfect
color cube the contents of the colormap are, the lossier the
transformation will be. No dithering is done.
#### Duplicated in driver/xscreensaver-getimage.c
*/
void
remap_image (Screen *screen, Window window, Colormap cmap, XImage *image)
{
Display *dpy = DisplayOfScreen (screen);
unsigned long map[4097];
int x, y, i;
int cells;
XColor colors[4097];
if (image->depth == 8)
cells = 256;
else if (image->depth == 12)
cells = 4096;
else
abort();
memset(map, -1, sizeof(*map));
memset(colors, -1, sizeof(*colors));
for (i = 0; i < cells; i++)
colors[i].pixel = i;
XQueryColors (dpy, cmap, colors, cells);
if (grab_verbose_p)
fprintf(stderr, "%s: building table for %d bit image\n",
progname, image->depth);
for (i = 0; i < cells; i++)
{
unsigned short r, g, b;
if (cells == 256)
{
/* "RRR GGG BB" In an 8 bit map. Convert that to
"RRR RRR RR" "GGG GGG GG" "BB BB BB BB" to give
an even spread. */
r = (i & 0x07);
g = (i & 0x38) >> 3;
b = (i & 0xC0) >> 6;
r = ((r << 13) | (r << 10) | (r << 7) | (r << 4) | (r << 1));
g = ((g << 13) | (g << 10) | (g << 7) | (g << 4) | (g << 1));
b = ((b << 14) | (b << 12) | (b << 10) | (b << 8) |
(b << 6) | (b << 4) | (b << 2) | b);
}
else
{
/* "RRRR GGGG BBBB" In a 12 bit map. Convert that to
"RRRR RRRR" "GGGG GGGG" "BBBB BBBB" to give an even
spread. */
r = (i & 0x00F);
g = (i & 0x0F0) >> 4;
b = (i & 0xF00) >> 8;
r = (r << 12) | (r << 8) | (r << 4) | r;
g = (g << 12) | (g << 8) | (g << 4) | g;
b = (b << 12) | (b << 8) | (b << 4) | b;
}
map[i] = find_closest_pixel (colors, cells, r, g, b);
}
if (grab_verbose_p)
fprintf(stderr, "%s: remapping colors in %d bit image\n",
progname, image->depth);
for (y = 0; y < image->height; y++)
for (x = 0; x < image->width; x++)
{
unsigned long pixel = XGetPixel(image, x, y);
if (pixel >= cells) abort();
XPutPixel(image, x, y, map[pixel]);
}
}
#endif /* HAVE_READ_DISPLAY_EXTENSION */