In this section you'll get a brief introduction to using imaging Lingo before creating the graph for the training log. Let's begin by talking about the most basic element of imaging Lingo, the image object. An image object is essentially bitmap data that resides in RAM. Image objects can be created from scratch using Lingo:
myImage = image(width, height, depth)
An image object can also be accessed as a property on certain Director cast members, such as text, bitmap, certain video types, Flash, and even the Stage:
myImage = member("aMember").image myImage = (the stage).image
When you get the image from a cast member or the Stage, the dimensions of the image will be that of what you retrieved it from. In addition to being able to get the image, you are also able to set it. This allows you to first manipulate the image in memory before placing it back for viewing. In this manner you can composite multiple images together, do cropping and scaling, color manipulation, etc.
member("aMember").image = myImage
Because image objects reside in memory, they are very fast to work with. You can composite and animate many layers using Lingo much faster than Director can, because nothing goes to the screen until you tell it to.
Using the Duplicate Function
A common error when first getting to know imaging Lingo, is not taking advantage of the speed that is offered by manipulating images directly in memory. Let's look at a simple example.
Open the imaging_test.dir file from the Lesson08 folder on the CD-ROM.
When the movie opens you'll see that it is empty except for a few bitmap cast members in the internal cast and their corresponding sprites on the stage. You'll be using the bitmaps to test out some code in the Message window.
Position the playhead within the first sprite span, then open the Message window. Enter the following Lingo:
a = member(1).image b = member(1).image.duplicate()
You now have two variables, a and b, pointing to the image of member 1. That's not entirely true, however. Variable a is pointing directly at member 1's image, but variable b is pointing at a duplicate of the image.
Let's see how this is different by using Lingo's fill command, which will fill a rectangle, within a bitmap, with a chosen color. Enter the following:
a.fill(a.rect, rgb(0, 255, 0))
Did you expect the sprite and its cast member to turn green? Because you used the rect property of the image, you filled the entire thing with solid green, rgb(0, 255, 0). Because variable a was pointing directly at the image of the member, and not at a duplicate of it, any modifications you do on the variable are immediately reflected in the cast member. If you didn't have a duplicate available in variable b, there'd be no way to recover the bitmap you just erased to green. To recover the image, enter the following:
member(1).image = b
Presto! The sprite and cast member are back to showing the original house image.
When working with the images of cast members, always work with a duplicate, unless you want the member to be updated in real time.
Doing Flood Fills
While being able to fill a rectangular area is useful in its own right, depending on what you're doing you may need to fill an irregular shaped area. A flood fill will allow you to fill any enclosed space with a color. Imagine a children's coloring book application, where you might have simple black outlines on a white page, such as that shown in cast member 2.
As a quick example, let's create a five-minute coloring book demo right now.
Double-click the behavior channel at frame 25 to open a Script window, then create a standard loop on frame behavior.
on exitFrame me _movie.go(_movie.frame) end
Now, you want to add a behavior to the bitmap sprite that will flood fill from whatever point in the image the user clicks on. To do this will require a bit of simple math to compensate for the difference in stage coordinates versus member coordinates.
Within the cast member, the top left corner of the bitmap is at point (0, 0). However, when you place the sprite on the stage, the top left corner could be at point (50, 50) or point (100, 50), or whatever. Examine the following image, which helps to clarify this point.
You can see that on the Stage, the sprite's upper left corner is at point (80, 60). To turn the leaves of the tree green, the user clicks at point (110, 100), as shown. Now, look at where point (110, 100) is within the cast member. It's not anywhere near the leaves of the tree. To compensate, you need to subtract the point at the top left corner of the sprite from the mouse's click location.
Subtracting point (80, 60) from point (110, 100) yields point (30, 40), which in the cast member's coordinates is right within the leaves of the tree, where it should be.
Right-click the sprite, and select Script from the contextual menu. Edit the default mouseUp handler so it looks like this:
on mouseUp me myLeft = sprite(me.spriteNum).left myTop = sprite(me.spriteNum).top myPoint = the mouseLoc - point(myLeft, myTop) sprite(me.spriteNum).member.image.floodFill(myPoint, _global.theColor) end
Before seeing how this works, let's quickly discuss what the code is doing here. First, two variables are set to the left, and top properties of the sprite. These properties, along with right and bottom, give you the extent of the sprite's bounding rectangle. Examine the following image:
This image shows the various sprite properties you can use to get positional information. You can see how the left, top, right and bottom properties relate to the sprite's bounding box while the loc, locH, and locV properties are based on the sprite's registration point. Note that the locH and locV properties match the individual components of the loc point. If this were an actual Sprite, on Stage, you'd get the following output from the Message window.
trace(sprite(1).loc -- point(150, 150) trace(sprite(1).locH) -- 150
Note that you can also retrieve the numbers by using the Property inspector as shown here.
What you're doing with the numbers will determine whether you want the straight numbers (locH and locV), or if you want a point (loc). Or you can do what you did in the code, and use the point() function to create a point. Subtracting the point(myLeft, myTop) from the mouseLoc determines the correct point within the cast member.
This is then fed to the floodFill method of the member's image. The global theColor will allow you to fill the image with any color you choose.
Press Play, then click somewhere in the image.
You'll notice the image fills with black wherever you click it. This is because the global variable theColor has not been set, and so is void.
Open the Message window and enter theColor = rgb(0, 150, 255), then click in the sky section of the image.
The sky turns a nice light blue. Now change theColor to rgb(255, 255, 0) and click in the sun, making it a nice bright yellow. You can continue to use the Message window to color the scene. To erase an area, change theColor to rgb(255, 255, 255), which is solid white.
Any variables you set in the Message window automatically become global in scope. This is why you don't need to use _global.theColor within the Message window.
Stop the movie when you're finished coloring.
Let's now look at Lingo's pixel methods in order to eliminate the need to change the color using the Message window.
Lingo has two methods for modifying individual pixels within an image. Those are getPixel and setPixel. The getPixel method allows you to get the RGB color value at a specified point within the image. Conversely, setPixel allows you to modify the color of an individual pixel. Although you won't use setPixel in this lesson, it's still good to know about it. The getPixel method, however, is a good way to allow a user to pick colors from a swatch.
Select an empty cast member in the cast, then click the Paint Window icon in Director's toolbar to open the paint window.
For a quick sample of what you can do, you can use the paint window to quickly create a small swatch of colors for the coloring book example.
Use the Filled Rectangle tool to draw a bunch of small colored squares like what is shown here.
Don't worry about being sloppy, this is only a test. You just want some colors to choose from.
When you're finished, close the paint window, drag the new cast member from the cast, and place it to the right of the other sprite. Use the Property inspector to change the ink to Background Transparent.
You can now use the getPixel method within a behavior attached to the sprite to modify the global theColor variable.
Right-click the color swatch sprite and select Script from the context menu. Edit the mouseUp handler so it appears as follows:
on mouseUp me myLeft = sprite(me.spriteNum).left myTop = sprite(me.spriteNum).top myPoint = the mouseLoc - point(myLeft, myTop) _global.theColor = sprite(me.spriteNum).member.image.getPixel(myPoint) end
As with the coloring-book sprite, you need to determine the click position within the cast member, because the image you're referring to is a member property. Once you have the point to get the color from, the getPixel method is called and the returned color is stored in the global theColor. Because the floodFill method used in the coloring-book sprite uses the same global variable, the fill is performed with whatever color is clicked on in the swatch.
Play the movie and color the image using the colors available in the swatch you created. When you're finished, stop the movie.
Now, let's look at the powerhouse of imaging Lingo, copyPixels.
The copyPixels method is really quite simple in concept: it allows you to copy chunks of one image into another image. But simple as that might be, you can do some pretty cool things with it. Let's look at a few simple examples before moving on to the graph itself.
The syntax of the copyPixels method is as follows:
destination_image.copyPixels(source_image, destination_rect, source_rect)
In addition to this basic syntax, you can add a property list that will allow you to copy at varying levels of transparency, use different ink effects, even use a mask image.
Let's say you have a need to do color separations. Using copyPixels you can easily remove color channels from images by combining the image and color channel, using the Subtract Pin ink type.
Move the playhead to frame 5 so that the first sprite, with the house, is showing on stage.
So that you can return to the original, you should first make a duplicate and store it in a global variable.
Open the Message window, and enter the following Lingo:
original = member(1).image.duplicate() work = member(1).image
You now have a duplicate original and a working copy.
Now, make a solid color image of whatever color you want to remove from the base image. Begin by removing red:
redImage = image(1, 1, 24) redImage.fill(redImage.rect, rgb(255, 0, 0))
No mistake here! You created a 1x1 pixel image and filled it with red. Enter the following in the Message window:
work.copyPixels(redImage, work.rect, redImage.rect, [#ink: #subtractPin])
With its red component now removed, the sprite on Stage turns a shade of green.
You can see how copying from a source rectangle into a destination rectangle lets you scale the image as you copy. The destination and scale rectangles can be arbitrary sizes. Here you copied a 1 x 1 pixel bitmap into a 526 x 345 pixel image.
Because you used the Subtract Pin ink type, with a red image, the red component was removed. Subtract ink works by subtracting the color in the foreground from the background. However, if the subtraction makes the color value go below zero, the colors will "wrap" around the color palette. Subtract pin works exactly the same way, except the colors are limited to zero, instead of wrapping.
Let's remove the blue from the image, which will result in only the green channel remaining.
Enter the following Lingo in the Message window.
redImage.fill(redImage.rect, rgb(0, 0, 255)) work.copyPixels(redImage, work.rect, redImage.rect, [#ink: #subtractPin])
By further subtracting the blue channel from the image, you are left with the image's green channel. You can get creative and remove other color channels, besides just red, green, and blue.
Now let's look further at copying data from one image into another image.
Copying and Scaling
You can use the copyPixels function to copy chunks of one image into another at varying transparency levels, as well as do arbitrary scaling on the image being copied.
Restore the now-green house image back to its original state.
member(1).image = original
If you missed something and the member doesn't get restored, you can either ignore it or simply reopen the imaging_test.dir file from the CD.
As an example, let's have a bit of fun with the little portrait image in the cast.
In the Message window, execute the following Lingo.
work.copyPixels(member(3).image, rect(204, 192, 236, 224), member(3).rect)
Notice, the head appeared in the window of the house! Try one more. (Hint: simply modify the line in the Message window to use the new rect).
work.copyPixels(member(3).image, rect(222, 67, 252, 95), member(3).rect)
The head now appears in the attic window, as shown.
Now try copying the portrait and scaling it up to match that of the house image.
Enter the following in the Message window.
work.copyPixels(member(3).image, work.rect, member(3).rect, [#blendLevel: 150])
The portrait image now appears quite large over the house image, but semi-transparent. Using the #blendLevel property in the list lets you specify a level of transparency from 0255, with 0 being completely transparent.
Although there's much, much more that can be done with imaging Lingo, this introduction should give you some ideas on which to build. In the next section, you'll use imaging Lingo's drawing abilities to draw rectangles and circles for the graph.