Pages

Friday, September 5, 2014

[Minix][Tutorial 8] Loading bmp images

This is one of the best tutorials because we are finally going to load some images to our program! This will enable our game to have somewhat cool graphics. So, shall we start?

Implementing the bitmap loader

Go ahead and create Bitmap.h and Bitmap.c inside your src folder.



As usual, do not forget to declare the .c source file in the Makefile:



Below are the code snippets of Bitmap.h and Bitmap.c. You should do a little research and make sure to know exactly what is going on, rather than just copy/pasting. LCOM teachers will not tolerate it.

Bitmap.h

#pragma once

/** @defgroup Bitmap Bitmap
 * @{
 * Functions for manipulating bitmaps
 */

typedef enum {
    ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT
} Alignment;

typedef struct {
    unsigned short type; // specifies the file type
    unsigned int size; // specifies the size in bytes of the bitmap file
    unsigned int reserved; // reserved; must be 0
    unsigned int offset; // specifies the offset in bytes from the bitmapfileheader to the bitmap bits
} BitmapFileHeader;

typedef struct {
    unsigned int size; // specifies the number of bytes required by the struct
    int width; // specifies width in pixels
    int height; // specifies height in pixels
    unsigned short planes; // specifies the number of color planes, must be 1
    unsigned short bits; // specifies the number of bit per pixel
    unsigned int compression; // specifies the type of compression
    unsigned int imageSize; // size of image in bytes
    int xResolution; // number of pixels per meter in x axis
    int yResolution; // number of pixels per meter in y axis
    unsigned int nColors; // number of colors used by the bitmap
    unsigned int importantColors; // number of colors that are important
} BitmapInfoHeader;

/// Represents a Bitmap
typedef struct {
    BitmapInfoHeader bitmapInfoHeader;
    unsigned char* bitmapData;
} Bitmap;

/**
 * @brief Loads a bmp image
 *
 * @param filename Path of the image to load
 * @return Non NULL pointer to the image buffer
 */
Bitmap* loadBitmap(const char* filename);

/**
 * @brief Draws an unscaled, unrotated bitmap at the given position
 *
 * @param bitmap bitmap to be drawn
 * @param x destiny x coord
 * @param y destiny y coord
 * @param alignment image alignment
 */
void drawBitmap(Bitmap* bitmap, int x, int y, Alignment alignment);

/**
 * @brief Destroys the given bitmap, freeing all resources used by it.
 *
 * @param bitmap bitmap to be destroyed
 */
void deleteBitmap(Bitmap* bmp);

/**@}*/

Bitmap.c

#include "Bitmap.h"

#include "stdio.h"
#include "Graphics.h"
#include "Utilities.h"

Bitmap* loadBitmap(const char* filename) {
    // allocating necessary size
    Bitmap* bmp = (Bitmap*) malloc(sizeof(Bitmap));

    // open filename in read binary mode
    FILE *filePtr;
    filePtr = fopen(filename, "rb");
    if (filePtr == NULL)
        return NULL;

    // read the bitmap file header
    BitmapFileHeader bitmapFileHeader;
    fread(&bitmapFileHeader, 2, 1, filePtr);

    // verify that this is a bmp file by check bitmap id
    if (bitmapFileHeader.type != 0x4D42) {
        fclose(filePtr);
        return NULL;
    }

    int rd;
    do {
        if ((rd = fread(&bitmapFileHeader.size, 4, 1, filePtr)) != 1)
            break;
        if ((rd = fread(&bitmapFileHeader.reserved, 4, 1, filePtr)) != 1)
            break;
        if ((rd = fread(&bitmapFileHeader.offset, 4, 1, filePtr)) != 1)
            break;
    } while (0);

    if (rd = !1) {
        fprintf(stderr, "Error reading file\n");
        exit(-1);
    }

    // read the bitmap info header
    BitmapInfoHeader bitmapInfoHeader;
    fread(&bitmapInfoHeader, sizeof(BitmapInfoHeader), 1, filePtr);

    // move file pointer to the begining of bitmap data
    fseek(filePtr, bitmapFileHeader.offset, SEEK_SET);

    // allocate enough memory for the bitmap image data
    unsigned char* bitmapImage = (unsigned char*) malloc(
            bitmapInfoHeader.imageSize);

    // verify memory allocation
    if (!bitmapImage) {
        free(bitmapImage);
        fclose(filePtr);
        return NULL;
    }

    // read in the bitmap image data
    fread(bitmapImage, bitmapInfoHeader.imageSize, 1, filePtr);

    // make sure bitmap image data was read
    if (bitmapImage == NULL) {
        fclose(filePtr);
        return NULL;
    }

    // close file and return bitmap image data
    fclose(filePtr);

    bmp->bitmapData = bitmapImage;
    bmp->bitmapInfoHeader = bitmapInfoHeader;

    return bmp;
}

