14 Haziran 2012 Perşembe

Algorithmic Art

To contact us Click HERE
One of my favorite mathematical recreations is to create algorithmic art: images constructed using computer programs and mathematical formulas. An image is just a collection of pixels (colored dots) arranged in a rectangular grid. The computer program loops over each pixel in the image, using whatever mathematical formula you want to specify its color.

There are many algorithms for selecting the colors, each with its own signature style. For example, many web sites have beautiful pictures of fractals like the Mandelbrot Set, with each point colored based on the results of iterating a complex dynamic system. Alternatively, the POV-Ray Hall of Fame hosts images created using the free, open source 3-D ray tracing program POV-Ray, which colors each pixel by tracking it back to a 3-D object or light source, yielding a photo-realistic image of a virtual scene complete with shadows and reflections.

Today, however, I want to share a short C++ program I wrote to experiment with a much simpler approach. I've used it to make images like these four:

Regular readers will recall that I usually provide code examplesusing R. Why switch to C++today? Because it runs faster. We will be generating high resolutionimages, the sort one might use as wallpaper on a wide screen monitor,or even enlarge to print and hang on a wall. A screen-sized imageneeds roughly 1000 rows of 2000 pixels each, for a total of 2 millionpixels (2 mega-pixels, in digital camera marketing speak). A higherresolution image for enlargement and printing might need ten times asmany pixels. We will need to do a lot of calculations on each pixel in orderto decide how to color it. And, since the algorithm to specify thecolors contains several parameters that we can experiment with, aswell as some random aspects, we will want to generate multiple images,varying the parameters to see what happens.

All this computation adds up. Even using C++, it will take a fewseconds to generate each image. At that rate, we can have the computergenerate a dozen random image variations in under a minute, which wecan examine, compare, and learn from. In contrast, using R wouldprobably require minutes in place of seconds, requiring perhaps anhour to create the same set. Waiting an hour is a lot less fun thanwaiting a minute, and it makes the cycle of learning and experimentingmuch more difficult.

You can of course apply the ideas we discuss today to theprogramming language of your choice. The part where we loop over allpixels to assign colors will be straightforward in any language. Thetricky part is finding a means for converting a grid of colors into anactual image file that your operating system can recognize anddisplay. If you do want to use R for this, re-readmy ChaoticSystems post. It will give you a place to start, showing how toplot individual points and save the result as a PNG (portable networkgraphics) image file, but it will probably run relatively slowly.

If you do want to follow along with the C++ version, I haveto warn you that C++ is significantly harder to use than R. If youwant to modify the C++ code, you will find it less forgiving than thecorresponding R code. Also, if you've never used C++ before, you willneed to download and install several different packages just to runthe code I provide below. This can be tricky, especially on Windowssystems. Linux systems, in contrast, normally have tools like C++ andImageMagick preloaded, or at least configured to be easy to install.So, with apologies to regular readers who like to try the R codethemselves, for today only we are going to assume you already have aworking C++ installation at hand, and a working knowledge of the C++language as well. Even if you don't have C++ installed, you may find itinteresting to follow the discussion and look at the resulting images.We will go back to using R next time.

In the C++ code, we will represent colors by 3 whole numbers in therange from 0 to 255. These tell the computer how bright to make theRed, Green, and Blue dots on your monitor. For instance, 0-0-0 isblack, since all three colors are turned off. 255-0-0 is bright red,with the red color turned fully on, but green and blue off. 255-255-0is bright yellow, because when mixing light (as opposed tomixing paint), red and green blend to make yellow. 128-128-128 is amedium gray, with equal amounts of all three colors, halfway betweenblack and 255-255-255, which is white.

Graphics file formats like PNG, JPG, and BMP are relativelycomplicated. To keep things simple, we will have our C++program write a file containing the grid of colors in "raw"form, and then we will use a program called convert toconvert this into an actual PNG file. convert is part of asuite of free, open source, cross-platform programscalled ImageMagick.You can download and install it on Windows, Mac and Linuxsystems. This keeps our C++ program simple, so we can focus on the math ofhow to color each pixel, rather than on the details of image fileformats.

