How to resize bitmap images using C++ [shorts #2]

Since 2016, I’ve always returned back to my Macintosh Classic CRT build, constantly trying to refine the project and make it easier to reproduce my results. In my latest attempt, I used a Raspberry Pi to communicate with the monitor. While that method worked like a charm for me, others have reported a few problems they’ve encountered and possible solutions. In my next attempt to finally get this project right, I decided to go down another path. Without going into too much detail here, the new method required me to implement a simple scaling and dithering algorithm in C++. This short article discusses my image scaling solution in C++.

How to resize a bitmap image with C++

One of the easiest methods to make a bitmap smaller in C++ is to delete some of the image’s columns and lines. Suppose you wanted to reduce the width and height of an image by 50%. Then, you’d have to delete every other line and column of the source image. In other words, you’d only keep half of the original pixels. Note that this method may produce very rough looking and pixelated results, but it is good enough for this application, as I’m actively trying to reduce details in the image.

Unfortunately, it’s not enought to just reduce images by 50% in this project. Instead, I want the program to generate target images with a fixed width and height (e.g., 512 x 342, or 800 x 600, etc.), regardless of the size of the source images. Therefore, my function for resizing images looks a little more complicated:

// Important: source must be different from result!
// This function destroys the original contents of result
// targetWidth must be less than or equal to source->getWidth()
void ImageManipulator::reduce(Image* source, Image* result, int targetWidth, int targetHeight)
{
    int copiedColumns = 0;
    int copiedLines = 0;

    // Calculate the ration between the target and source image
    // We'll use this value later to determine how many lines/columns we need to skip before
    // copying another line/column to the target buffer
    float horizontalRatio = (float)targetWidth / (float)source->getWidth();
    float verticalRatio = (float)targetHeight / (float)source->getHeight();

    // 'current' values = arbitrary number that tells the program when to copy a pixel to the
    // target image. The program copies a pixel whenever one of these values is at least 1.
    // We start with both values set to 1 so that we keep the first column and line of the
    // source image.
    float horizontalCurrent = 1.0f;
    float verticalCurrent = 1.0f;

    // Set the bitmap data so that other programs can read the resulting *.bmp file
    result->setWidth(targetWidth);
    result->setHeight(targetHeight);
    result->recalculateBuffers();

    // Iterate over all columns of the source image
    for(int x = 0; x < source->getWidth(); x++)
    {
        // If we reached the target width, abort
        if(copiedColumns == targetWidth)
            break;
        
        // Copy the current column to the resulting image if the current value is at least 1
        if(horizontalCurrent >= 1.0f)
        {
            // Iterate over each pixel in the current column (from the top of the image to the bottom)
            for(int y = 0; y < source->getHeight(); y++)
            {
                if(copiedLines == targetHeight)
                    break;
                
                // But make sure to only copy the needed pixels of the current column
                if(verticalCurrent >= 1.0f)
                {
                    unsigned char pixel[3];

                    source->getPixel(x, y, &pixel[0]);
                    result->setPixel(copiedColumns, copiedLines, pixel[0], pixel[1], pixel[2]);

                    copiedLines += 1;
                    verticalCurrent -= 1.0f;
                }

                verticalCurrent += verticalRatio;
            }

            copiedLines = 0;
            copiedColumns += 1;
            horizontalCurrent -= 1.0f;
            verticalCurrent = 1.0f;
        }

        horizontalCurrent += horizontalRatio;
    }
}

First, my algorithm determines the ratio between the source and target image sizes. It does this separately for each axis. Next, you can see the current values. You can think of these as weights. Whenever this weight is at least 1.0, we copy one or more pixels to the resulting image. When we copy pixels, we reduce the weight by 1.0. These weights are, again, separete for each axis. After each iteration, the algorithm adds the previously determined ratio on to the weight. The program, therefore, iterates over all columns and lines of the image, checks the weight, copies pixels if necessary, and then modifies the weights accordingly.

Note that this algorithm only works if you want to shrink images. Furthermore, there are several other methods you can employ when resizing images. You could, for example, preserve the aspect ratio of the original image. You could also scale the image in one direction and then apply the same operation in the other direction.

Download the source code

You can download the source code on GitHub. If you share or use it, please make sure to include a link to this article. Thank you!

Enlarge images using C++

Note that this method only makes images smaller. If you want to make images larger, you’d first have to create a buffer with the size of the new image. Then, you copy the contents of the source image into the target buffer. Again, suppose, you want to increase an image’s size by 100% (i.e., double its size). Then, you’d skip every other line and column of the target buffer when copying the source image:

Figure 1: Spread the pixels in the target buffer

Next, you’d have to generate pixels to fill in the blank spots generated in step one. Here, you could, for example, duplicate the original pixels or you could use an average value between two pixels:

Figure 2: Then, fill the empty spots with newly generated pixels. From left to right: Empty pixels; duplicated pixels; averages (filter size 4×4, edges wrapped)

These are only two examples. Note how some methods work best for certain images while others produce undesirable results in certain situations. Duplicating pixels creates crisper edges which is perfect for pixel graphics. Averaging and filtering creates softer (blurry) lines and edges. The latter option is great for generating thumbnails of photographs, artwork, video previews, and so on. The first option is faster and easier to implement.

One thought on “How to resize bitmap images using C++ [shorts #2]

Leave your two cents, comment here!

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.