[comp.windows.open-look] Xor'ing of colors problems

brown@ftms.UUCP (Vidiot) (02/21/91)

We are using OpenLook under SunOS 4.1.  The programmer is using the GXxor
function in the graphics context for Xlib calls.  We get the following results:

	draw color	background	result		expect

	black		black		white		black
	white		white		tan(?)		black
	red		red		white		black
	red		blue		aquamarine(?)/	magenta
					cyan
	blue		blue		white		black

Can someone explain why we are not getting the expected results?  Is there
a formula that can be used to determine ahead of time what the results will
be?

Thanks in advance.
-- 
harvard\
  ucbvax!uwvax!astroatc!ftms!brown
rutgers/
INTERNET: spool.cs.wisc.edu!astroatc!ftms!brown

whaley@spectre.pa.dec.com (Ken Whaley) (02/22/91)

>	draw color	background	result		expect
>
>	black		black		white		black
>	white		white		tan(?)		black
>	red		red		white		black
>	red		blue		aquamarine(?)/	magenta
>					cyan
>	blue		blue		white		black
>
>Can someone explain why we are not getting the expected results?  Is there
>a formula that can be used to determine ahead of time what the results will
>be?

Ah, color XORing, my favorite topic! :)  Maybe this should be added to the FAQ
list?   I'd be glad to write the entry.  Anyway, here goes.

Basically, if you care about what colors result from XORing pixel values in
your application, you must make certain preparations. Otherwise, you'll get
apparently random results (as you have) which depend on what colors are
already in the colormap. 

As an example, let's consider the 8-plane PseudoColor case, which is a common
color system that most people have.  This colormap has 256 entries, each of
which can be set to produce any color your system is capable of displaying.
Suppose your colors are red and blue and suppose they have pixel values
3 and 5, and your GC has foreground==red, background==blue, function==XOR.
red XOR red == 3 XOR 3 == 0, so whatever color has pixel value zero is
displayed when you draw over red with red.  

So, since combining equal colors gives you white (except for white+white
= tan, which means you must have more than one pixel value for white), your
white has pixel value 0.

Now, drawing blue on red or red on blue results in pixel value 3 XOR 5,
or 6.  Whatever color is at pixel value 6 in your colormap is displayed
here.  The strange colors you're seeing have a pixel value equal to that of
the XOR of the two colors you're combining, and might have been set by a 
different application which allocated colors from the default colormap.

In general, if you'd like to use XOR to flip between two arbitrary colors (fg
and bg), set up an XOR-ing GC with function = XOR, foreground = fg ^ bg, and
background = 0 ("^" is XOR in C).  To apply this to our case, foreground =
red ^ blue (== 3 ^ 5 == 6). Then, drawing over red with this GC results in:
	foreground ^ red == 6 ^ 3 == 5 == blue
and drawing over blue results in:
	foreground ^ blue == 6 ^ 5 == 3 == red.		 Voila!

It's a little more complicated when you want to combine colors rather than
switch between them, and if many colors are involved, it gets yet more
complicated.   Basically, you want to use XAllocColorCells with one pixel and
several planes to allocate consecutive color planes, then to store colors into
specific pixel values so that XORing works the way you want.  If you want to
use "overlay planes" instead of XORing, you call XAllocColorCells with several
base colors and the number of overlay planes you want, and then set up colors
for each combination of base pixel value + overlay plane (+ overlay plane....).

Rough Example: using XOR to combine colors (in a reversible way, i.e., drawing
again erases the first draw) with XAllocColorCells (one color, 3 planes)

0 is your window background color
1, 2, and 4 are your drawing colors
XOR table:
	draw color	background color	result
	---------       ----------------	-----
	1		0			1	(1^1 == 0, also)
	1		2			3	(1^3 == 2, also)
	1		4			5	(1^5 == 4, also...etc)
	2		0			2
	2		1			3
	2		4			6
	4		0			4
	4		1			5
	4		2			6

Using this table, you allocate 0, 1, 2, 3, 4, 5, 6 to whatever you want them
to be, and you can "rubber band" with either color 1, 2, or 4.

Now, you don't really get these values literally; you get them allocated
to you as consecutive planes + a base pixel, which means the actual pixel
values might be (in binary, assuming a base pixel value of 101):

	0---0101

where "---" ranges from 000 to 111 in binary.  These values correspond to the
numbers used in the table above.  You select your drawing color through
the plane_mask, and set your foreground to 0xff (all ones).

Below is an example program that calls XAllocColorCells to get 3 consecutive
color planes, sets up the colors, and draws each of the drawing colors 
on top of each other and the combined colors.

It comes up and displays horizontal lines of the drawing colors (1, 2, 4)
and the combined colors (3, 5, 6) against a background of color 0.

When you press a key in the window, it draws the drawing colors vertically
in XOR mode.  Press a key again and they vanish (are drawn again), thanks
to the magic of XOR.  Note that there are a 3 colors here (marked "X" )
that won't occur if you use a "draw then erase" (as in rubber banding)
technique (which is what most people use XOR for in the first place).

	  1 2 4
	1 
	2
	4
	3     X
	5   X
	6 X
	0

Use the table by looking at the leftmost color on each row, and choosing
a column (corresponds to drawing color...look at last row to see which one).

Whew!  I hope that this is helpful!

	Ken

Save in "file.c", and compile with "cc -o colortest file.c -lX11"

---------------------CUT HERE-----------------------
#include <X11/Xlib.h>
#include <X11/X.h>

#define PIXEL(x)	(((x) << shift) | pixel)

unsigned long plane_masks[6];
unsigned long pixel;
Status result;
int shift, i;
GC gc;
XEvent e;
Display *d;
int scr;
Colormap cm;
Window w, r;