Here's the C++ program. It is just over one hundred lines of code.
#include <iostream>#include <fstream>#include <sstream>#include <vector>#include <math.h>#include <stdlib.h>using namespace std;#define srandom srand#define random rand// adjustable image size in pixels:int const nI = 1600;int const nJ = 1100;typedef unsigned char byte;struct Color {  Color(byte R=0, byte G=0, byte B=0)     : r(R), g(G), b(B) {}  byte r, g, b;};struct Image {  Image() : data(nI*nJ) {}  Color& operator()(int i,int j) {return data[i+nI*j];}  Color const& operator()(int i, int j) const     { return data[i+nI*j]; }  vector<Color> data; // one contiguous array};void png(Image const& img, char const* name) {  { // nested to close file:    ofstream out("temp.rgb", ios::out | ios::binary);    out.write((char const*) &(img(0,0)),               nI*nJ*sizeof(Color));  }  stringstream s;  s << "convert -size " << nI << "x" << nJ    << " -depth 8 temp.rgb " << name << ".png";  cout << s.str() << endl;  system(s.str().c_str());}Color smooth(Image const& src, int i0, int j0) {  int n = 0, r=0, g=0, b=0;  for(int i=max(0, i0-1); i<min(nI, i0+2); i++)    for(int j=max(0, j0-1); j<min(nJ, j0+2); j++) {      Color const& c(src(i,j));      r += c.r; g += c.g; b += c.b;      n++;    }  return Color(r/n, g/n, b/n);}void smooth(Image& dest, Image const& src, int nRep) {  Image temp = src;  for(int nReps = 0; nReps<nRep; nReps++) {    for(int i=0; i<nI; i++)      for(int j=0; j<nJ; j++)	dest(i,j) = smooth(temp, i, j);    for(int i=0; i<nI; i++)      for(int j=0; j<nJ; j++)	temp(i,j) = dest(i,j);  }}inline double r() { return random()/(double)RAND_MAX; }inline byte N(double x, double dx)   { return min(255., max(0., x+dx*2*(r()-0.5))); }byte S(double d) { return 127.99*(1+sin(d)); }double fR(double x,double y) {return (x+2*y)*(5+3*x-y);}double fG(double x, double y)   { return 2*sin(2*x+y) - 5*cos(x-2*y); }double fB(double x, double y) {return (3*x+y)*(x-2*y);}double mix(double a, double b) {  if(r() < 0.3) return fabs(a-128)>fabs(b-128) ? a : b;  if(r() < 0.3)     return (r() < 0.5) ? max(a,b) : min(a,b);  return (a+b)/2;}void blend(Color& dest, Color const& src) {  double dx = 15;  if(r() < 0.67) {    dest.r = N(mix(dest.r,src.r), dx);    dest.g = N(mix(dest.g,src.g), dx);     dest.b = N(mix(dest.b,src.b), dx);  }  if(r() < 0.8 and      (abs(dest.r-dest.g)+abs(dest.r-dest.b)+      abs(dest.g-dest.b)) < 1.2)    blend(dest, src); // try again}void make(Image& img) {  double sx = 1./nI, sy = 1./nJ;  for(int ix=0; ix<nI; ix++)    for(int iy=0; iy<nJ; iy++) {      double x = ix*sx, y = iy*sy;      img(ix, iy).r = S(fR(x, y));      img(ix, iy).g = S(fG(x, y));      img(ix, iy).b = S(fB(x, y));      blend(img(ix, iy), img(ix, iy));    }}int main() {  srandom(135);  Image img, sm;  make(img);  smooth(sm, img, 5);  png(sm, "example");  return 0;}
To do anything with this, you will need to install a(standards-compliant) C++ compiler. I recommend using the GNUcompiler g++, which is free, open-source and extremely highquality: the de facto standard. You can download it fromThe GNU Compiler Collection page.On Windows, an easy way to get g++ is to installthe Minimalist GNU for Windows development environment (for older, 32 bit Windows),or mingw-w64 for64-bit Windows.

