macdevcenter.com
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Basics of Transparent Blitting, Part 2

by Michael J. Norton
08/27/2004

This is it, the session you’ve been waiting for – the transparency blit routine. In our last session, Basics of Transparent Blitting, we waded through the swamp of the hexadecimal number system, as well as navigated our way around pixel boundary rectangles. Now we pull it all together and understand how transparency pixel blitting is used in video-game animation. I know you future Game Boy Advance SP developers are chomping at the bit and want to see how it’s done.

Lesson 4: Retrieving Raw Pixel Data

To carry out a blitting operation we need to know the following -- the source raw-image data and the destination raw-image data. Our source image is the monster sprite and the destination image is the location on the offscreen buffer where we want the monster to be rendered.

Let’s load the art file and create the sprites image and the offscreen buffer.


# load the art file
set filename "/game_dev/game_art.gif"
set sprites [image create photo -file $filename]

# create the offscreen buffer
set video_game_width 640
set video_game_height 480

# create a blank offscreen image
set offscreen [image create photo \
    -height $video_game_height -width $video_game_width]
  
# copy the background mountains image
$offscreen copy $sprites -from 0 0 639 479 -to 0 0 639 479

Now let’s get the raw source-image data. Remember the raw source-image is the monster sprite pixels. Using our clever math example from above, we have:

set src_x 265
set src_y 737
set width 63
set height 63

# calculate source bounding rectangle
set src_x2 [expr $src_x + $width]
set src_y2 [expr $src_y + $height]

# get source image raw data
set raw_src_data \
    [$sprites data -from $src_x $src_y $src_x2 $src_y2]

And we need the raw-image data of the offscreen buffer we’re copying the monster into. Again, notice the math we plugged in from the discussion above to calculate the destination bounding rectangle.

# calculate destination bounding rectangle
set dst_x 320
set dst_y 303
set dst_x2 [expr $dst_x + $width]
set dst_y2 [expr $dst_y + $height]

# get destination image raw data
set raw_dst_data \
    [$offscreen data -from $dst_x $dst_y $dst_x2 $dst_y2]

From Figure 3, I showed that our raw_src_image, the sprite data, has 63 scan rows of pixel information. And each scan row has 63 pixels of information. To carry out our transparent pixel blit we will need to walk each scan row and look at all 63 pixels per scan row.

Scan Rows

In order to walk all 63 scan rows of our raw-pixel data we will need to write a loop. The idea behind a computer loop is making the computer count up to 63 on its own. When a computer counts it starts with the number 0 instead of the number 1 as you or I would. For the computer to count to 63, starting with 0, it would count to 62.

Using Tcl we would use a for loop to count to 63. A Tcl for-loop looks like this:

() 32 % for { set count 0 } { $count < 63 } { incr count } {
> puts $count
> }
0
1
2
3
:
59
60
61
62
() 33 %

The ingredients of the Tcl for loop include the start of the loop, count = 0. We want the loop to start counting at 0. The next ingredient of the for loop we need to know is when to stop the loop. In this case, we keep running the loop as long as the value of count is less than ( < ) 63. Finally, we need to tell the loop to count. The Tcl command, incr count, tells the loop to go to the next value of count. So if count = 6, the Tcl incr (which stands for increment) will set count to 7.

So how do we use the for loop to walk scan rows? Well, we need the loop to count to 63 for us. This for loop looks a lot like the previous for loop, except I used the variable scan_row_index instead of count. This doesn’t change anything in the loop. I am just making the for loop more humanly readable for the task at hand. When you read the loop you’ll know we are examining scan rows rather than just simply counting.

for {set scan_row_index 0} {$scan_row_index < $height} \
    {incr scan_row_index} {
    puts $scan_row
  }

Just as our previous example, we need to start the loop at some value. We are using the name scan_row_index for our counting. The value of scan_row_index represents what scan row we are currently examining in the loop. We set scan_row_index to start at 0. We want the counting of scan_row to continue while less than the value of height. Remember, height is set to 63 in our script. While scan_row_index is less than height we will incr the value of scan_row_index. We now have a simple for loop that counts to 63. But what about the pixel values in the scan rows we would like to analyze? How do we access those pixel values?

Each scan row is a Tcl list. The Tcl list contains all 63 pixel values. We will use the Tcl command, llindex, to retrieve a scan row of pixel information. This looks like:

