ASCII art can be done in several ways. The most common way is "pure ASCII art", which is where a set of symbols are used to create the "pixels" in an image, and nothing else. There is also colour ASCII art, where the colour of each symbol is really what decides how much detail it represents. You can theoretically mix the two to get the third type, but it's not really common: the effects from colour ASCII are usually good enough for most people!
The process looks something like this:
Load the image we want to convert
Grab the stats about that image (its dimensions, particularly)
Loop through each row in the image, and within that row each column, changing individual pixels of that image into the ACSII symbols most similar to them
Go back to step one, and repeat until done.
Yes, that's an oversimplified list - steps one, two, and four are a cinch, with step three taking the brunt of the effort. As discussed, pure ASCII art relies just on the complexity of symbols in order to reproduce detail of images: the darker a pixel is, the more complex a character should be used. By "complex" I mean that it takes up more screen real estate. For example, "i" is less complex than "m", as, in a fixed-width printout (and ASCII art always requires fixed-width fonts to be used so that the output is predictable), it results in the space occupied by a character having more white space than the "m" character, and so looks darker.
So, the first step is to have an array of symbols ordered from most complex (like @ and #) to least complex (like , and . ) so they can be used in place of pixels. At the very end of the array should be the least-complex ASCII symbol, " " (that is, a space), used for the lightest pixels of all.
Now, with an array containing the symbols we want to use in our ASCII art, how should we select which of them to use? Well, consider this: each pixel in our input picture will be made up of 256 shades of red, green, and blue each, making a total of 16.7 million possible combinations (known as 24-bit colour, as 2 to the power of 24 is 16,777,216). While there are pictures out there that don't use so many red, green, and blue shades (they have smaller palettes), we shan't be worry about them. The RGB values for our images will be stored as a number between 0 and 255, which means that pure white is red 255, green 255, blue 255, and middle grey is red 127, green 127, blue 127. In order to get the brightness of this pixel in order to use it to decide how complex a character to use, we must add together the individual RGB values in order to form the absolute brightness.
As the maximum value of each of the three colours is 255, the maximum combined value is 765. If we then divide that by a suitably large number (I've used 85, as it divides exactly into 765) and round the result using PHP's round() function (which turns 3.01 into 3, and 3.51 into 4) we end up with a whole number between 0 and 9. As we have sorted our ASCII symbols array in order of the complexity of its symbols, these values between 0 and 9 suit us perfectly, because 9 is the result when you divide the maximum RGB value by 85, which means that our simplest character (a space) will get used for the brightest white pixels - perfect!
What our code needs to do is loop through each row in the image, and within each row it needs to also loop through each column. This means we're accessing each pixel one-by-one, which is what we need. Then, for each pixel in the image, we need to grab the red, green, and blue colour values of the pixel, add them all together, divide by 85, round them, and look up their elements in our symbol array. The symbol returned needs to be printed out, with line breaks being added at the end of each line.