UEFI News and Commentary

Monday, September 17, 2012

HOW TO: Move an Image using Keyboard Input

The most recent project I've been working on has been to move an image around the screen using keyboard input.  I described in a previous post how I had put together a function that displayed an image.  After making a few changes and doing some more work, I was able to create a function that allows the user to use the arrow keys to move the image around the display window.  This article will describe the process to achieve this.  It assumes you have followed the instructions described in the post mentioned above to display an image.

My plans were pretty basic.  The arrow keys would be used to move the image, and I'd use some other key to quit the application.  I would need some sort of loop that would read keyboard input and break out when the proper key was pressed.  In the UEFI spec, I found a function, WaitForEvent(), that would halt execution until a specified event occurred, as well as functions for reading which key was pressed on the keyboard.  After getting that working, it was simply a matter of making sure it dealt with the edges of the graphics output window properly and consolidating some code.

Now, let's go through the code.  Firstly, I made some changes to the DisplayImage() function that I wrote previously.  Since I knew I'd have to display the image over and over again, I wanted to repeat as little code as possible.  Blt() is the function that actually displays the image.  Among the parameters, the data buffer, the instance of EFI_GRAPHICS_OUTPUT_PROTOCOL, and the dimensions of the image all remain constant once they have been initialized, so I made them global variables that can be reached by other functions.

EFI_GRAPHICS_OUTPUT_PROTOCOL *mGraphicsOutput;
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *mBlt;
UINTN mHeight;
UINTN mWidth;

I then created a new function, DisplayImageAt(), that simply took the new coordinates of the image as parameters and called Blt().  It made the code cleaner and easier to read.

int DisplayImageAt (UINTN X, UINTN Y) 
{
  mGraphicsOutput->Blt (
                     mGraphicsOutput,
                     mBlt,
                     EfiBltBufferToVideo,
                     0,
                     0,
                     X,
                     Y,
                     mWidth,
                     mHeight,
                     mWidth * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL)
                     );
  return 0;
}

Now, when we move the image around the screen, we'll have to clear where the image previously was before displaying the image in its new location.  Instead of clearing the entire screen, we can use Blt() to clear it by setting EfiBltVideoFill as the operation.  Now, EfiBltVideoFill takes the top left-hand pixel in an image and fills the specified area with the color of that pixel.  To ensure we always have a black pixel, we can just make one of our own outside the function declaration so we can access it later.

EFI_GRAPHICS_OUTPUT_BLT_PIXEL mBlack = {0, 0, 1};

Next we have to write the function that actually lets us move the image around.  So let's walk through this.

First, we set the current coordinates to wherever the image was initially displayed (in my case, it was centered on the screen).

  XCoordinate = (mGraphicsOutput->Mode->Info->HorizontalResolution / 2) - (mWidth / 2);
  YCoordinate = (mGraphicsOutput->Mode->Info->VerticalResolution / 2) - (mHeight / 2);

Next we start the actual loop that waits for input and acts on it.  First is WaitForEvent(), which will just wait until the specified event happens.  In this case, I found an already-existing event that referred to a keyboard action, WaitForKey, so I used that.  After someone hits a key on the keyboard, ReadKeyStroke() will store which key was pressed into the variable Key.

do {
    gBS->WaitForEvent (1, &gST->ConIn->WaitForKey, &index);
    gST->ConIn->ReadKeyStroke(gST->ConIn, &Key);

The next part makes up the bulk of the function.  It's simply deciding which key was pressed, and updating the coordinates accordingly.  This part also makes sure that the image doesn't try to move past the edges of the window.  The instance of EFI_GRAPHICS_OUTPUT_PROTOCOL keeps track of the resolution of the display window, so we can use that to keep our image from trying to go past the borders.  If the key pressed wasn't one of the arrow keys, the coordinates remain the same.

if (Key.ScanCode != SCAN_NULL) {
      if (Key.ScanCode == SCAN_UP) {
        NewX = XCoordinate;
        if (YCoordinate >= 5) {
          NewY = YCoordinate - 5;
        } else {
          NewY = 0;
        }
        
      } else if (Key.ScanCode == SCAN_DOWN) {
        NewX = XCoordinate;
        NewY = YCoordinate + 5;
        if (NewY > mGraphicsOutput->Mode->Info->VerticalResolution - mHeight) {
          NewY = mGraphicsOutput->Mode->Info->VerticalResolution - mHeight;
        }
      } else if (Key.ScanCode == SCAN_RIGHT) {
        NewY = YCoordinate;
        NewX = XCoordinate + 5;
        if (NewX > mGraphicsOutput->Mode->Info->HorizontalResolution - mWidth) {
          NewX = mGraphicsOutput->Mode->Info->HorizontalResolution - mWidth;
        }
      } else if (Key.ScanCode == SCAN_LEFT) {
        NewY = YCoordinate;
        if (XCoordinate >= 5) {
          NewX = XCoordinate - 5;
        } else {
          NewX = 0;
        }
        
      } else {
        NewX = XCoordinate;
        NewY = YCoordinate;
      }
    } else {
      continue;
    }

Now we need to clear the area where the image is currently displayed before we draw the new one.  So, using the pixel we defined earlier, we can call Blt() to clear the area.

    mGraphicsOutput->Blt(
                      mGraphicsOutput,
                      &mBlack,
                      EfiBltVideoFill,
                      0,
                      0,
                      XCoordinate,
                      YCoordinate,
                      mWidth,
                      mHeight,
                      0
                      );

In the last part of our loop, we call DisplayImageAt() with the new coordinates and set the current coordinates to the correct values.  The condition to continue the loop is whether or not the key pressed was the [End] key.  This way, if the user presses the [End] key, the application will exit.

    DisplayImageAt (NewX, NewY);
    
    XCoordinate = NewX;
    YCoordinate = NewY;

  } while (Key.ScanCode != SCAN_END);

And finally, to finish off the function, the screen is cleared and the success code is returned.

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

  return EFI_SUCCESS;

Now we can build and run the project.  The image movement is controlled by the arrow keys, and pressing [End] will exit the application back to the Shell.

As usual, if you would like me to send you the code, let me know, and I'll get it sent to you.


7 comments:

AndreiW said...

As a presentation and demo it would be a good college tech talk :). Nice!

Werner Glanzer said...

Hi, im stuck, so if you could send me the Code via EMail (lucky5825@gmx.de) you could really help me out! Thanks.

faizalsn said...

I am trying out to display bmp image
Can you please send me the code through email (faizalsn@gmail.com)

Thank you

Hank said...

Hi, would you please send me your code?

伍成静 said...

Hi, I'm Victoria,I would like to see your code of this article and your other 3 articles dealing with image in the same month, would you please send me your code via the email (wuchengjing55@hotmail.com)?I'm kind of working on it now,and your code would really help me out! Thanks for everyting! Victoria

Chao-Yen Wu said...

Hi, would you send me the code of this article and other 2 articles dealing with displaying the image?

My email is lawrencelrc@gmail.com

Thank you.

Tim Lewis said...

You can now find all of the code for this at SourceForge using this link: http://sourceforge.net/p/syslibforuefi/wiki/Home/

Good luck!