/*
* $Id$
*
* Copyright © 2002 Keith Packard
*
* 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, and that the name of Keith Packard not be used in
* advertising or publicity pertaining to distribution of the software without
* specific, written prior permission. Keith Packard makes no
* representations about the suitability of this software for any purpose. It
* is provided "as is" without express or implied warranty.
*
* KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
* EVENT SHALL KEITH PACKARD 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.
*/
#include "xcursorint.h"
#include <X11/Xlibint.h>
#include <X11/Xutil.h>
XcursorCursors *
XcursorCursorsCreate (Display *dpy, int size)
{
XcursorCursors *cursors;
cursors = malloc (sizeof (XcursorCursors) +
size * sizeof (Cursor));
if (!cursors)
return NULL;
cursors->ref = 1;
cursors->dpy = dpy;
cursors->ncursor = 0;
cursors->cursors = (Cursor *) (cursors + 1);
return cursors;
}
void
XcursorCursorsDestroy (XcursorCursors *cursors)
{
int n;
if (!cursors)
return;
--cursors->ref;
if (cursors->ref > 0)
return;
for (n = 0; n < cursors->ncursor; n++)
XFreeCursor (cursors->dpy, cursors->cursors[n]);
free (cursors);
}
XcursorAnimate *
XcursorAnimateCreate (XcursorCursors *cursors)
{
XcursorAnimate *animate;
animate = malloc (sizeof (XcursorAnimate));
if (!animate)
return NULL;
animate->cursors = cursors;
cursors->ref++;
animate->sequence = 0;
return animate;
}
void
XcursorAnimateDestroy (XcursorAnimate *animate)
{
if (!animate)
return;
XcursorCursorsDestroy (animate->cursors);
free (animate);
}
Cursor
XcursorAnimateNext (XcursorAnimate *animate)
{
Cursor cursor = animate->cursors->cursors[animate->sequence++];
if (animate->sequence >= animate->cursors->ncursor)
animate->sequence = 0;
return cursor;
}
static int
nativeByteOrder (void)
{
int x = 1;
return (*((char *) &x) == 1) ? LSBFirst : MSBFirst;
}
static XcursorUInt
_XcursorPixelBrightness (XcursorPixel p)
{
XcursorPixel alpha = p >> 24;
XcursorPixel r, g, b;
if (!alpha)
return 0;
r = ((p >> 8) & 0xff00) / alpha;
if (r > 0xff) r = 0xff;
g = ((p >> 0) & 0xff00) / alpha;
if (g > 0xff) g = 0xff;
b = ((p << 8) & 0xff00) / alpha;
if (b > 0xff) b = 0xff;
return (r * 153 + g * 301 + b * 58) >> 9;
}
static unsigned short
_XcursorDivideAlpha (XcursorUInt value, XcursorUInt alpha)
{
if (!alpha)
return 0;
value = value * 255 / alpha;
if (value > 255)
value = 255;
return value | (value << 8);
}
static void
_XcursorPixelToColor (XcursorPixel p, XColor *color)
{
XcursorPixel alpha = p >> 24;
color->pixel = 0;
color->red = _XcursorDivideAlpha ((p >> 16) & 0xff, alpha);
color->green = _XcursorDivideAlpha ((p >> 8) & 0xff, alpha);
color->blue = _XcursorDivideAlpha ((p >> 0) & 0xff, alpha);
color->flags = DoRed|DoGreen|DoBlue;
}
#undef DEBUG_IMAGE
#ifdef DEBUG_IMAGE
static void
_XcursorDumpImage (XImage *image)
{
FILE *f = fopen ("/tmp/images", "a");
int x, y;
if (!f)
return;
fprintf (f, "%d x %x\n", image->width, image->height);
for (y = 0; y < image->height; y++)
{
for (x = 0; x < image->width; x++)
fprintf (f, "%c", XGetPixel (image, x, y) ? '*' : ' ');
fprintf (f, "\n");
}
fflush (f);
fclose (f);
}
static void
_XcursorDumpColor (XColor *color, char *name)
{
FILE *f = fopen ("/tmp/images", "a");
fprintf (f, "%s: %x %x %x\n", name,
color->red, color->green, color->blue);
fflush (f);
fclose (f);
}
#endif
static int
_XcursorCompareRed (const void *a, const void *b)
{
const XcursorPixel *ap = a, *bp = b;
return (int) (((*ap >> 16) & 0xff) - ((*bp >> 16) & 0xff));
}
static int
_XcursorCompareGreen (const void *a, const void *b)
{
const XcursorPixel *ap = a, *bp = b;
return (int) (((*ap >> 8) & 0xff) - ((*bp >> 8) & 0xff));
}
static int
_XcursorCompareBlue (const void *a, const void *b)
{
const XcursorPixel *ap = a, *bp = b;
return (int) (((*ap >> 0) & 0xff) - ((*bp >> 0) & 0xff));
}
static XcursorPixel
_XcursorAverageColor (XcursorPixel *pixels, int npixels)
{
XcursorPixel p;
XcursorPixel red, green, blue;
int n = npixels;
blue = green = red = 0;
while (n--)
{
p = *pixels++;
red += (p >> 16) & 0xff;
green += (p >> 8) & 0xff;
blue += (p >> 0) & 0xff;
}
if (!n)
return 0;
return (0xff << 24) | ((red/npixels) << 16) | ((green/npixels) << 8) | (blue/npixels);
}
typedef struct XcursorCoreCursor {
XImage *src_image;
XImage *msk_image;
XColor on_color;
XColor off_color;
} XcursorCoreCursor;
static Bool
_XcursorHeckbertMedianCut (const XcursorImage *image, XcursorCoreCursor *core)
{
XImage *src_image = core->src_image, *msk_image = core->msk_image;
int npixels = image->width * image->height;
int ncolors;
int n;
XcursorPixel *po, *pn, *pc;
XcursorPixel p;
XcursorPixel red, green, blue, alpha;
XcursorPixel max_red, min_red, max_green, min_green, max_blue, min_blue;
XcursorPixel *temp, *pixels, *colors;
int split;
XcursorPixel leftColor, centerColor, rightColor;
int (*compare) (const void *, const void *);
int x, y;
/*
* Temp space for converted image and converted colors
*/
temp = malloc (npixels * sizeof (XcursorPixel) * 2);
if (!temp)
return False;
pixels = temp;
colors = pixels + npixels;
/*
* Convert to 2-value alpha and build
* array of opaque color values and an
*/
po = image->pixels;
pn = pixels;
pc = colors;
max_blue = max_green = max_red = 0;
min_blue = min_green = min_red = 255;
n = npixels;
while (n--)
{
p = *po++;
alpha = (p >> 24) & 0xff;
red = (p >> 16) & 0xff;
green = (p >> 8) & 0xff;
blue = (p >> 0) & 0xff;
if (alpha >= 0x80)
{
red = red * 255 / alpha;
green = green * 255 / alpha;
blue = blue * 255 / alpha;
if (red < min_red) min_red = red;
if (red > max_red) max_red = red;
if (green < min_green) min_green = green;
if (green > max_green) max_green = green;
if (blue < min_blue) min_blue = blue;
if (blue > max_blue) max_blue = blue;
p = ((0xff << 24) | (red << 16) |
(green << 8) | (blue << 0));
*pc++ = p;
}
else
p = 0;
*pn++ = p;
}
ncolors = pc - colors;
/*
* Compute longest dimension and sort
*/
if ((max_green - min_green) >= (max_red - min_red) &&
(max_green - min_green) >= (max_blue - min_blue))
compare = _XcursorCompareGreen;
else if ((max_red - min_red) >= (max_blue - min_blue))
compare = _XcursorCompareRed;
else
compare = _XcursorCompareBlue;
qsort (colors, ncolors, sizeof (XcursorPixel), compare);
/*
* Compute average colors on both sides of the cut
*/
split = ncolors >> 1;
leftColor = _XcursorAverageColor (colors, split);
centerColor = colors[split];
rightColor = _XcursorAverageColor (colors + split, ncolors - split);
/*
* Select best color for each pixel
*/
pn = pixels;
for (y = 0; y < image->height; y++)
for (x = 0; x < image->width; x++)
{
p = *pn++;
if (p & 0xff000000)
{
XPutPixel (msk_image, x, y, 1);
if ((*compare) (&p, ¢erColor) >= 0)
XPutPixel (src_image, x, y, 0);
else
XPutPixel (src_image, x, y, 1);
}
else
{
XPutPixel (msk_image, x, y, 0);
XPutPixel (src_image, x, y, 0);
}
}
free (temp);
_XcursorPixelToColor (rightColor, &core->off_color);
_XcursorPixelToColor (leftColor, &core->on_color);
return True;
}
#if 0
#define DITHER_DIM 4
static XcursorPixel orderedDither[4][4] = {
{ 1, 9, 3, 11 },
{ 13, 5, 15, 7 },
{ 4, 12, 2, 10 },
{ 16, 8, 14, 6 }
};
#else
#define DITHER_DIM 2
static XcursorPixel orderedDither[2][2] = {
{ 1, 3, },
{ 4, 2, },
};
#endif
#define DITHER_SIZE ((sizeof orderedDither / sizeof orderedDither[0][0]) + 1)
static Bool
_XcursorBayerOrderedDither (const XcursorImage *image, XcursorCoreCursor *core)
{
int x, y;
XcursorPixel *pixel, p;
XcursorPixel a, i, d;
pixel = image->pixels;
for (y = 0; y < image->height; y++)
for (x = 0; x < image->width; x++)
{
p = *pixel++;
a = ((p >> 24) * DITHER_SIZE + 127) / 255;
i = (_XcursorPixelBrightness (p) * DITHER_SIZE + 127) / 255;
d = orderedDither[y&(DITHER_DIM-1)][x&(DITHER_DIM-1)];
if (a > d)
{
XPutPixel (core->msk_image, x, y, 1);
if (i > d)
XPutPixel (core->src_image, x, y, 0); /* white */
else
XPutPixel (core->src_image, x, y, 1); /* black */
}
else
{
XPutPixel (core->msk_image, x, y, 0);
XPutPixel (core->src_image, x, y, 0);
}
}
core->on_color.red = 0;
core->on_color.green = 0;
core->on_color.blue = 0;
core->off_color.red = 0xffff;
core->off_color.green = 0xffff;
core->off_color.blue = 0xffff;
return True;
}
static Bool
_XcursorFloydSteinberg (const XcursorImage *image, XcursorCoreCursor *core)
{
int *aPicture, *iPicture, *aP, *iP;
XcursorPixel *pixel, p;
int aR, iR, aA, iA;
int npixels = image->width * image->height;
int n;
int right = 1;
int belowLeft = image->width - 1;
int below = image->width;
int belowRight = image->width + 1;
int iError, aError;
int iErrorRight, aErrorRight;
int iErrorBelowLeft, aErrorBelowLeft;
int iErrorBelow, aErrorBelow;
int iErrorBelowRight, aErrorBelowRight;
int x, y;
int max_inten, min_inten, mean_inten;
iPicture = malloc (npixels * sizeof (int) * 2);
if (!iPicture)
return False;
aPicture = iPicture + npixels;
/*
* Compute raw gray and alpha arrays
*/
pixel = image->pixels;
iP = iPicture;
aP = aPicture;
n = npixels;
max_inten = 0;
min_inten = 0xff;
while (n--)
{
p = *pixel++;
*aP++ = (int) (p >> 24);
iR = (int) _XcursorPixelBrightness (p);
if (iR > max_inten) max_inten = iR;
if (iR < min_inten) min_inten = iR;
*iP++ = iR;
}
/*
* Draw the image while diffusing the error
*/
iP = iPicture;
aP = aPicture;
mean_inten = (max_inten + min_inten + 1) >> 1;
for (y = 0; y < image->height; y++)
for (x = 0; x < image->width; x++)
{
aR = *aP;
iR = *iP;
if (aR >= 0x80)
{
XPutPixel (core->msk_image, x, y, 1);
aA = 0xff;
}
else
{
XPutPixel (core->msk_image, x, y, 0);
aA = 0x00;
}
if (iR >= mean_inten)
{
XPutPixel (core->src_image, x, y, 0);
iA = max_inten;
}
else
{
XPutPixel (core->src_image, x, y, 1);
iA = min_inten;
}
iError = iR - iA;
aError = aR - aA;
iErrorRight = (iError * 7) >> 4;
iErrorBelowLeft = (iError * 3) >> 4;
iErrorBelow = (iError * 5) >> 4;
iErrorBelowRight = (iError - iErrorRight -
iErrorBelowLeft - iErrorBelow);
aErrorRight = (aError * 7) >> 4;
aErrorBelowLeft = (aError * 3) >> 4;
aErrorBelow = (aError * 5) >> 4;
aErrorBelowRight = (aError - aErrorRight -
aErrorBelowLeft - aErrorBelow);
if (x < image->width - 1)
{
iP[right] += iErrorRight;
aP[right] += aErrorRight;
}
if (y < image->height - 1)
{
if (x)
{
iP[belowLeft] += iErrorBelowLeft;
aP[belowLeft] += aErrorBelowLeft;
}
iP[below] += iErrorBelow;
aP[below] += aErrorBelow;
if (x < image->width - 1)
{
iP[belowRight] += iErrorBelowRight;
aP[belowRight] += aErrorBelowRight;
}
}
aP++;
iP++;
}
free (iPicture);
core->on_color.red =
core->on_color.green =
core->on_color.blue = (min_inten | min_inten << 8);
core->off_color.red =
core->off_color.green =
core->off_color.blue = (max_inten | max_inten << 8);
return True;
}
static Bool
_XcursorThreshold (const XcursorImage *image, XcursorCoreCursor *core)
{
XcursorPixel *pixel, p;
int x, y;
/*
* Draw the image, picking black for dark pixels and white for light
*/
pixel = image->pixels;
for (y = 0; y < image->height; y++)
for (x = 0; x < image->width; x++)
{
p = *pixel++;
if ((p >> 24) >= 0x80)
{
XPutPixel (core->msk_image, x, y, 1);
if (_XcursorPixelBrightness (p) > 0x80)
XPutPixel (core->src_image, x, y, 0);
else
XPutPixel (core->src_image, x, y, 1);
}
else
{
XPutPixel (core->msk_image, x, y, 0);
XPutPixel (core->src_image, x, y, 0);
}
}
core->on_color.red =
core->on_color.green =
core->on_color.blue = 0;
core->off_color.red =
core->off_color.green =
core->off_color.blue = 0xffff;
return True;
}
Cursor
XcursorImageLoadCursor (Display *dpy, const XcursorImage *image)
{
Cursor cursor;
#if RENDER_MAJOR > 0 || RENDER_MINOR >= 5
if (XcursorSupportsARGB (dpy))
{
XImage ximage;
int screen = DefaultScreen (dpy);
Pixmap pixmap;
Picture picture;
GC gc;
XRenderPictFormat *format;
ximage.width = image->width;
ximage.height = image->height;
ximage.xoffset = 0;
ximage.format = ZPixmap;
ximage.data = (char *) image->pixels;
ximage.byte_order = nativeByteOrder ();
ximage.bitmap_unit = 32;
ximage.bitmap_bit_order = ximage.byte_order;
ximage.bitmap_pad = 32;
ximage.depth = 32;
ximage.bits_per_pixel = 32;
ximage.bytes_per_line = image->width * 4;
ximage.red_mask = 0xff0000;
ximage.green_mask = 0x00ff00;
ximage.blue_mask = 0x0000ff;
ximage.obdata = NULL;
if (!XInitImage (&ximage))
return None;
pixmap = XCreatePixmap (dpy, RootWindow (dpy, screen),
image->width, image->height, 32);
gc = XCreateGC (dpy, pixmap, 0, NULL);
XPutImage (dpy, pixmap, gc, &ximage,
0, 0, 0, 0, image->width, image->height);
XFreeGC (dpy, gc);
format = XRenderFindStandardFormat (dpy, PictStandardARGB32);
picture = XRenderCreatePicture (dpy, pixmap, format, 0, NULL);
XFreePixmap (dpy, pixmap);
cursor = XRenderCreateCursor (dpy, picture,
image->xhot, image->yhot);
XRenderFreePicture (dpy, picture);
}
else
#endif
{
XcursorDisplayInfo *info = _XcursorGetDisplayInfo (dpy);
int screen = DefaultScreen (dpy);
XcursorCoreCursor core;
Pixmap src_pixmap, msk_pixmap;
GC gc;
XGCValues gcv;
core.src_image = XCreateImage (dpy, NULL, 1, ZPixmap,
0, NULL, image->width, image->height,
32, 0);
core.src_image->data = Xmalloc (image->height *
core.src_image->bytes_per_line);
core.msk_image = XCreateImage (dpy, NULL, 1, ZPixmap,
0, NULL, image->width, image->height,
32, 0);
core.msk_image->data = Xmalloc (image->height *
core.msk_image->bytes_per_line);
switch (info->dither) {
case XcursorDitherThreshold:
if (!_XcursorThreshold (image, &core))
return 0;
break;
case XcursorDitherMedian:
if (!_XcursorHeckbertMedianCut (image, &core))
return 0;
break;
case XcursorDitherOrdered:
if (!_XcursorBayerOrderedDither (image, &core))
return 0;
break;
case XcursorDitherDiffuse:
if (!_XcursorFloydSteinberg (image, &core))
return 0;
break;
default:
return 0;
}
/*
* Create the cursor
*/
src_pixmap = XCreatePixmap (dpy, RootWindow (dpy, screen),
image->width, image->height, 1);
msk_pixmap = XCreatePixmap (dpy, RootWindow (dpy, screen),
image->width, image->height, 1);
gcv.foreground = 1;
gcv.background = 0;
gc = XCreateGC (dpy, src_pixmap,
GCForeground|GCBackground,
&gcv);
XPutImage (dpy, src_pixmap, gc, core.src_image,
0, 0, 0, 0, image->width, image->height);
XPutImage (dpy, msk_pixmap, gc, core.msk_image,
0, 0, 0, 0, image->width, image->height);
XFreeGC (dpy, gc);
#ifdef DEBUG_IMAGE
_XcursorDumpColor (&core.on_color, "on_color");
_XcursorDumpColor (&core.off_color, "off_color");
_XcursorDumpImage (core.src_image);
_XcursorDumpImage (core.msk_image);
#endif
XDestroyImage (core.src_image);
XDestroyImage (core.msk_image);
cursor = XCreatePixmapCursor (dpy, src_pixmap, msk_pixmap,
&core.on_color, &core.off_color,
image->xhot, image->yhot);
XFreePixmap (dpy, src_pixmap);
XFreePixmap (dpy, msk_pixmap);
}
return cursor;
}
XcursorCursors *
XcursorImagesLoadCursors (Display *dpy, const XcursorImages *images)
{
XcursorCursors *cursors = XcursorCursorsCreate (dpy, images->nimage);
int n;
if (!cursors)
return NULL;
for (n = 0; n < images->nimage; n++)
{
cursors->cursors[n] = XcursorImageLoadCursor (dpy, images->images[n]);
if (!cursors->cursors[n])
{
XcursorCursorsDestroy (cursors);
return NULL;
}
cursors->ncursor++;
}
return cursors;
}
Cursor
XcursorImagesLoadCursor (Display *dpy, const XcursorImages *images)
{
Cursor cursor;
if (images->nimage == 1 || !XcursorSupportsAnim (dpy))
cursor = XcursorImageLoadCursor (dpy, images->images[0]);
else
{
XcursorCursors *cursors = XcursorImagesLoadCursors (dpy, images);
XAnimCursor *anim;
int n;
if (!cursors)
return 0;
anim = malloc (cursors->ncursor * sizeof (XAnimCursor));
if (!anim)
{
XcursorCursorsDestroy (cursors);
return 0;
}
for (n = 0; n < cursors->ncursor; n++)
{
anim[n].cursor = cursors->cursors[n];
anim[n].delay = images->images[n]->delay;
}
cursor = XRenderCreateAnimCursor (dpy, cursors->ncursor, anim);
XcursorCursorsDestroy(cursors);
free (anim);
}
#if defined HAVE_XFIXES && XFIXES_MAJOR >= 2
if (images->name)
XFixesSetCursorName (dpy, cursor, images->name);
#endif
return cursor;
}
Cursor
XcursorFilenameLoadCursor (Display *dpy, const char *file)
{
int size = XcursorGetDefaultSize (dpy);
XcursorImages *images = XcursorFilenameLoadImages (file, size);
Cursor cursor;
if (!images)
return None;
cursor = XcursorImagesLoadCursor (dpy, images);
XcursorImagesDestroy (images);
return cursor;
}
XcursorCursors *
XcursorFilenameLoadCursors (Display *dpy, const char *file)
{
int size = XcursorGetDefaultSize (dpy);
XcursorImages *images = XcursorFilenameLoadImages (file, size);
XcursorCursors *cursors;
if (!images)
return NULL;
cursors = XcursorImagesLoadCursors (dpy, images);
XcursorImagesDestroy (images);
return cursors;
}
/*
* Stolen from XCreateGlyphCursor (which we cruelly override)
*/
Cursor
_XcursorCreateGlyphCursor(Display *dpy,
Font source_font,
Font mask_font,
unsigned int source_char,
unsigned int mask_char,
XColor _Xconst *foreground,
XColor _Xconst *background)
{
Cursor cid;
register xCreateGlyphCursorReq *req;
LockDisplay(dpy);
GetReq(CreateGlyphCursor, req);
cid = req->cid = XAllocID(dpy);
req->source = source_font;
req->mask = mask_font;
req->sourceChar = source_char;
req->maskChar = mask_char;
req->foreRed = foreground->red;
req->foreGreen = foreground->green;
req->foreBlue = foreground->blue;
req->backRed = background->red;
req->backGreen = background->green;
req->backBlue = background->blue;
UnlockDisplay(dpy);
SyncHandle();
return (cid);
}
/*
* Stolen from XCreateFontCursor (which we cruelly override)
*/
Cursor
_XcursorCreateFontCursor (Display *dpy, unsigned int shape)
{
static XColor _Xconst foreground = { 0, 0, 0, 0 }; /* black */
static XColor _Xconst background = { 0, 65535, 65535, 65535 }; /* white */
/*
* the cursor font contains the shape glyph followed by the mask
* glyph; so character position 0 contains a shape, 1 the mask for 0,
* 2 a shape, etc. <X11/cursorfont.h> contains hash define names
* for all of these.
*/
if (dpy->cursor_font == None)
{
dpy->cursor_font = XLoadFont (dpy, CURSORFONT);
if (dpy->cursor_font == None)
return None;
}
return _XcursorCreateGlyphCursor (dpy, dpy->cursor_font, dpy->cursor_font,
shape, shape + 1, &foreground, &background);
}
|