Bitwise pattern generator
Type an expression in x and y, the column and row of each pixel, and see the picture it draws. Hover or arrow over a pixel to see the AND, XOR or OR that made it, bit by bit.
t is a frame number from 0 to 255 you can use in the expression. Animate steps it at the speed set here.
Use x, y and t, whole numbers (decimal, 0x1F, 0b101), ~ - * / % + << >> & ^ | with brackets, and the functions abs, min, max, sqrt, sin, cos and atan2.
Pixel x 45, y 22
Value 59 = 00111011
A is x, B is y. XOR of the low 8 bits, column by column:
| A | 0 | 0 | 1 | 0 | 1 | 1 | 0 | 1 |
|---|---|---|---|---|---|---|---|---|
| B | 0 | 0 | 0 | 1 | 0 | 1 | 1 | 0 |
| XOR | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 1 |
Colour #a104db
What the expression can say
Every pixel is worked out on its own, with x as its column and y as its row, both counted from the top left. Numbers are 32-bit two's complement integers, so adding past 2,147,483,647 wraps round to negative, and the picture shows the low 8 bits of the result: in greyscale, 0 is black and 255 is white.
| Operator or function | Name | Notes |
|---|---|---|
| ~ - + | Unary | Flip every bit, negate, or leave alone. Binds tightest. |
| * / % | Multiply, divide, remainder | Whole numbers only. / drops the fraction, and / or % by zero give 0. |
| + - | Add, subtract | Wraps at 32 bits. |
| << >> | Shift left, right | The right shift keeps the sign. The count uses its low 5 bits. |
| & | AND | A bit is 1 only where both inputs have a 1. |
| ^ | XOR | A bit is 1 where the inputs differ. |
| | | OR | A bit is 1 where either input has a 1. Binds loosest. |
| abs(a) | Absolute value | The most negative 32-bit number has no positive twin, so it stays negative. |
| min(a, b) max(a, b) | Smaller, larger | Of two values. |
| sqrt(a) | Square root | Rounded down to a whole number; 0 for zero or less. sqrt(x * x + y * y) is distance from the corner. |
| sin(a) cos(a) | Sine, cosine | a counts a whole turn as 256, so only its low 8 bits matter. Results are whole numbers from -127 to 127: sin(64) is 127, sin(192) is -127, and cos(a) is sin(a + 64). |
| atan2(y, x) | Direction | The angle of the point (x, y) from the middle, as a whole turn of 256: 0 along +x, 64 along +y (down the picture), 128 along -x, 192 along -y. Rounded to the nearest step; atan2(0, 0) is 0. Note that y comes first. Use atan2(y - 64, x - 64) for the angle round the centre of a 128 grid. |
Rows run from tightest to loosest, as in C. So x + y & 8 is (x + y) & 8, and ^ is XOR here, never a power.
Starting points
Each thumbnail is drawn by the same engine as the picture above. Press the name to load it.
| Picture | Expression | What to notice |
|---|---|---|
x ^ y |
The classic. Each quadrant is a copy of the whole at half size. | |
x & y |
Black where x and y share no set bit: the Sierpinski triangle is the black cells. | |
((x & y) - 1) >> 31 |
White exactly where x & y is 0, so the triangle stands out. | |
x | y |
The AND picture turned half way round and inverted, because x | y is 127 minus ((127 - x) & (127 - y)) on this grid. | |
x & y & 8 |
Only bit 3 can survive, so each cell is on or off. Shown as bit 3, since 8 is almost black in greyscale. | |
x * y |
Only the low 8 bits of x times y are drawn, so it wraps into curved bands. | |
(x * y) >> 8 |
The next 8 bits up. The curves are hyperbolas, x times y = constant. | |
(x * x + y * y) >> 6 |
Squared distance from the corner, divided down; wrapping makes the rings. | |
x % y |
Row 0 divides by zero, which is defined here as 0. | |
x ^ y |
One bit plane of the XOR texture: on or off, nothing between. | |
x ^ y , x | y , x & y |
XOR for red, OR for green and AND for blue. | |
(x ^ y) + t |
Adds the frame number, so the colours cycle as t moves. | |
128 + sin(x * 3 + t * 2) / 2 + sin(y * 4 - t * 3) / 2 , 128 + sin((x + y) * 2 + t * 4) / 2 + sin(x * 5) / 2 , 128 + sin(sqrt((x - 64) * (x - 64) + (y - 64) * (y - 64)) * 6 - t * 4) / 2 + sin(y * 3 + t) / 2 |
Each channel adds two sines with different speeds and directions, so the colours drift against each other. | |
sqrt((x - 64) * (x - 64) + (y - 64) * (y - 64)) * 6 - t * 4 |
Distance from the middle, times 6, minus 4t. A ring is one value, so as t grows the rings move outwards. | |
sin(sqrt((x - 64) * (x - 64) + (y - 64) * (y - 64)) * 8 - t * 6) + 128 |
The sine of the distance, moved up by 128 so that it runs from 1 to 255 and not from -127 to 127. | |
y * 2 + sin(x * 4 + t * 4) / 2 |
The sine bends the bands that y * 2 would draw, and t slides it sideways. | |
sqrt((x - 32 - (t >> 1)) * (x - 32 - (t >> 1)) + (y - 64) * (y - 64)) ^ sqrt((x - 96) * (x - 96) + (y - 64) * (y - 64)) |
The distance to two points, XORed. The left point slides right as t grows, and the fringes between them change. | |
((x & y) - t) >> 31 |
White wherever x & y is below t. Cells with a small x & y fill in first; from t = 128 on, every cell is white. | |
sqrt((x - 64) * (x - 64) + (y - 64) * (y - 64)) ^ (t + x + y) |
Distance from the middle XOR a diagonal that slides with t. | |
(abs(x - 64) ^ abs(y - 64)) * 4 - t * 2 |
abs(x - 64) folds the picture about the middle column, and the same for rows, so every quadrant is a mirror of the others. The XOR of the folded coordinates is multiplied by 4 so it wraps, and 2t is subtracted so the colours cycle. | |
atan2(y - 64, x - 64) * 3 + sqrt((x - 64) * (x - 64) + (y - 64) * (y - 64)) * 4 - t * 4 |
The angle round the middle (0 to 255) times 3, plus 4 times the distance, minus 4t. Three colour cycles fit round one turn, so there are three arms, and the distance term shears them into spirals that move outwards as t grows. | |
(atan2(y - 64, x - 64) * 2) ^ (6000 / (sqrt((x - 64) * (x - 64) + (y - 64) * (y - 64)) + 1) - t * 2) |
The angle times 2, XOR 6000 divided by the distance. The division is steep near the middle, so the rings crowd together there, like looking down a tunnel. Subtracting 2t makes the ring pattern drift towards the middle. | |
128+sin(atan2(y - 64, x - 64)*2+sqrt((x - 64) * (x - 64) + (y - 64) * (y - 64))*2-t*4+0) , 128+sin(atan2(y - 64, x - 64)*2+sqrt((x - 64) * (x - 64) + (y - 64) * (y - 64))*2-t*4+85) , 128+sin(atan2(y - 64, x - 64)*2+sqrt((x - 64) * (x - 64) + (y - 64) * (y - 64))*2-t*4+170) |
All three channels are 128 plus the sine of 2 times the angle, plus 2 times the distance, minus 4t. They are offset by 0, 85 and 170, a third of a turn apart, so the hues run round the colour wheel. | |
(((x - 64) * cos(t) - (y - 64) * sin(t)) >> 7) ^ (((x - 64) * sin(t) + (y - 64) * cos(t)) >> 7) |
Turns x - 64 and y - 64 through the angle t with sin and cos (a whole turn is 256, so t = 0 to 255 is one full rotation), shifts right by 7 to undo the scale of about 128, then XORs the two. The XOR texture rotates about the middle. | |
((x - 64) * 64 / (y - 40) ^ 2048 / (y - 40) + t * 4) & ((40 - y) >> 31) |
Dividing by y - 40 gives a perspective floor with its horizon on row 40: x - 64 and 2048 divided by y - 40 are the position across and the depth. Their XOR has bit 6 set on alternate squares, which is shown as the bit. Adding 4t to the depth slides the squares towards you; the last term blanks the rows above the horizon. | |
((x - 64) * (160 + sin(t)) >> 7) ^ ((y - 64) * (160 + sin(t)) >> 7) |
The XOR texture of x - 64 and y - 64 scaled by (160 + sin(t)) / 128, between about 0.26 and 2.2, so it zooms in and out about the middle. Because sin repeats every 256, the loop is seamless. | |
(((x * (100 + sin(t) / 2) >> 7) & (y * (100 + sin(t) / 2) >> 7)) - 1) >> 31 |
The coordinates are scaled by between about 0.3 and 1.3 before the AND, then white where it is 0. With a small scale the triangle is magnified towards the top left corner; with a large one it breaks into finer copies. | |
min(255, 40000/((x-64-sin(t)/3)*(x-64-sin(t)/3)+(y-64-cos(t*2)/3)*(y-64-cos(t*2)/3)+1)+40000/((x-64-cos(t*3)/3)*(x-64-cos(t*3)/3)+(y-64-sin(t*2)/3)*(y-64-sin(t*2)/3)+1)+40000/((x-64-sin(t*2+90)/2)*(x-64-sin(t*2+90)/2)+(y-64-sin(t*3)/4)*(y-64-sin(t*3)/4)+1)) |
Each of three balls adds 40000 divided by the squared distance plus 1, using integer division, so the field falls off steeply. Where the fields add up, close balls merge into one blob; min caps the total at 255. The centres orbit with sin and cos. | |
min(255,80000/((x-64-sin(t)/4)*(x-64-sin(t)/4)+(y-64-sin(t*2)/2)*(y-64-sin(t*2)/2)+1)+80000/((x-64-cos(t)/4)*(x-64-cos(t)/4)+(y-64-sin(t*2+90)/2)*(y-64-sin(t*2+90)/2)+1)+80000/((x-64-sin(t*3)/3)*(x-64-sin(t*3)/3)+(y-64-cos(t)/2)*(y-64-cos(t)/2)+1)) |
The same sum of inverse squared distances as Metaballs, with bigger balls (80000) that rise and fall, drawn in the Sunset palette. The blobs stretch and join when they pass close to each other. | |
sin(sqrt((x-64+sin(t)/4)*(x-64+sin(t)/4)+(y-64)*(y-64))*5)+sin(sqrt((x-64)*(x-64)+(y-64+cos(t)/4)*(y-64+cos(t)/4))*5)+sin(sqrt((x-64-sin(t)/4)*(x-64-sin(t)/4)+(y-64-cos(t)/4)*(y-64-cos(t)/4))*5)+384 |
The sum of the sine of the distance from three centres that circle around the middle. The total runs from 3 to 765, so it wraps about three times, which cuts it into contour bands; the bands bend where the waves from different centres meet. | |
(255-((atan2(y - 64, x - 64)-t*2)&255))>>3&((sqrt((x - 64) * (x - 64) + (y - 64) * (y - 64))-60)>>31) , (255-((atan2(y - 64, x - 64)-t*2)&255))&((sqrt((x - 64) * (x - 64) + (y - 64) * (y - 64))-60)>>31) , (255-((atan2(y - 64, x - 64)-t*2)&255))>>3&((sqrt((x - 64) * (x - 64) + (y - 64) * (y - 64))-60)>>31) |
atan2 gives the bearing of each pixel, so (bearing - 2t) & 255 is how far behind the beam it is. Green is 255 minus that, brightest at the beam and fading behind it; red and blue are an eighth of it. The beam turns once every 128 steps, and the last term blanks everything beyond 60 pixels from the middle. | |
y*3-t*8+sin(x*6)/2+sin(x*13+t*4)/4 |
y * 3 - 8t makes bands that move upwards, as t grows; the two sines bend them sideways. It is flame-like in the Fire palette, nothing more. | |
255-min(255,abs(((x+t)&7^(((((x+t)>>3)+(y>>3)*57)*((((x+t)>>3)+(y>>3)*57)*(((x+t)>>3)+(y>>3)*57)*15731+789221)>>14)&1)*7)-(y&7))*60) |
Cells of 8 by 8 pixels. A hash of the cell number picks the direction of one diagonal line in each cell (u or its mirror 7 - u, compared with v), and the lines join into a maze, as in the old one-line 10 PRINT programs. Adding t to x scrolls it sideways. |
Why x & y is the Sierpinski triangle
Build Pascal's triangle with the usual rule, each entry the one above plus the one to its left, and keep only whether it is odd. Lay the entries on a grid so that the entry in column x and row y is C(x + y, x). On the left is that grid for 16 by 16. On the right is a light cell wherever x & y is 0. They are the same picture, and the check holds for all 256 by 256 cells.
The reason is Kummer's theorem: the highest power of 2 dividing C(x + y, x) is the number of carries when you add x and y in binary. The entry is odd when that number is 0. Adding two numbers carries at all exactly when some column has a 1 in both, which is to say when x & y is not 0. The preset ((x & y) - 1) >> 31 shows it directly: the subtraction makes a 0 negative, and the shift turns a negative number into all ones.
Why x ^ y repeats itself
Look at the top bit of x and of y in a grid whose size is a power of two. In the top left quadrant both are 0, and in the bottom right both are 1; in either case the XOR of the top bits is 0, so the quadrant is a copy of the picture one size down. In the other two quadrants exactly one is 1, so the top bit of the result is set: the same copy, brighter by half the grid size. Here are the four bit planes of x ^ y on a 16 by 16 grid, from bit 3 down to bit 0.
A bit plane is one bit of every pixel's value, shown as on or off. Bit 3 splits the grid into four quadrants, bit 2 splits each of those again, and bit 0 is a checkerboard. Use One bit above to look at any plane of any expression.
Different expressions can be one picture. (x ^ y) & (x | y), (x | y) - (x & y), (x + y) - 2 * (x & y) all draw exactly x ^ y, and the page has compared every pixel to prove it.
Animating t
The slider sets t to a value from 0 to 255 and the Animate button steps it, wrapping from 255 to 0, at the number of steps a second set by the Speed slider. Try (x ^ y) + t in a palette, or (x ^ t) & y in One bit. The starting points marked as moving, such as Plasma and Ripples, begin animating when you load them. Animation never starts by itself otherwise, and it is switched off if your system asks for reduced motion.
The moving starting points use t in a few ways. Added to a coordinate or a distance, as in x + t, it scrolls the picture. Inside sin or cos it is a position or angle in a repeating cycle: sin(t) moves the zoom in XOR zoom, and the angle in Spinning XOR is t itself, so frames 0 to 255 are one full turn. Because sin and cos repeat every 256, those pictures join up when t wraps from 255 to 0; ones that just add t, such as Scrolling maze, jump at that point. Subtracting t from a distance or an angle, as in Ripples and Spiral arms, sends the rings or arms outwards or round, and the sign decides the direction.
Common mistakes
- Everything looks black. At size 16 the largest x ^ y is 15, which is nearly black in 256 shades. Multiply it up, as in (x ^ y) * 16, or pick a palette.
- Using ^ as a power. It is XOR. Multiply for squares: x * x.
- Using <, == or &&. Comparisons are not supported. Subtract and shift instead, as in the Sierpinski preset.
- Expecting fractions. x / 2 is whole division, so 5 / 2 is 2.
- Forgetting the wrap. x * y is drawn modulo 256, so large products band. Shift right to look at the higher bits.
Questions
What does x ^ y draw?
The XOR texture. For each pixel the value is the column number XOR the row number, and the picture is that value as a shade. Each quadrant is a copy of the whole picture at half size, either as it is or with the top bit of the value set, so the pattern repeats at every scale.
Why does x & y show the Sierpinski triangle?
The entry C(x + y, x) of Pascal's triangle is odd exactly when x & y is 0. By Kummer's theorem the power of 2 dividing C(x + y, x) is the number of carries when x and y are added in binary, and the addition has no carries exactly when no bit position is set in both. Colour the odd entries and you have the Sierpinski triangle. This page builds Pascal's triangle by addition and confirms the match for every x and y below 256.
Why are the pictures limited to 256 shades?
A pixel is the low 8 bits of the result, so any value wraps: 256 draws the same shade as 0, and -1 the same as 255. Wrapping is what makes x * y curve into bands. To see higher bits, shift them down first, as in (x * y) >> 8.
Why do sin and cos give whole numbers from -127 to 127?
Everything in the expression is a 32-bit whole number, so sin takes a whole turn as 256 (only its low 8 bits count) and returns 127 times the sine, rounded: sin(64) is 127, sin(0) is 0 and sin(192) is -127. Add 128 to centre it on mid grey, as the Pond starting point does, or halve it before adding several together, as Plasma does.
How do I get an angle for spirals and sweeps?
Use atan2(y - 64, x - 64). It returns the direction of each pixel from the middle of a 128 by 128 picture as a whole turn of 256, in the same units that sin and cos take, so atan2(y - 64, x - 64) * 3 has three cycles round the middle. Adding the distance, sqrt((x - 64) * (x - 64) + (y - 64) * (y - 64)), twists those into spiral arms, and subtracting t * 4 turns them. Spiral arms and Radar sweep above are built this way.
What happens when I divide by zero?
The result is 0. An error half way through an image would leave nothing to look at, so this tool defines / and % by zero as 0. Row 0 of x % y is therefore black.
Is the expression run as code?
No. It is read by a small parser written for this page that knows the operators in the table above and nothing else, so a shared link cannot run anything. Everything happens in your browser and nothing is sent anywhere.
Why do x ^ y and (x ^ y) & (x | y) look the same?
They are the same picture. A bit set in x ^ y is set in exactly one of x and y, so it is also set in x | y, and ANDing with x | y changes nothing. This page compares all 65,536 pixels of x ^ y with (x | y) - (x & y) and (x + y) - 2 * (x & y) as well, and they all match.
The operators are explained on the binary calculator and in bit manipulation tricks; for the other neighbour of x ^ y, see the Gray code converter.