Fun With Procedural Images

Screenshot from Farbrausch's "The Product" 64kb demo

Screenshot from Farbrausch’s “The Product” 64kb demo

Many, many years ago, a German group known as Farbrausch created a rather impressive piece of software, known as The Product, a 64-kilobyte executable which produces, without the aid of a network connection or any additional files, a short, real-time rendered music video advertising all of the great things “The Product” can do.

The really cool thing is that at 64 kilobytes, the file itself is almost smaller than the download request, and it’s old enough that virtually every (windows) PC available today can run it flawlessly.

One of my goals when designing the engine for my game was to include a set of procedural content creation tools, similar to what Farbrausch uses.  To this end, I have created a fairly extensive 2D image library, with several tools for making and manipulating images.  Nothing fancy, though you’d be surprised at how fancy you can make things without using anything fancy.  Now I just need to make files that have all these instructions for creating pictures, and… well, that’s where the post from the other day came from.

See, I used a LOT of templates in my image library.  Just because it’s intended for image editing doesn’t mean I know it will ALWAYS be used for image editing.  Cloud noise is really good for making lightning bolt shapes, for example.  Actually, cloud noise is useful for a lot of things.  The point is that whenever I could think of a non-image-related use for a given function, I was sure to make the function as general as possible, through the use of Templates.

Well, now we have a problem.  See, if I’m reading in a file, that means all of my incoming data is either strings or raw untyped bytes, and I need to figure out what kind of data I’m using.  This is reasonably easy to fix, I can just add a little note before each value I used a Template for.  And for images, I can’t really think of anything but floating point or pixel values that I would use.

Wait, we may be onto something here.

Procedurally-generated "blank" texture.  The checkerboard pattern helps measure the size(this one is 64x64 pixels) and make sure nothing has gone wrong  The colored corners help to align the image.  Red is top-left, blue is top-right, green is bottom-left.  The bottom-right corner has invisible pixels, but that doesn't show up well in this screenshot.  If any of the corners is in the wrong place, I know something's gone wrong, and since this pattern is generated inside the program, I know it will always be available, even if I can't load any external files.

Procedurally-generated “blank” texture. The checkerboard pattern helps measure the size(this one is 64×64 pixels) and make sure nothing has gone wrong The colored corners help to align the image. Red is top-left, blue is top-right, green is bottom-left. The bottom-right corner has invisible pixels, but that doesn’t show up well in this screenshot. If any of the corners is in the wrong place, I know something’s gone wrong, and since this pattern is generated inside the program, I know it will always be available, even if I can’t load any external files.

So let’s restrict our possible values to “pixel” and “float.”  Now I only have to screen two variable types, and I can organize my image functions based on whether they accept and output the same Template type, accept one particular type and output a particular type, or accept any type and output a particular type.

Time consuming, yes.  Difficult, not really.  Turns out past-me was really clever: every single function outputs in pixel format.  Most of them can also output in floating point.  In other words, most of my image functions output in whatever format I feed the base data in.  This is both great, and terrible.  Great, because it means I have some serious flexibility when it comes to image manipulation.  Terrible, because it makes it harder to set up a dumb menu system and let the backend handle everything.  I had originally planned to leave storage of intermediate arrays to the main program, and just patch in a string->function bridge, so for example, I could type “Clouds, 512, 512, 1.0, 1, 0.5, -1” and get a Cloud noise image, which is a square, 512 pixels on a side, ranging from 0.0 to 1.0(floats), using the random seed of 1, with a roughness factor of 0.5, with the maximum detail level.  And then I could do whatever I want with that.

Well, there’s a workaround, but it makes the system less flexible.  Instead of asking for an array and having the bridge spit one out for me, I’ll tell the bridge to store its own set of arrays, and add functions to tell me whether each of them is pixel or float format, and ask it to fetch them for me.

This does mean that I have to manually ask for whatever I just generated if I want to admire my own handiwork, but actually it’s a more self-contained system in the long run.  Which is a good thing.  Shamus Young, over on his fantastic, educational, and inspirational blog, has a couple of posts dealing with code and library portability and modularity.  The gist is that you want your library to be as self-sufficient as possible.  You want to create as little work for the future programmer as you can get away with, especially when that work involves tracking down 3rd-party libraries.

So a single library that does everything with regards to loading procedural image data from a file and then bridging the gap between that file and an already-present image library?  Perfect.  IF the image library is indeed already-present.

The next challenge is assembling everything in such a way that you don’t get a huge, ugly, impenetrable block of text.  Remember what I said previously about everybody hating you?  Yeah, you don’t want people to hate you.  Especially when they turn out to be yourself, because THAT guy knows where you sleep!

In this case, I end up putting together a bunch of tiny little functions whose job it is to accept an array of strings, decode them into function parameters, and then run the appropriate image function, and stick the output in the appropriate place.  Each function is only responsible for one image function, so that keeps them short, sweet, and descriptive.  I actually ended up doing all the gruntwork in a set of Structs whose job it is to–

Oh.  A “Struct” is like a custom variable type in C and D.  You can give it internal values and functions of its own, and it will help you manage data.  Struct support in C was one of the cornerstones of Object-Oriented design paradigm, which is pretty much the only way modern applications can become so complex while still being within the realm human capability to build and maintain.

So I have a set of Structs which represent(contain, actually) the function parameters like so:

struct DefaultTextureGridParameters
{
int width = 256;
int height = 256;

void reset()
{
width = 256;
height = 256;
}
bool load(string[] params)
{
reset();
if(params.length == 1)
{
if(params[0].isNumeric())
{
width = height = to!int(params[0]);
return true;
}
else
return false;//invalid dimension
}
if(params.length >= 2)
{
if(params[0].isNumeric() && params[1].isNumeric())
{
width = to!int(params[0]);
height = to!int(params[1]);
}
else
return false;//invalid dimensions.
}
return true;
}
bool load(ubyte[] rawdataparams)
{
reset();
union arraymerger
{
int integerval;
ubyte[4] arrayval;
};
arraymerger merger;
if(rawdataparams.length >= 4)
{
merger.arrayval = rawdataparams[0..4];
height = width = merger.integerval;
}
if(rawdataparams.length >= 8)
{
merger.arrayval = rawdataparams[4..8];
width = merger.integerval;
}
return true;
}
}

Why yes, that is COMPLETELY un-readable, Thank-you SO MUCH for stripping my whitespace, WordPress.

And yes, I should probably comment more.  Anyway, this is a Struct used for storing width and height information, for example, for the function that generates that Default Texture you see up there.  And I use it like this:

bool generateDefaultTextureGrid(string[] parameters, string storeName = "")
{
DefaultTextureGridParameters params;
if(!params.load(parameters))
return false;

if(storeName.length > 0)
pixelArrays[storeName] = ImageUtils.defaultTextureGrid(params.height, params.width);
else
latestPixels = ImageUtils.defaultTextureGrid(params.height, params.width);
return true;
}

Most of the “generate” functions look like that.  Well, they look like that with proper whitespace alignment.(Thanks again, WordPress!)  Allocate our “Parameters” struct, load it with a set of strings, and then we can just pick the appropriate parameters out of it and call our function.

Now I just need to put together a system to call all the functions as appropriate.

Advertisements

Leave a Reply. Wheaton's Rule in full effect.

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s