Hoist-Point.com

   Back in the early 1990s, when I was in college and new to computer graphics, I came up with an interesting pattern-drawing algorithm. The algorithm is quite simple (its logic fits in just 5 lines of C code), and you can see the result as the background image of this page.

   The idea to draw such a pattern came from a magazine I had in my hands one day. One page in it had a simple, but delicate background: on a light-gray surface there were thin, snake-shaped, shaded short lines, generally going up and right. Of course, a seasoned computer graphics programmer would try to 'model' this: sprinkle a set of points that would 'start' snakes and draw lines going in a general 'up-right' direction, shading them. This would produce the desired image.

    I, on the other hand, ended up with an entirely different method that is far simpler and produces exactly what I had in mind. I decided to make it public, because I have not seen anything like it in computer graphics literature, or online. Here is the original code:


#define LIGHT_GRAY	7
#define DARK_GRAY	8
#define WHITE		15

void draw_surface (int iLeft,int iTop, int iRight, int iBottom)
	{
	register int x, y;
	int iWidth = iRight - iLeft;
	int iHeight = iBottom - iTop;

	// fill area with light-gray color 
	setfillstyle (SOLID_FILL, LIGHT_GRAY);

	// loop untill interrupted 
	while (!kbhit())
		{
		x = rand() % iWidth + iLeft;
		y = rand() % iHeight + iTop;

		if (LIGHT_GRAY == getpixel(x, y))
			{
			if (!(DARK_GRAY == getpixel(x, y-1) || DARK_GRAY == getpixel(x+1, y-1) ||
	                     DARK_GRAY == getpixel(x-1, y-1) || DARK_GRAY == getpixel(x-1, y)))
				{
				if (WHITE != getpixel(x+1, y+1))
					{
					putpixel (x, y, WHITE);
					putpixel (x+1, y+1, DARK_GRAY);
					}
				}
			}
		}
	}

   Of course, the three if statements can be combined into just one long one. I separated them, to make the code look simpler. Here is the logic.

   After filling the surface with a base light-gray color, the program goes into a loop, producing random (x,y) pairs and checking if the (x,y) point is still light-gray. If yes, it checks four neighboring points, three above (x,y): (x-1,y-1),(x,y-1),(x+1,y-1) and one to the left (x-1,y). If none of them is shaded (painted with dark-gray), it performs yet another check, of the point (x+1,y+1) for being not white. If this condition is met, it paints (x,y) with white and (x+1,y+1) with the shade (dark-gray) color.

   This code can be modified a little to produce patterns that are a bit different. Just adding another (fifth) point to the second if statement that checks (x-1,y+1) slightly changes the general direction of the pattern snakes. Alternatively, removing the (x+1,y-1) point from the same if statement produces similar result, but pattern snakes fill the surface somewhat more densely.

4-point check   5-point check   3-point check

4-point check

 

5-point check

 

3-point check

   As the loop progresses, the picture gets closer to a certain 'final' state, when not a single (x,y) point will meet all three conditions. This fact can be used to make the decision when to end the loop, rather than wait for a keystroke as in the code above.


void draw_surface (int iLeft,int iTop, int iRight, int iBottom)
	{
	register int x, y;
	unsigned int iCnt = 0;
	int iWidth = iRight - iLeft;
	int iHeight = iBottom - iTop;
	
	// iGraceCount is max number of unsuccessful tries
	// before loop is ended. It should depend on the area:
	unsigned int iGraceCount = (iWidth * iHeight)/2;
	
	// fill area with light-gray color
	setfillstyle (SOLID_FILL, LIGHT_GRAY);

	 // main loop
	while (iGraceCount > iCnt)
		{
		x = rand() % iWidth + iLeft;
		y = rand() % iHeight + iTop;
		iCnt++;

		if (LIGHT_GRAY == getpixel(x, y))
			{
			if (!(DARK_GRAY == getpixel(x, y-1) || DARK_GRAY == getpixel(x+1, y-1) ||
	                     DARK_GRAY == getpixel(x-1, y-1) || DARK_GRAY == getpixel(x-1, y)))
				{
				if (WHITE != getpixel(x+1, y+1))
					{
					putpixel (x, y, WHITE);
					putpixel (x+1, y+1, DARK_GRAY);
					iCnt = 0;
					}
				}
			}
		}
	}

   Statistically, the total number of changes depends (linearly) on the area, and slightly on the rand() function used in the algorithm. Tests show that in the case of a 4-point check (inside the second if statement) the total number of 'hits' (when all 3 conditions are met) is slightly above 30% of the total number of points in the area, while in the case of a 5-point check the number is around 28%, and with a 3-point check this number is around 32%. This difference is logical: the more conditions you set for a point, the fewer will meet all of them.


 

   Finally, in order to remove the border seams, the code has to treat pixels to the right from the right edge of the area as if they were pixels on the left side, and pixels below the bottom, as if they were pixels on the top. The following code paints an area 200x200 pixels, and returns the number pixels painted white:


#define LEFT_CORNER	100
#define TOP_CORNER 	100
#define AREA_WIDTH 	200
#define AREA_HEIGHT 	200

int VirtGetPixel (int x, int y)
{
	if (x < LEFT_CORNER) x += AREA_WIDTH;
	if (y < TOP_CORNER) y += AREA_HEIGHT;
	if (x >= LEFT_CORNER + AREA_WIDTH) x -= AREA_WIDTH;
	if (y >= TOP_CORNER + AREA_HEIGHT) y -= AREA_HEIGHT;
	return getpixel (x, y);
}

void VirtSetPixel (int x, int y, int color)
{
	if (x < LEFT_CORNER) x += AREA_WIDTH;
	if (y < TOP_CORNER) y += AREA_HEIGHT;
	if (x >= LEFT_CORNER + AREA_WIDTH) x -= AREA_WIDTH;
	if (y >= TOP_CORNER + AREA_HEIGHT) y -= AREA_HEIGHT;
	putpixel (x, y, color);
}

unsigned int draw_surface (void)
	{
	register int x, y;
	unsigned int iCnt = 0;
	unsigned int iGraceCount = 0;
	unsigned int iWhiteCnt = 0;
	
	// iGraceCount is max number of unsuccessful tries
	// before loop is ended. It should depend on the area:
	iGraceCount = (AREA_WIDTH * AREA_HEIGHT) / 2;
	
	// fill area with light-gray color
	setfillstyle (SOLID_FILL, LIGHT_GRAY);

	 // main loop
	while (iGraceCount > iCnt)
		{
		x = rand() % AREA_WIDTH + LEFT_CORNER;
		y = rand() % AREA_HEIGHT + TOP_CORNER;
		iCnt++;

		if (LIGHT_GRAY == VirtGetPixel(x, y))
			{
			if (!(DARK_GRAY == VirtGetPixel (x,   y-1) ||
			      DARK_GRAY == VirtGetPixel (x+1, y-1) ||
	                      DARK_GRAY == VirtGetPixel (x-1, y-1) ||
			      DARK_GRAY == VirtGetPixel (x-1, y)))
				{
				if (WHITE != VirtGetPixel(x+1, y+1))
					{
					VirtPutPixel (x, y, WHITE);
					VirtPutPixel (x+1, y+1, DARK_GRAY);
					iCnt = 0;
					iWhiteCnt++;
					}
				}
			}
		}
	return iWhiteCnt ;
	}
Yuri Yakimenko, 2017