Strings example: ASCII art
This ASCII Art example shows a number of features of working with the String class in ActionScript 3.0, including the following:
The
split()
method of the String class is used to extract values from a character-delimited string (image information in a tab-delimited text file).Several string-manipulation techniques, including
split()
, concatenation, and extracting a portion of the string usingsubstring()
andsubstr()
, are used to capitalize the first letter of each word in the image titles.The
getCharAt()
method is used to get a single character from a string (to determine the ASCII character corresponding to a grayscale bitmap value).String concatenation is used to build up the ASCII art representation of an image one character at a time.
The term ASCII art refers to a text representations of an image, in which a grid of monospaced font characters, such as Courier New characters, plots the image. The following image shows an example of ASCII art produced by the application:
The ASCII art version of the graphic is shown on the right.
To get the application files for this sample, see FlashPlatformAS3DevGuideExamples.zip. The ASCIIArt application files can be found in the folder Samples/AsciiArt. The application consists of the following files:
File | Description |
---|---|
AsciiArtApp.mxml or AsciiArtApp.fla | The main application file in Flash (FLA) or Flex (MXML) |
com/example/programmingas3/asciiArt/AsciiArtBuilder.as | The class that provides the main functionality of the application, including extracting image metadata from a text file, loading the images, and managing the image-to-text conversion process. |
com/example/programmingas3/asciiArt/BitmapToAsciiConverter.as | A class that provides theparseBitmapData()method for converting image data into a String version. |
com/example/programmingas3/asciiArt/Image.as | A class which represents a loaded bitmap image. |
com/example/programmingas3/asciiArt/ImageInfo.as | A class representing metadata for an ASCII art image (such as title, image file URL, and so on). |
image/ | A folder containing images used by the application. |
txt/ImageData.txt | A tab-delimited text file, containing information on the images to be loaded by the application. |
Extracting tab-delimited values
This example uses the common practice of storing application data separate from the application itself; that way, if the data changes (for example, if another image is added or an image's title changes), there is no need to recreate the SWF file. In this case, the image metadata, including the image title, the URL of the actual image file, and some values that are used to manipulate the image, are stored in a text file (the txt/ImageData.txt file in the project). The contents of the text file are as follows:
FILENAME TITLE WHITE_THRESHHOLD BLACK_THRESHHOLD
FruitBasket.jpg Pear, apple, orange, and banana d8 10
Banana.jpg A picture of a banana C8 20
Orange.jpg orange FF 20
Apple.jpg picture of an apple 6E 10
The file uses a specific tab-delimited format. The first line (row) is a heading row. The remaining lines contain the following data for each bitmap to be loaded:
The filename of the bitmap.
The display name of the bitmap.
The white-threshold and black-threshold values for the bitmaps. These are hex values above which and below which a pixel is to be considered completely white or completely black.
As soon as the application starts, the AsciiArtBuilder class loads and parses
the contents of the text file in order to create the "stack" of images that it
will display, using the following code from the AsciiArtBuilder class's
parseImageInfo()
method:
var lines:Array = _imageInfoLoader.data.split("\n");
var numLines:uint = lines.length;
for (var i:uint = 1; i < numLines; i++)
{
var imageInfoRaw:String = lines[i];
...
if (imageInfoRaw.length > 0)
{
// Create a new image info record and add it to the array of image info.
var imageInfo:ImageInfo = new ImageInfo();
// Split the current line into values (separated by tab (\t)
// characters) and extract the individual properties:
var imageProperties:Array = imageInfoRaw.split("\t");
imageInfo.fileName = imageProperties[0];
imageInfo.title = normalizeTitle(imageProperties[1]);
imageInfo.whiteThreshold = parseInt(imageProperties[2], 16);
imageInfo.blackThreshold = parseInt(imageProperties[3], 16);
result.push(imageInfo);
}
}
The entire contents of the text file are contained in a single String instance,
the _imageInfoLoader.data
property. Using the split()
method with the
newline character ( "\n"
) as a parameter, the String instance is divided into
an Array ( lines
) whose elements are the individual lines of the text file.
Next, the code uses a loop to work with each of the lines (except the first,
because it contains only headers rather than actual content). Inside the loop,
the split()
method is used once again to divide the contents of the single
line into a set of values (the Array object named imageProperties
). The
parameter used with the split()
method in this case is the tab ( "\t"
)
character, because the values in each line are delineated by tab characters.
Using String methods to normalize image titles
One of the design decisions for this application is that all the image titles are displayed using a standard format, with the first letter of each word capitalized (except for a few words that are commonly not capitalized in English titles). Rather than assume that the text file contains properly formatted titles, the application formats the titles while they're being extracted from the text file.
In the previous code listing, as part of extracting individual image metadata values, the following line of code is used:
imageInfo.title = normalizeTitle(imageProperties[1]);
In that code, the image's title from the text file is passed through the
normalizeTitle()
method before it is stored in the ImageInfo object:
private function normalizeTitle(title:String):String
{
var words:Array = title.split(" ");
var len:uint = words.length;
for (var i:uint; i < len; i++)
{
words[i] = capitalizeFirstLetter(words[i]);
}
return words.join(" ");
}
This method uses the split()
method to divide the title into individual words
(separated by the space character), passes each word through the
capitalizeFirstLetter()
method, and then uses the Array class's join()
method to combine the words back into a single string again.
As its name suggests, the capitalizeFirstLetter()
method actually does the
work of capitalizing the first letter of each word:
/**
* Capitalizes the first letter of a single word, unless it's one of
* a set of words that are normally not capitalized in English.
*/
private function capitalizeFirstLetter(word:String):String
{
switch (word)
{
case "and":
case "the":
case "in":
case "an":
case "or":
case "at":
case "of":
case "a":
// Don't do anything to these words.
break;
default:
// For any other word, capitalize the first character.
var firstLetter:String = word.substr(0, 1);
firstLetter = firstLetter.toUpperCase();
var otherLetters:String = word.substring(1);
word = firstLetter + otherLetters;
}
return word;
}
In English, the initial character of each word in a title is not capitalized
if it is one of the following words: "and," "the," "in," "an," "or," "at," "of,"
or "a." (This is a simplified version of the rules.) To execute this logic, the
code first uses a switch
statement to check if the word is one of the words
that should not be capitalized. If so, the code simply jumps out of the switch
statement. On the other hand, if the word should be capitalized, that is done in
several steps, as follows:
The first letter of the word is extracted using
substr(0, 1)
, which extracts a substring starting with the character at index 0 (the first letter in the string, as indicated by the first parameter0
). The substring will be one character in length (indicated by the second parameter1
).That character is capitalized using the
toUpperCase()
method.The remaining characters of the original word are extracted using
substring(1)
, which extracts a substring starting at index 1 (the second letter) through the end of the string (indicated by leaving off the second parameter of thesubstring()
method).The final word is created by combining the newly capitalized first letter with the remaining letters using string concatenation:
firstLetter + otherLetters
.
Generating the ASCII art text
The BitmapToAsciiConverter class provides the functionality of converting a
bitmap image to its ASCII text representation. This process is performed by the
parseBitmapData()
method, which is partially shown here:
var result:String = "";
// Loop through the rows of pixels top to bottom:
for (var y:uint = 0; y < _data.height; y += verticalResolution)
{
// Within each row, loop through pixels left to right:
for (var x:uint = 0; x < _data.width; x += horizontalResolution)
{
...
// Convert the gray value in the 0-255 range to a value
// in the 0-64 range (since that's the number of "shades of
// gray" in the set of available characters):
index = Math.floor(grayVal / 4);
result += palette.charAt(index);
}
result += "\n";
}
return result;
This code first defines a String instance named result
that will be used to
build up the ASCII art version of the bitmap image. Next, it loops through
individual pixels of the source bitmap image. Using several color-manipulation
techniques (omitted here for brevity), it converts the red, green, and blue
color values of an individual pixel to a single grayscale value (a number from 0
to 255). The code then divides that value by 4 (as shown) to convert it to a
value in the 0-63 scale, which is stored in the variable index
. (The 0-63
scale is used because the "palette" of available ASCII characters used by this
application contains 64 values.) The palette of characters is defined as a
String instance in the BitmapToAsciiConverter class:
// The characters are in order from darkest to lightest, so that their
// position (index) in the string corresponds to a relative color value
// (0 = black).
private static const palette:String = "@#$%&8BMW*mwqpdbkhaoQ0OZXYUJCLtfjzxnuvcr[]{}1()|/?Il!i><+_~-;,. ";
Since the index
variable defines which ASCII character in the palette
corresponds to the current pixel in the bitmap image, that character is
retrieved from the palette
String using the charAt()
method. It is then
appended to the result
String instance using the concatenation assignment
operator ( +=
). In addition, at the end of each row of pixels, a newline
character is concatenated to the end of the result
String, forcing the line to
wrap to create a new row of character "pixels."