for {set scan_row_index 0} {$scan_row_index < $height} \
    {incr scan_row_index} {

    set src_scan_row [lindex $raw_src_data $scan_row_index]
    set dst_scan_row [lindex $raw_dst_data $scan_row_index]
      puts "$scan_row_index: $src_scan_row"
  }

The for loop is our workhorse for the transparent pixel blit. The sample output below shows output from the for loop. Scan row 0 contains 63 pixels -- all of them are our transparency pixel, the RGB values are in hexadecimal, c6 00 6b. This information is actually stored in the Tcl list as #c6006b. Remember, #c6006b is our transparency pixel value. We don’t draw the transparency pixels.

As we go through more scan rows, like scan row 13 and 14, we see RGB values that are not hex, c6 00 6b. These are the pixel values we are going to draw onto the background art. Go ahead and test this script in the Wish Shell and see for yourself what the raw RGB pixel values look like. I edited out most of the values. The * * * notation depicts were where I cut long lines and made them short for this presentation.

Sample Output:

  0: #c6006b #c6006b #c6006b * * * #c6006b #c6006b #c6006b
  1: #c6006b #c6006b #c6006b * * * #c6006b #c6006b #c6006b
  :
  13: #c6006b #c6006b #c6006b * * * #ffffff #5a737b * * * #c6006b #c6006b
  14: #c6006b #c6006b * * * #310039 #310039 *** #c6006b #c6006b
  :
  61: #c6006b #c6006b #c6006b * * * #c6006b #c6006b #c6006b
  62: #c6006b #c6006b #c6006b * * * #c6006b #c6006b #c6006b

The Script:

# walking the scan rows

# load the art file
set filename "/game_dev/game_art.gif"
set sprites [image create photo -file $filename]

# create the offscreen buffer
set video_game_width 640
set video_game_height 480

# create a blank offscreen image
set offscreen [image create photo \
    -height $video_game_height -width $video_game_width]

# copy the background mountains image
$offscreen copy $sprites -from 0 0 639 479 -to 0 0 639 479

# load the art file
set filename "/game_dev/game_art.gif"
set sprites [image create photo -file $filename]

# create the offscreen buffer
set video_game_width 640
set video_game_height 480
  
# create a blank offscreen image
set offscreen [image create photo \
    -height $video_game_height -width $video_game_width]

# copy the background mountains image
$offscreen copy $sprites -from 0 0 639 479 -to 0 0 639 479

set src_x 265
set src_y 737
set width 63
set height 63
  
# calculate source bounding rectangle
set src_x2 [expr $src_x + $width]
set src_y2 [expr $src_y + $height]
  
# get source image raw data
set raw_src_data \
    [$sprites data -from $src_x $src_y $src_x2 $src_y2]

# calculate destination bounding rectangle
set dst_x 320
set dst_y 303
set dst_x2 [expr $dst_x + $width]
set dst_y2 [expr $dst_y + $height]
  
# get destination image raw data
set raw_dst_data \
    [$offscreen data -from $dst_x $dst_y $dst_x2 $dst_y2]
  
for {set scan_row_index 0} {$scan_row_index < $height} \
    {incr scan_row_index} {

    set src_scan_row [lindex $raw_src_data $scan_row_index]
    set dst_scan_row [lindex $raw_dst_data $scan_row_index]
      puts "$scan_row_index: $src_scan_row"
}

Walking Scan Row Pixels

Our previous scripting example demonstrated how to loop through all the scan rows. Now we need to examine every RGB pixel value in each scan row. We know there are 63 pixels per scan row. We can accomplish this task by looping through each pixel in the scan row. Borrowing the for loop from our previous example, we have:

for {set scan_row_index 0} {$scan_row_index < $height} \
  {incr scan_row_index} {

  set src_scan_row [lindex $raw_src_data $scan_row_index]
  set dst_scan_row [lindex $raw_dst_data $scan_row_index]
}

What we need to add here is what is called a nested loop. That is computer jargon for putting a loop inside of a loop. It looks like this:

for {set scan_row_index 0} {$scan_row_index < $height} \
  {incr scan_row_index} {

  set src_scan_row [lindex $raw_src_data $scan_row_index]
  set dst_scan_row [lindex $raw_dst_data $scan_row_index]
  
  for {set pixel_index 0} {$pixel_index < $width} \
      {incr pixel_index} {
      set src_pixel [lindex $src_scan_row $pixel_index]
      set dst_pixel [lindex $dst_scan_row $pixel_index]
  }
  # END for each pixel
}
# END for each scan row

Please notice in the nested loop that we are grabbing both the src_pixel and dst_pixel simultaneously. We need to examine both pixels to determine which pixel gets written to the destination buffer. We examine the source pixels and check to see if it is the transparency pixel (#c6006b).

# check if source pixel is transparent pixel
if {$src_pixel == $trans_pixel} {
  # write the destination pixel
  set raw_scan_row "$raw_scan_row $dst_pixel"
} else {
  # write the source pixel
  set raw_scan_row "$raw_scan_row $src_pixel"
}

If the value of the source pixel (src_pixel) is #c6006b -- in our example the transparent pixel -- we draw the background pixel (dst_pixel). However, if the source pixel isn’t the transparent pixel we will draw the source pixel. So what is going on with this code?

set raw_scan_row "$raw_scan_row $dst_pixel"

We are looping through the pixels in each scan row. With each pixel we append (add to the end of the buffer) the pixel value we want to render. Let’s look at an example of the loop and raw_scan_row values.

  pixel_index = 0 raw_scan_row = “#ffffff”
  pixel_index = 1 raw_scan_row = “#ffffff #c0000”
  pixel_index = 2 raw_scan_row = “#ffffff #c0000 #cf0c0c”
  :
  pixel_index = 61 raw_scan_row = “#ffffff #c0000 #cf0c0c * * * #ab0cbf”
  pixel_index = 62 raw_scan_row = “#ffffff #c0000 #cf0c0c * * * #ab0cbf 
  #cdc0ff”

When the loop completes, that is, pixel_index = 62, the scan row will have 63 RGB pixel entries. Counting from 0 to 62 is 63 pixels if that last statement throws you off.

Putting it all together, this is what our blitting code looks like:

for {set scan_row_index 0} {$scan_row_index < $height} \
  {incr scan_row_index} {

  set src_scan_row [lindex $raw_src_data $scan_row_index]
  set dst_scan_row [lindex $raw_dst_data $scan_row_index]
  
  for {set pixel_index 0} {$pixel_index < $width} \
      {incr pixel_index} {
      set src_pixel [lindex $src_scan_row $pixel_index]
      set dst_pixel [lindex $dst_scan_row $pixel_index]
  
      # check if source pixel is transparent pixel
      if {$src_pixel == $trans_pixel} {
          # write the destination pixel
          set raw_scan_row "$raw_scan_row $dst_pixel"
      } else {
      # write the source pixel
        set raw_scan_row "$raw_scan_row $src_pixel"
      }
  }
  # END for each pixel
lappend raw_image_data $raw_scan_row
}
# END for each scan row

This pulls all the lines of code together; you will notice I added a line of code at the end of the for pixel_index loop. I am updating the new scan row that contains the results of our transparent blit. This is one scan row we updated in for pixel_index loop. The results of the transparency blit for that one scan row are in the raw_scan_row data list. This is one scan row of 63 scan rows we are building for the blit operation.

Once we have created the new image we need to copy it over to the offscreen buffer. Remember, the offscreen buffer is our destination buffer. Our frame of animation, using our cool transparency pixel blitting, is sitting in a Tcl list, raw_image_data.

# create a new blank temporary image buffer
set temp_image \
    [image create photo \
    -height $height -width $width -format gif]
  
# copy raw_image_data into the temp buffer
$temp_image put $raw_image_data -to 0 0

# copy the temp image buffer to the destination buffer
$dst_image copy $temp_image -from 0 0 $width $height \
    -to $dst_x $dst_y $dst_x2 $dst_y2

Using Tk image library we create a blank image buffer, temp_image, to copy our frame of animation into. The raw_image_data is placed into the Tk image using the image library procedure put. This places our frame of animation from the Tcl list into the Tk image buffer. Once the animation sequence is in temp_image we can copy temp_image out to the destination buffer, dst_image. Which in this example, is pointing to the offscreen buffer.

Pages: 1, 2

Next Pagearrow