Once you have installed the compiler and the image manipulation suite,you can copy and paste the above code into a text filecalled example.cpp. If you need to, you could use the Rscript editor as your text editor. Iprefer GNU Emacs,which is a very powerful text editor and integrated developmentenvironment. Emacs too is free, open source, high quality,and cross platform. Among other things, it will automatically colorthe words in a C++ program to highlight comments, text strings, and soforth, which is quite helpful. Do not use a word processor, as it willadd all sorts of extra invisible characters to your file that willcause the compiler to reject it.

Next, compile the file by issuing a command like
g++ -Wall -O2 example.cpp
This runs the compiler, turns on helpful warnings to detect commonprogramming errors, and turns on optimizations to make the program runfaster. On Linux, this command works verbatim, assuming you havecorrectly installed the compiler in your search path. If you areseriously interested in writing complicated computer programs, Ihighly recommend learning to use Linux. On other systems, likeWindows, you will need to refer to the installation instructions foryour particular compiler to see what the command looks like, and youwill need to run some sort of command shell, unless your editorenvironment can invoke the compiler for you (as for instance Emacs can).

Once you have compiled the program, you will have a binary executableready to run. On Linux, typing ./a.out will run it. OnWindows, you can double click the a.exe file instead.Either way, assuming you properly installed ImageMagick, it willproduce a file called example.png that looks like this:

On Windows, if you get an error message about not being able to findthe convert program, modify the line in the C++ code thatbegins
s << "convert -size "
and change the word convert to the full path to the convertprogram; this might be somethinglike C:/imagemagick/bin/convert.exe. As this examplesuggests, do not install ImageMagick inside the usual Program Files folder, since that name has a space in the middle of itwhich will confuse matters even further.

So how does the program work?

You can ignore most of it. Up until the line
byte S(double d) { return 127.99*(1+sin(d)); }
it is juststandard C++ stuff for setting up an Image as a grid of Colors, anddefining a "smoothing" operator that averages each pixel with itsneighbors. The one interesting bit is that you can change the size ofthe resulting image by editing the lines
int const nI = 1600;int const nJ = 1100;
The default image will be 1100 pixels tall and 1600 wide, but you canchange these to any size and aspect ratio you wish.

The mathematically interesting part begins with the S function, butfirst, glance down to the end and read backward. The mainfunction is the main program; it seeds the random numbergenerator with an initial value (here 135), then creates two blankimages. It fills in the first using the make function, thensmoothes it five times, storing the result in the second imageobject, which it finally writes out as a PNG file.

You can change the random seed from 135 to some other number; thiswill make all the "dice rolls" come out differently, potentiallyhaving some impact on the resulting image. To really understand thealgorithm, though, we need to look at the make function. Thisloops over all rows and columns in the image. For each pixel, itcomputes x and y coordinates on a scale from zero to one. Then comethree important lines:
img(ix, iy).r = S(fR(x, y));img(ix, iy).g = S(fG(x, y));img(ix, iy).b = S(fB(x, y));
These calculate the red, green, and blue components of the color touse for the pixel in column ix and row iy of the image. The way wecompute the color is simple: we define three functions (fR, fG, fB) ofx and y, which determine the red, green and blue amounts. We wouldlike to use arbitrary mathematical functions, and not worry aboutsqueezing the result into the range from 0 to 255, so we letthe S function handle the squeezing for us.

Thus, all the real action is in the three functions defined a littleearlier on in the code:
double fR(double x,double y) {return (x+2*y)*(5+3*x-y);}double fG(double x, double y)   { return 2*sin(2*x+y) - 5*cos(x-2*y); }double fB(double x, double y) {return (3*x+y)*(x-2*y);}
You can change these to anything you want. Experiment! This part shouldbe pretty easy to modify: formulas in C++ look almost the same as those inR, except that to raise x to the n'th power, use pow(x,n), instead of writing x^n the way R does. Among other things, you cantake a natural logarithm with log, or exponentiateusing exp, or use the tangent function tan or theinverse tangent atan(y,x), which is useful for doing polar coordinates.

The S function takes any real number as input. It uses thesine function to knock it down to the range from -1 to 1, then shiftsand scales it to get something between 0 and 255 for use in colors.