void drawBitmap(Bitmap* bmp, int x, int y, Alignment alignment) {
    if (bmp == NULL)
        return;

    int width = bmp->bitmapInfoHeader.width;
    int drawWidth = width;
    int height = bmp->bitmapInfoHeader.height;

    if (alignment == ALIGN_CENTER)
        x -= width / 2;
    else if (alignment == ALIGN_RIGHT)
        x -= width;

    if (x + width < 0 || x > getHorResolution() || y + height < 0
            || y > getVerResolution())
        return;

    int xCorrection = 0;
    if (x < 0) {
        xCorrection = -x;
        drawWidth -= xCorrection;
        x = 0;

        if (drawWidth > getHorResolution())
            drawWidth = getHorResolution();
    } else if (x + drawWidth >= getHorResolution()) {
        drawWidth = getHorResolution() - x;
    }

    char* bufferStartPos;
    char* imgStartPos;

    int i;
    for (i = 0; i < height; i++) {
        int pos = y + height - 1 - i;

        if (pos < 0 || pos >= getVerResolution())
            continue;

        bufferStartPos = getGraphicsBuffer();
        bufferStartPos += x * 2 + pos * getHorResolution() * 2;

        imgStartPos = bmp->bitmapData + xCorrection * 2 + i * width * 2;

        memcpy(bufferStartPos, imgStartPos, drawWidth * 2);
    }
}

void deleteBitmap(Bitmap* bmp) {
    if (bmp == NULL)
        return;

    free(bmp->bitmapData);
    free(bmp);
}

You should now compile the program to make sure there are no errors related to what we have just introduced to the project.
Here is a quick explanation about the Bitmap functions: loadBitmap, drawBitmap and deleteBitmap.

loadBitmap

This function receives a path to an image as a string and loads that image to memory, it then returns a pointer to the memory region where the image was loaded.

drawBitmap

This function receives a bitmap pointer, a coordinate and an alignment.

If the alignment is ALIGN_LEFT, the bitmap is drawn with its top left corner at the specified coordinates.
If the alignment is ALIGN_CENTER, the bitmap is drawn with the middle of the top border at the specified coordinates.
If the alignment is ALIGN_RIGHT, the bitmap is drawn with its top right corner at the specified coordinates.

deleteBitmap

This function receives a pointer to a bitmap and frees the memory used by it, destroying the bitmap.

Preparing an image

Since we are using a 16-bit graphics mode, we can not load every image type. In fact, the load function in Bitmap.c is only able to load .bmp images which are saved in 5:6:5 mode.

In order to use images in our project we will need to prepare them by opening them with an editor and save them in this specific format. I am going to use GIMP to edit the images.

Let's add an image background to our program. Since we are using a graphics mode with a resolution of 800x600, I am going to use this image.

Go ahead and open the image with GIMP. Now select File > Export and name it test.bmp. An export dialog will pop up; expand the Advanced Options and under 16 bits select R5 G6 B5 and finally press Export.



This image is now prepared to be correctly loaded by our program, so let's add it. Inside the res folder, create a new folder called images. Paste the exported image - test.bmp - inside the newly created images folder.



If you open test.bmp you will notice the quality has decreased. That has to due with the fact we exported it as a 16 bit image, since that is the only mode supported by our program. There's nothing to do about it.

Do not forget to repeat the same exporting process every time you want to add a new image to the project. You first need to export it as a 16-bit 5:6:5 .bmp image.

Loading the new background

Open FlappyNix.h and include Bitmap.h. Also, add a Bitmap* called test to the FlappyNix struct.
Open FlappyNix.c and load the bitmap as shown in line 24.



After that, go to the draw method and instead of filling the screen - remove that line, draw the loaded bitmap - add line 87. Your draw method should now look as follows:



We are now ready to see the final result! But first, do not forget to compile and since we have edited the res folder, you will also need to run the install script again. Always remember, if the res folder is modified, rerun sh install.sh.

Now you are ready to run the program. It should look something like the image below. Isn't that awesome?



Back to index

Click here to go back to the index post.

No comments:

Post a Comment