UEFI News and Commentary

Thursday, September 20, 2012

HOW TO: Move an Image Using a Timer

Now that I've been able to get images to move using keyboard input, my next project has been to get an image to move around on its own and bounce off the edges of the screen.  This article will describe the process to do that, and it assumes that you have followed the directions for previous articles detailing how to move an image using keyboard input.

The first thing I realized about this is that getting an image to move on its own will require a timer.  SetTimer() is a function in Boot Services that will signal an event every so often. I can then use WaitForEvent() to wait until the timer signals an event and then move the image according to that event, rather than user input.

So let's walk through the code that we use to get this image bouncing around.

Firstly, we have our global variables.  We still need the instance of EFI_GRAPHICS_OUTPUT_PROTOCOL, the dimensions of the image, and the black pixel to clear the image, so we can keep those.  Only one more is needed, and that's a pointer to an EFI_EVENT.  This event  will be a simple, empty event that the timer will signal after every given time interval.

EFI_GRAPHICS_OUTPUT_PROTOCOL *mGraphicsOutput;
UINTN mHeight;
UINTN mWidth;

EFI_GRAPHICS_OUTPUT_BLT_PIXEL mBlack = {0, 0, 1};

EFI_EVENT mEvent;

Now we can get into the meat of the actual function that bounces this image around the screen.  We start off by initializing the EFI_EVENT using CreateEvent().  Like I mentioned earlier, it's an empty timer event so we set the first parameter (the event type) to EVT_TIMER, the last one as a pointer to mEvent, and the others are 0 and NULL.

  gBS->CreateEvent (
    EVT_TIMER,
    0,
    NULL,
    NULL,
    &mEvent
    );

After that, we call SetTimer() to create our timer that'll be the signal when to move the image.  The first parameter is mEvent, the event that is signaled whenever the timer goes off.  Second, we have to specify what sort of a timer this is, so we put TimerPeriodic, which means the timer will go off every interval, rather than only once.  The final parameter is the time interval that determines how often we want the timer to signal.  It's measured in increments of 100 nanoseconds, so I put a large number in.

  gBS->SetTimer (
    mEvent,
    TimerPeriodic,
    1000000  
    );

Next, we need to initialize all our variables to their starting values.  We give the ScanCode an intial value of NULL just to start.  DeltaX and DeltaY are the values that help determine which direction the image will be moving.  I set them both to a non-zero number so that the image won't simply bounce up and down.  After that, we have to initialize the array of events that WaitForEvent() is going to wait for.  First is one that reads if the user pressed something on the keyboard so that we can have an exit condition.  Second is mEvent, which is what the timer will signal every time interval. And finally, we initialize the current coordinates of the image.

  Key.ScanCode = SCAN_NULL;

  DeltaX = 5;
  DeltaY = 5;
  events[0] = gST->ConIn->WaitForKey;
  events[1] = mEvent;
  CurrentX = (mGraphicsOutput->Mode->Info->HorizontalResolution / 2) - (mWidth / 2);
  CurrentY = (mGraphicsOutput->Mode->Info->VerticalResolution / 2) - (mHeight / 2);

Finally, we get to our loop.  The condition for this loop is that the [End] key is not pressed.  If it is, the application will quit.  We start off by waiting for an event.  It will be waiting either for a timer signal or for a keyboard key to be pressed.  So once an event is signaled, we have to check to see which it is.  If it was a key press, we read which key was pressed and check the loop condition.  If [End] was pressed, we exit the loop.

do {
    gBS->WaitForEvent (2, events, &index);

    if (index == 0)  {
      gST->ConIn->ReadKeyStroke(gST->ConIn, &Key);
      continue;

So if it wasn't a keyboard event that was signaled, it must have been a timer event, so we have to move the image.  The first step is to set the new values of the X and Y coordinates.  We do that by adding the values of DeltaX and DeltaY to the current coordinates.  We then have to check to see if the image would go beyond the borders of the window.  If they do, set the new coordinates to the value so the image is right on the edge of the screen, and then change DeltaX or DeltaY so that the image will "bounce" off the wall.  If it hits the left or right borders, change the sign of DeltaX.  If it hits the top or bottom, change the sign of DeltaY.

} else {
      
      NewX = CurrentX + DeltaX;
      NewY = CurrentY + DeltaY;

      if (NewX + mWidth > mGraphicsOutput->Mode->Info->HorizontalResolution) {
        NewX = mGraphicsOutput->Mode->Info->HorizontalResolution - mWidth;
        DeltaX = -DeltaX;
      } else if (NewX < 0) {
        NewX = 0;
        DeltaX = -DeltaX;
      }
      if (NewY + mHeight > mGraphicsOutput->Mode->Info->VerticalResolution) {
        NewY = mGraphicsOutput->Mode->Info->VerticalResolution - mHeight;
        DeltaY = -DeltaY;
      } else if (NewY < 0) {
        NewY = 0;
        DeltaY = -DeltaY;
      }

The final parts of the loop are mainly concerned with displaying the image.  We use the EfiBltVideoFill operation of Blt() to clear the image and then call our own function, DisplayImageAt() to display the image, using the new coordinates.  Lastly, we set the current coordinates to the coordinates of the newly displayed image.

      mGraphicsOutput->Blt(
                      mGraphicsOutput,
                      &mBlack,
                      EfiBltVideoFill,
                      0,
                      0,
                      CurrentX,
                      CurrentY,
                      mWidth,
                      mHeight,
                      0
                      );
      DisplayImageAt (NewX, NewY);
    
      CurrentX = NewX;
      CurrentY = NewY;
    }
   
  } while (Key.ScanCode != SCAN_END);

The last two lines of the function lie outside the loop, and they simply clear the screen and return EFI_SUCCESS.  

  gST->ConOut->Reset (gST->ConOut, FALSE);
  return EFI_SUCCESS;

And that's it!  The image will move around the screen on its own and "bounce" off the edges of the window, and the application will quit when the [End] key is pressed.

If you are interested in seeing the entirety of the code, let me know and I'll get it sent to you.






1 comment:

Unknown said...

You are amazing, Shannon. Thank you.