We also define function called blend and mix. Theseblend a new color onto an existing color, using random numbers to control the mixing process. This has verylittle effect in our first example, but I've included it to give youan idea of other kinds of experiments you can try.

In this next image, I change the make function to alternateamong three different coordinate systems:
void make(Image& img, int kind) {  double sx = 1./nX, sy = 1./nY;  double x, y, r, th;  for(int ix=0; ix<=nX; ix++)    for(int iy=0; iy<=nY; iy++) {      x = ix*sx+1.5; y = iy*sy-1.5;       kind = (kind+1)%3 + 1;      if(kind == 2) { r = x; th = y; }      if(kind == 3){r = sqrt(x*x+y*y); th = atan2(y,x);}      if(kind > 1) { x = r*sin(th); y = r*cos(th); }      img(ix, iy).r = S(fR(x, y));      img(ix, iy).g = S(fG(x, y));      img(ix, iy).b = S(fB(x, y));      if(::r() < 0.6) blend(img(ix, iy), Color(1,1,0));    }}
Then I change the main program to call make, once with aspecific value of kind or multiple times,using blend to merge the variations together. This createsa "noisy" appearance, somewhat reminiscent of a chalk drawing. Ilike the result, since it is more "textured" looking than we would getby just making the three images partially transparent in an imageediting program.

As another alternative, instead of using the mechanism of specifyingthe 3 color functions fR, fG, and fB, the next two images create alarge number of squares with randomly chosen position and color, thenuse blend so that newer ones do not completely obscureearlier ones.

To achieve this, I created a new square function withan extra argument that controls the size of 50 randomlyplaced squares. The use of blend means theywind up partially obscuring the ones underneath them, but partlyshowing through, which leads to an interesting visual effect.

Instead of picking random sizes, all 50 squares have the samesize. If the size is big enough, we repeat the process, dividing the size by e = 2.71, and drawing another 50 smaller squares.We use 2.71, not the Golden Ratio, followingNikos'suggestion.
void square(Image& img, int size) {  size = size/exp(1);  for(int i=0; i<50; i++) {    int x0 = nX*r(), y0 = nY*r();    Color c(0.5, 0.5, 0.5);    blend(c, c);    if(r() < 0.3) blend(c, yellow);    blend(c, img(x0,y0));    for(int x=0; x<size; x++)      for(int y=0; y<size; y++)        blend(img((x0+x)%nX, (y0+y)%nY), c);  }  if(size > 30) square(img, size);}
We also change make to call this squarefunction, passing the height of the image as theinitial size.

Finally, the next version blends in some extra yellow inrandomly selected squares. This warms the result, making a pleasantimage that I am currently using as a screen background or "wallpaper".

Feel free to experiment further with this code. With this kind ofalgorithmic art, you apply your artistic judgment by choosing amongstalternative algorithms and parameters, rather than by controlling thecolor of each pixel individually as if you were painting. You canupload images to sites likeGoogle's Picasa, so if youdiscover something interesting, please post a comment below, ideallywith a link to the resulting image and/or the modified code. There isno "right answer": rather than envisioning a result and spending a lotof time fruitlessly trying to recreate it, just try changing somethingand see what it does. With luck, you will eventually stumble upon amodification that generates a visual effect that you find pleasing.

If you enjoyed this article, you might also like Koch Snowflakes, for some algorithmic art ideas based on fractals using R.

You may also want to use the 'Topic', 'Search' or 'Archive'widgets in the side-bar to find other articles of related interest.Or check outthe Contentspage for a complete list of past topics in historical order.

I hope you enjoyed this discussion. You can use the littlebuttons near the comment box below to share it. Click the little Mto email this post to a friend, or the T toTweet it, or the F to share it on Facebook.

Please post questions, comments and other suggestions using the box below, or email me directly at the address given by the clues at the end of the Welcome post. Remember that you can sign up for email alerts about new posts by entering your address in the widget on the sidebar. If you prefer, you can follow @ingThruMath on Twitter, where I will tweet about each new post to this blog. See you next time!

Hiç yorum yok:

Yorum Gönder