main()
{
	int mode = 0;
	int flags = {DoRed | DoGreen | DoBlue};
	d = XOpenDisplay("");
	scr = XDefaultScreen(d);
	cm = DefaultColormap(d, scr);
	r = DefaultRootWindow(d);
	result = XAllocColorCells(d, cm, True, plane_masks, 3, &pixel, 1);

	if (!result)
	{
		printf("allocation failed.\n");
		exit(1);
	}

	/* find the location of the first bit plane */
	for (shift = 0; plane_masks[0] != (1 << shift); shift++)
		;

	XStoreNamedColor(d, cm, "White",	PIXEL(0), flags);
	XStoreNamedColor(d, cm, "Red",		PIXEL(1), flags);
	XStoreNamedColor(d, cm, "Blue",		PIXEL(2), flags);
	XStoreNamedColor(d, cm, "Magenta",	PIXEL(3), flags);
	XStoreNamedColor(d, cm, "Black",	PIXEL(4), flags);
	XStoreNamedColor(d, cm, "Yellow",	PIXEL(5), flags);
	XStoreNamedColor(d, cm, "Green",	PIXEL(6), flags);

	w = XCreateSimpleWindow(d, r, 10, 10, 400, 700, 0, 0, 
			PIXEL(0));
	gc = DefaultGC(d, scr);

	XSelectInput(d, w, ExposureMask | KeyPressMask);
	XMapWindow(d, w);

	while(1)
	{
		XNextEvent(d, &e);	

		switch(e.type)
		{
			case Expose:
				setup();
				if (mode)
					combine();
				break;
			case KeyPress:
				mode ^= 1;
				combine();
				break;
		}
	}
}
setup()
{
	XClearWindow(d, w);

	XSetForeground(d, gc, 0xff);
	XSetFunction(d, gc, GXxor);

	/* draw "drawing" colors horizontally against the
	 * background color.
	 */
	for (i = 0; i < 3; i++)
	{
		XSetPlaneMask(d, gc, plane_masks[i]);
		XFillRectangle(d, w, gc, 0, i*100, 400, 100);
	}

	XSetPlaneMask(d, gc, 0xff);
	XSetFunction(d, gc, GXcopy);

	/* draw the "combined" colors to show that the drawing colors
	 * revert them back to the original colors.
	 */
	XSetForeground(d, gc, PIXEL(3));
	XFillRectangle(d, w, gc, 0, 300, 400, 100);
	XSetForeground(d, gc, PIXEL(5));
	XFillRectangle(d, w, gc, 0, 400, 400, 100);
	XSetForeground(d, gc, PIXEL(6));
	XFillRectangle(d, w, gc, 0, 500, 400, 100);
}

combine()
{
	XSetForeground(d, gc, 0xff);
	XSetFunction(d, gc, GXxor);

	/* Now draw the "drawing" colors over each of the drawing
	 * and combined colors to show the combined ones go back
	 * to the original corresponding drawing color
	 */
	for (i = 0; i < 3; i++)
	{
		XSetPlaneMask(d, gc, plane_masks[i]);
		XFillRectangle(d, w, gc, (i+1)*100, 0, 100, 700);
	}
}
--
^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^
                                    /----\//                             
                                   /     //                             
Ken Whaley                        /  :) // \      whaley@atd.dec.com
                                  | :-)//  |                             
                                  |   //;-)|                             
Help stamp out smileys!           |;)//    |                             
                                  | // :)  |                             
                                   // :-) /                              
                                  //\____/                               
^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^v^

chuck@Morgan.COM (Chuck Ocheret) (02/28/91)

> We are using OpenLook under SunOS 4.1.  The programmer is using the GXxor
> function in the graphics context for Xlib calls.  We get the following results:
> 
>         draw color      background      result          expect
> 
>         black           black           white           black
>         white           white           tan(?)          black
>         red             red             white           black
>         red             blue            aquamarine(?)/  magenta
>                                         cyan
>         blue            blue            white           black
> 
> Can someone explain why we are not getting the expected results?  Is there
> a formula that can be used to determine ahead of time what the results will
> be?

Xoring pixels operates on bits, not on names.  Depending on your server and
the Visual you are using you cannot expect particular colors to have
particular pixel values.  Basically, colors can be allocated to arbitrary
cells in the colormap.  To accomplish what it looks like you are trying to
do you should be Xor drawing with your foreground pixel set to a value
which is an Xor of the displayed pixel and the desired color's pixel.

For example, let's say that you allocate the following colors on a 4 bit
deep display.

color name	pixel allocated(treat these as completely arbitrary)
----------	---------------
red		0x5
blue		0xf
magenta		0x8
green		0xa

Let's also say that most of your window has been drawn in blue and you
want to do Xor drawing (e.g. rubber band line) that appears magenta.
Don't try to draw in red 0x5 (Xor does not do color blending unless you
are real lucky with your color map).  If you were to Xor draw in red
the blue pixels will get changed from 0xf to 0xa = green.

Draw with the pixel value set to blue^magenta = 0xf^0x8 = 0x7 (^ is
Xor).  The funny thing is that you may never have allocated color cell
0x7.  When you Xor draw with 0x7 over the blue 0xf background, the pixels
are changed to magenta 0x8.

~chuck
-- 
+--------------------+             Chuck Ocheret             +---------------+
|chuck@fid.Morgan.COM|       Morgan Stanley & Co., Inc.      |(212) 703-4474 |
|    Duty now ...    |19th Floor, 1251 Avenue of the Americas|for the future.|
+--------------------+      New York, N.Y.  10020 USA        +---------------+