Imaging Lingo Basics
Chuck Neal
If you've recently visited the Macromedia Director forums, read any of the Director mailing lists, or routinely check many of the common web sites, you may have seen references to something known as imaging lingo. Hmm, a quick search in the online help turns up no results? Hey, what's up here? Well, the term imaging lingo is not really an official name, but more a descriptor of a number of features added in Macromedia Director 8. These 10-12 new lingo commands and objects are easily overlooked if you browse the manuals, but within them lies massive power for use in your projects.
In this article, I will refer to a number of behaviors on MediaMacros.com as well as a presentation I gave at Macromedia Devcon 2002 on Imaging Lingo. Additionally, I will include links to other resources and sites at the end of this document that can provide additional information on advanced imaging effects.
Requirements
To complete this tutorial you will need to install the following software:
Director MX
So what is imaging lingo, really? Basically, it refers to functions that allow you to directly interact with and edit image data in cast members, the stage, and image objects. Think of it as a Lingo version of your favorite paint program. You can use it to edit photos, create a drawing program, or even perform interesting effects on sprites and cast members. Here are a few items I have used imaging lingo for in the past:
- Create a paint program
- Real-time jigsaw puzzle engine
- Ripple/water effects on images
- Real-time motion blur
- Create "morphs" between pictures
- Create a "goo" type application for photos
- Color correct or modify pictures
- Add video to 3D texture maps in Shockwave 3D
- Create multi-tiered pop-up menus, tree views, and complex systems of buttons, all with a single sprite
- Create custom transitions between frames
- Tile based game engines
- Real time game board creation
- And many, many other uses...
The Image Object
So now that you have an idea of some of the possible uses, let's take a look at this magic bag of tricks and its great potential. When working with imaging lingo, remember that you are working with raw data that will process quicker than any on-screen effect. That is, you can composite and animate dozens of layers through Lingo far faster than Director can animate layered, transparent images with varying inks. Since everything happens in memory, nothing goes to the image buffer for compositing until you finish the operation, making it efficient for many special effects that would be too slow through raw score animation.
At the core of imaging lingo is the image object. Some items that contain an image object are the stage, cast members, and even some video sprites like real video. An image object is basically bitmap data that resides in a variable in memory. It should not be confused with the sprite on the stage or the cast member. Instead it's an element of these, just like width, ink, text, or any other memory or sprite property. The base image object is fairly simple. You can create a new object as follows:
i = image(100,100,32)
Basically the three parameters are width, height, and depth; so our example above is an image 100 pixels wide, 100 pixels tall, and 32-bit depth. The 32-bit images have an important advantage as they carry a full alpha channel so you can even add transparency to images. Just be aware that this also requires slightly more RAM, more time to process, and you must be conscious of the alpha channel when editing images.
You can also add a fourth parameter to 8-bit images to define the palette to use. This way you can use a set color palette like #grayscale. To create a new 100 X 100, 1-bit grayscale image use the following:
i = image(100, 100, 8, #grayscale)
Images can also be extracted from other sources. Here are a few examples:
i = member(1, 1).image
This is the image object for cast member 1. Some types of members that have image properties are bitmaps, text, fields, flash, and vector shapes. Note that all members do not have image objects. Many video types like QuickTime, AVI, 3rd party Xtras, ActiveX, and others do not have an image property.i = member(1,1).picture
This is almost identical to the example above. The picture property actually showed up a bit before full imaging lingo was fleshed out, so the picture property is synonymous with the image property for most standard types. I would recommend using the image property as a habit though, as third-party vendors that add image properties to their Xtras are not as likely to rely on this alternate name.i = (the stage).image
This will capture the current image state of the stage. This can be both tested and set making it possible to bypass the stage entirely and write directly to the output. This can be very powerful for adding effects, overlays, and so forth, without using additional sprites.sprite(1).imageA few select media types have a sprite image property. One example of this is Real Media members. A common use for this is to play Real Media video off-screen, then copy the current image of the sprite to be used as a texture map inside 3D. This way you can have video playing in your 3D environment, add animated effects, and more.
The Color Object
So once you have an image, what can you do with it? Let's start with the smallest elements and go up from there. Any bitmap data is a collection of colored dots called pixels. In an image 10 pixels X 10 pixels, there are a total of 100 tiny dots or pixels of color. Director allows you to read and write to this level allowing total control over every single color spec that comprises a picture. First let's look at how Director defines color.
The color object is simply an object that defines color information to be used to draw, change text color, and so forth. The color object can be defined in may forms including RGB values, hexadecimal values, or even Director palette references. The base color object looks something like the following:
c = color(#rgb, 255, 255, 255)
The above represents pure white using RGB values. You can also use the following syntax:
- rgb(255,255,255)
This creates a color object based on RGB values. - rgb("FFFFFF")
This creates a color object by converting hexadecimal values to RGB. - color(#paletteIndex, 0)
This references the color in palette index slot 0 in the current Director palette. - paletteIndex(0)
This is the same as above but refers to the palette index directly.
Additionally, you can use Lingo to convert numeric values to other formats. There is existing code, such as the RGB to CMYK conversion script by Eliya Selhub, and HSV to/from RGB converters from Andrew Morton. You can download both scripts at www.mediamacros.com. Since color data is basically just numerical information that defines each pixel, you have total control over adjusting color, adding effects, and converting the data for other uses.
Color data can also come from existing locations. Here are a few places you can find color information:
member("text member").color
This refers to the color of the text in a text cast member.sprite(1).forecolor
This is the foreground color used by a sprite. Note that this returns a number of the palette index the sprite uses (or its closest match).i.getPixel(10,10)
This retrieves the color of a specific pixel of an image object at point 10, 10.
GetPixel and SetPixel
The last example brings us to our next imaging lingo term. getPixel was actually available (though undocumented) in Director 7. It allows you to read in an exact color object at a specific point in an image. For example:
c = i.getPixel(0,0)
This sets the variable c to the color object at the top left corner pixel of your image object. Note that locations start at 0,0 not 1,1, so the first pixel in any row or column is zero. You may additionally get a raw number representing the color of the pixel by adding the #integer symbol.
c = i.getPixel(0,0)
For pure white, this would return -1. This is primarily useful if you are using getPixel and then its counterpart, setPixel, to rapidly edit a lot of pixels at once. Using integers instead of true color objects is simply faster and more efficient. See how setPixel works:
i.setPixel(0, 0, rgb(0,0,0))
This sets the pixel at point 0,0 to pure black (rgb(0,0,0)) Using the raw integer method above, you could also use the integer value to do the following:
i.setPixel(0,0,-1)
This sets it back to pure white. Note that if you use the integer method, you are using the full 32-bit value. If you have alpha channels in your images, you need to be aware that you are overwriting both the color and alpha at the same time, so you may need to use some custom code if you don't want to affect the transparency when setting new color values. If you want the alpha value for a given pixel, there is a simple formula to do this. Raw number values are based on possible combinations. A 24-bit image is made up of 256 possible values for red, 256 possible values for blue, and 256 possible values for green. This comes up to 16777216 possible colors, or (24)ˆ2. The 32-bit images are based on the same, plus an additional 256 possible shades for the alpha. We are now looking at (32)^2 possible values, or 4294967296. So how do we get an alpha's value? Simple, you remove the other three values:
a = i.getPixel(1,1, #integer) / power(2, 8 + 8 + 8)
What this means is that you take each possible combination for a 24-bit color and use it with an alpha value of 0. Then you have each possible value with an alpha value of 1, and so on up to 256. Dividing the current value of the combined image data by 16777216, you get how many "cycles" to go through, or the current alpha value. Confused? Well it can make your head spin a bit at first. Something as conceptual as color is not always easy to translate to raw numbers. Take the time to experiment with this, though, as some of the best effects you will do with imaging lingo will rely on your understanding of how the color system works to correctly modify your images.
Alphas
You have seen that imaging lingo takes into account the use of alpha channels, but how do you interact with them? As mentioned above, you can use getPixel and setPixel to access this information directly, but sometimes you may want to draw, copy portions of an image, or do some other edit to just the alpha channel. To modify alpha data, you must look at how to extract and set the alpha layer of an image directly.
You can extract The alpha layer of an image as an image object. Then you can use any standard image commands to modify it. To extract the alpha, use the Lingo command extractAlpha(). Clever name, huh?
a = i.extractAlpha()
This sets the variable a to a 256 color grayscale image. After making changes, you may want to then set it back. Do this with setAlpha.
i.setAlpha(a)
Note that there are restrictions here. If you try to use extractAlpha on a 16-bit or lower image it will return zero as the image has no alpha. Additionally, setAlpha will have no effect on 16-bit and lower image objects. Always validate your code to ensure an image object exists and is the correct depth. Here are a few ways you can do this:
a = i.extractAlpha() if a = 0 then exit
The above method attempts to get the alpha. If it fails, the process stops running. This approach would have the same effect:
a = i.extractAlpha() if objectP(a) = 0 then exit
The best way though is to check before even trying to get the alpha in the first place:
If i.depth < 32 then exit
This checks the current color depth of the image object and if it's less than 32-bit, you know there is no alpha, so you can stop the current code.
Draw and Fill
You can now edit any image on every pixel, but honestly, that would be very slow and processor intensive. If you wanted to draw a line, fill an entire image with a single color, or add a circle in the center of our image you would spend a lot of unnecessary CPU time copying each pixel one-by-one. Computers are faster than the average pen and paper, but the same rules apply as if you were trying to draw or color a shape by hand. That's where you can use functions to write to larger areas of an image at once.
The draw command does just what is sounds like. It draws a shape on the image at the defined location and the color you specify. Here is a base example…
i.draw(rect(10,10,20,20), rgb(0,0,0))
This would draw a 1 pixel thick black square from 10, 10 to point 20, 20. Simple enough, but what if you want more control? The color parameter can actually be a list of parameters you can define, including shapeType, color, and lineSize. So try drawing a 2-pixel thick red circle in the same location.
i.draw(rect(10,10,20,20), [#color : rgb(255,0,0), #lineSize : 2, #shapeType : #oval])
The shapeType parameter can be #line, #oval, #rect, or #roundRect. Line sizes can be one or larger and color can be any valid color object. Note that you can also use four parameters instead of a rect object to define the area to write to:
i.draw(10,10,20,20, [#color : rgb(255,0,0), #lineSize : 2, #shapeType : #oval])
What about solid areas? Sometimes you may want to fill the entire area with a color. For this you can use the fill command. Fill works exactly like draw except that the result is a solid shape, not an outline. Additionally you have a new property you can use called #bgColor. For filled areas, this defines the color of the outline, so that you can fill the area with one solid color and add a different color border around it. This example creates a 10 X 10 rounded rectangle, filled with black, and a red 2-pixel outline:
i.fill(10,10,20,20, [#color : rgb(0,0,0), #bgColor : rgb(255,0,0), #lineSize : 2, #shapeType : #roundRect])
Note the format of the parameter list, as it will come up again in other imaging functions. With these parameter lists, you can add parameters to define specific elements of the imaging command, but they are entirely optional; you only need to define ones you need to use.
FloodFill – Undocumented
If you have ever used any paint program (including the Director paint window), you know about the bucket tool and flood fill. While you won't find it in any manual, there is a flood fill command in Director that you can use to change the color of large areas with a single command. FloodFill accepts a point as reference and a color object, then goes out from that point evaluating each pixel. If the neighboring pixel matches, it too change. This is very exact though, so even the slightest difference in color will not change. Here is the basic syntax:
i.floodFill(10, 10, rgb(255,0,0))
This will flood fill all of the matching color area from point 10, 10 to red. There are a few behaviors that use other methods, like getPixel and setPixel, such as this one, but the built-in one is very fast. You might want to look at the other examples though, since with getPixel and setPixel you can program in tolerance so that it fills similar colors as well.
CopyPixels
If you think this is fun so far, you haven't seen anything yet. Pixel editing and drawing lines and circles can be useful, but the real power lies in the ability to composite images together and to add effects to them in the process. The most commonly used function in your imaging arsenal is the copyPixels command. CopyPixels takes image data from one object and pastes it into another. In the process, here are some of the things that you can do to the source image as you paste it into its new home…
- Crop it
- Scale it
- Skew it
- Rotate it
- Distort it
- Add ink effects
- Mask it
- Copy without a background
- Blend it
- Dither it
All of this is possible with one command. Let's take a look at how this mighty function works.
i.copyPixels(i2, rect1, rect2)
These are all the required parameters. The above copies the source image (i2) into the destination image(i). It places takes the data from rect2 of image i2 and places it into rect rect1 of itself. Here is an example:
i.copyPixels(myImage, rect(10,10,20,20), rect(50,50,70,70))
This would copy the image data from rect(50,50,70, 70) out of the image stored in myImage and paste it into rect(10,10,20,20) of the main image, i. Not only have you copied over the image, but in the process, you scaled the source image data by half the size.
This easily gives us the ability to "stamp" one image into another. Pretty cool as long as you are just copying rectangular chunks on top of others, but what if you need a little more control? Remember the parameter property list from draw and fill? If so, then the next part will be fairly familiar. Using some predefined properties, you can send more detailed instructions to copyPixels to have it copy only the portions of the image you want, and place them in the new image any way you want.
Advanced CopyPixels
Blend
One of the most basic but also most useful parameters is the ability to blend images. Blending consists of two optional properties you may use.
#blend – This is a value of 0 – 100 with 0 being transparent and 100 being opaque. #blendLevel – This is a value of 0 -255 where 0 is transparent and 255 is opaque.
Why two functions instead of one? The Director score uses a percentage level of 0-100 to define transparency. However, as you learned above with color objects, red, green, blue and alpha vales are based on 0-255. If you want to simulate an alpha blend of 120, then it's easier to use the blendLevel. If you want to mimic a blend of 55% on the stage, then "blend" may be more for you. Both of the following will blend one image onto another at 50%.
i.copyPixels(myImage, i.rect, myImage.rect, [#blend : 50]) i.copyPixels(myImage, i.rect, myImage.rect, [#blendLevel : 127])
This can be very useful for a number of things. Here are a few examples of using blends to get some very nice effects.
- Animated Blend (Example by Dave Mennonah)
- Motion Blur/Trails (Refer to my Devcon Presentation for an example)
- And many other special effects
Inks
Blend is really just an ink effect Director can use. Calling blend or blendLevel automatically uses an ink setting of #blend or #blendTransparent. Any ink effect Director can use on the stage can also be used for imaging lingo as well. You can quickly make photo negatives with the reverse ink:
i.copyPixels(myImage, i.rect, myImage.rect, [#ink : #reverse])
Note that you can also use the numeric values for each ink as well. Refer to the Director help files for more info on the inks available and their numbers/symbols. Using the numeric value the above becomes:
i.copyPixels(myImage, i.rect, myImage.rect, [#ink : 2])
You can use this to apply background transparency when copying an image, ghost, invert, add to a color, subtract from a color, and so forth. Here are two quick examples of ways to use inks for special effects.
Let's create a new image object called "myRed" and fill it with a solid red box. Then we can use it to colorize an image.
myRed = image(1,1,8) myRed.fill(myRed.rect, rgb(255,0,0)) i.copyPixels(myRed, i.rect, myRed.rect, [#ink, #addPin])
Our result is similar to the effect you get in a paint program when you select Colorize and add solid red. Ok, so it's a decent part trick, but what about something useful? Well let's reverse it. What if you want to remove all the red from an image?
i.copyPixels(myRed, i.rect, myRed.rect, [#ink : #subtractPin])
Figure 1. Removing colors from images
This removes all red color data from the image. So where could you use this? How about color separations? Take any image and remove red and green to get just the blue values for an image. Remove blue and red to leave it green, and so forth. This allows you to make three separate images by separating each color out into its own image object.
The really nice thing about inks is that you can easily test before you code. Just take two cast members with your images in them, layer the destination member under the source in the score, and apply ink effects to see what results. Since the color math of blends, add, subtract, and so forth can get a bit confusing, this is a great way to try out your theories before debugging the code.
Another place I use inks is to lighten or darken an image. This can be very useful for making highlights and shadows on images in real-time.
Dither
Dithering takes a flat area of color and creates a pattern of multiple colors to simulate the original when down-sampling an image to a lower bit depth. Director allows you to do this manually by selecting Modify > Bitmap, but you can do this in real time with imaging lingo as well. If you take a large, subtle gradient and down-sample it, you quickly see banding in the image due to the lack of sufficient colors to reproduce the same results. If you dither while down-sampling, the eye can visually blend the pixels to try to smooth the effect of colors not available in the current palette.
Figure 2. 32-bit
Figure 4. 8-bit windows, dithered
Figure 3. 8-bit Windows, no dither
i.copyPixels(myImage, i.rect, myImage.rect, [#dither : 1])
This would use basic dithering to copy the image over. There are two other undocumented dither levels you can use as well.
i.copyPixels(myImage, i.rect, myImage.rect, [#dither : 1215])
This is a slightly slower but more precise dither.
i.copyPixels(myImage, i.rect, myImage.rect, [#dither : 1969])
The slowest dither but is more exact than the other two options. This is the closest you will get to manually using the dither in Director by selecting Modify >Transform Bitmap.
Using Quads to Skew, Rotate, and Distort
Director 7 introduced the concept of quads. A quad is basically a set of four points that describes a four-sided shape. A square or rectangle can be a quad, but so is a rhombus, trapezoid, and so forth. Think of it as a rectangle where you can move the corner points to create non-rectangular shapes. Here is an example of a rectangle and a quad:
rect(0, 0, 10, 10) [point(0.0000, 0.0000), point(10.0000, 0.0000), point(10.0000, 10.0000), point(0.0000, 10.0000)]
Both of these describe an 800 X 600 rectangle. The first uses four coordinates, because you know that the x location of both left-hand points are identical. In the quad, however, each point can be virtually anywhere. When defining the quad of any rectangle, start in the top left corner and go clockwise to describe the location of each point.
Figure 5. A quad with coordinates for all four points
This becomes useful because you can then skew, stretch or even rotate an image based on a quad instead of a rectangle. In the diagram below, you can take the first and second points and move them together, forcing the image into a triangle.
Figure 6. A quad with two points using identical coordinates
Using quads in this manner, you can also skew, stretch, and even rotate images. Here is an example that rotates an image 180 degrees by building a new quad off of the old rectangle's coordinates. It basically moves each point around two spaces so that the source point at the top left becomes the bottom right.
sRect = rect(0,0,800,600) --rotate the rect 180 degrees newQuad = [point(sRect[3], sRect[4]), point(srect[1], sRect[4]), point(sRect[1], sRect[2]), point(sRect[3], sRect[2])]
You can also use quads to replace the destination location. The source must be a rectangle. To copy a rectangle from myImage at rect(0,0,10,20) to a triangle area in the destination image you would use:
i.copyPixels(myImage, [point(5,0), point(5,0), point(10,20), point(0,20)], rect(0,0,10,20))
When working with quads, it's important to keep in mind the source image size and the destination image size. For example, if you have an 800 X 600 image and you rotate it 90 degrees, then it needs a 600 X 800 image to paste into to fit properly.
Here are some uses for quads:
- Rotate an image
- Skew an image
- Create virtual 3D effects (like tapering into the background)
- Combine with blend to create an image morph (check out the Devcon presentation for an example of this)
- A "goo" application for pulling and distorting images (check out the Devcon presentation for an example of this.)
Masking Images
If you start compositing a number of images you will quickly find places where you want to copy part of a non-rectangular area of an image. You can do this a number of ways.
- Use the image's own alpha
- Use another image's alpha
- Create a "mask" image
If you use copyPixels to copy from a 32-bit image to a 16-bit or lower image it will, by default, copy only the image data. If you copy to another 32-bit image, it will copy both the image and the alpha. This can be a problem as it will make the background transparent in the new image, any place it was transparent in the old one. Most of the time, you actually just want to overlay the portion of the source image using the alpha or a mask as more of a cutout. To do this, use #maskImage parameter. Calling this a maskImage can be a bit misleading though, as these are not standard image objects. Instead we have two functions that are specifically for use with the maskImage parameter.
createMask – This is used to create an object that will function similarly to the mask ink in Director.
m = iMaskImage.createMask()
i.copyPixels(myImage, i.rect, myImage.rect, [#maskImage : m])
createMatte – This is used to create an object that will function similarly to Director's matte ink.
m = iMaskImage.createMatte() i.copyPixels(myImage, i.rect, myImage.rect, [#maskImage : m])
You can use an image's alpha, a grayscale image, or any image source to create these objects. Some uses of this are:
- Copying text over an image
- Using a vector gradient to feather an image over another
- Create cutouts of one image on another
- Create puzzle pieces from a larger image (see the Devcon presentation for more info.)
- Compositing images and layers together at run time
- And much more...
Advanced Ideas and Techniques
If you have downloaded the Devcon presentation, you will see source code and examples for the following demos:
- Power Goo
- Morph
- Motion Blur
- Water Ripples
- 3D Terrain
- Video in 3D
Additionally there are other resources listed below with great source code examples you can download and try for things like navigation systems, dynamic menus, effects, games and more. Imaging lingo gives you total control over every pixel of an image, so the rest is up to your imagination. Virtually every Adobe Photoshop or Macromedia Fireworks filter, video transition, and web mouse effect you have seen can be recreated with imaging lingo. Experiment and the sky is the limit.
Resources
- Devcon 2002 Imaging Lingo Presentation
- Imaging behaviors on www.mediamacros.com
- RGB to CMYK conversion script
- HSV to/from RGB converters
- Copy Matching Pixels
- Set Pixel Fill
- Image Flood Fill
- Quick Fill
- New Bitmap From Existing Member
- Blend Images
- Rescale Image
- Change BitDepth of a member
- Change BitDepth
- Super Goo
- Image Magnify
- CloneIt
- Asteroids Game
- Text Box Resizer
- Mouse Puddle (ripple)
- Snow Falling
- Progress Bar
- Rescale
- Ripple
- Lingo Font
- Minefield game
Other sites/resources for imaging Lingo
- Director Online – A number of articles and examples of imaging.
- SetPixel.com – Examples and demos of some really nice imaging effects. Also some uses of imaging with the web cam Xtra.
- Lingo Workshop – Lots of examples including imaging lingo widgets, menus, trees, and more.
- James Newton's Imaging Lingo Examples – Examples of some tricks you can do with imaging lingo.
About the author
Chuck Neal is CEO of MediaMacros, Inc., and owner and operator of www.mediamacros.com, a top resource for Macromedia Director users, featuring behaviors, open-source games, Xtras database, and general information on using Director. Chuck has worked in multimedia and 3D for over eight years and for such diverse clients as Coca-Cola, Hasbro Toys, Sony, DreamWorks Records, the BBC, as well as a number of commercial game and software titles. He has had articles features in numerous online publications, spoken at the Macromedia Developer's conference, and has been a regular presence over the years on the Macromedia forums.Chuck currently accepts projects for CD, Internet, games, kiosks, installations, as well as custom components, product support and cleanup and mastering services for other multimedia firms. He has served in support roles and advisory boards for companies such as Tabuleiro and Integration New Media.