Mircea Mircea - 20 days ago 6
C++ Question

C++: BMP rotate image

Ok guys, it's the third time I'm posting the same question (previous are here and here).

Now at this time I will try to explain what's my problem:


  1. So first them all, I need to rotate a .bmp image and it's not rotate correctly. But I don't need to rotate a random image with extension .bmp, I need to rotate this one. I've tried with many other images and all of them was rotated correctly, except mine.

  2. In this moment my code it works just for 180-degree, how could make it to works on any degree which is multiple of 90-degree (I need to rotate my image just with 90, 180 or 270 degrees, not more).

  3. I don't need any kind of external library for this code like CImage, OpenCV, ImageMagik and so on... I need to make this code to work.



So yeh, that's it. And here you can find my actual result.

CODE:

#include <array>

using namespace std;

struct BMP {
int width;
int height;
unsigned char header[54];
unsigned char *pixels;
int row_padded;
int size_padded;
};

void writeBMP(string filename, BMP image) {
string fileName = "Output Files\\" + filename;
FILE *out = fopen(fileName.c_str(), "wb");
fwrite(image.header, sizeof(unsigned char), 54, out);

unsigned char tmp;
for (int i = 0; i < image.height; i++) {
for (int j = 0; j < image.width * 3; j += 3) {
//Convert(B, G, R) to(R, G, B)
tmp = image.pixels[j];
image.pixels[j] = image.pixels[j + 2];
image.pixels[j + 2] = tmp;
}
}
fwrite(image.pixels, sizeof(unsigned char), image.size_padded, out);
fclose(out);
}

BMP readBMP(string filename) {
BMP image;
string fileName = "Input Files\\" + filename;
FILE *in = fopen(fileName.c_str(), "rb");

fread(image.header, sizeof(unsigned char), 54, in); // read the 54-byte header

// extract image height and width from header
image.width = *(int *) &image.header[18];
image.height = *(int *) &image.header[22];

image.row_padded = (image.width * 3 + 3) & (~3); // ok size of a single row rounded up to multiple of 4
image.size_padded = image.row_padded * image.height; // padded full size
image.pixels = new unsigned char[image.size_padded]; // yeah !

if (fread(image.pixels, sizeof(unsigned char), image.size_padded, in) == image.size_padded) {
unsigned char tmp;
for (int i = 0; i < image.height; i++) {
for (int j = 0; j < image.width * 3; j += 3) {
//Convert (B, G, R) to (R, G, B)
tmp = image.pixels[j];
image.pixels[j] = image.pixels[j + 2];
image.pixels[j + 2] = tmp;
}
}
}
fclose(in);
return image;
}

BMP rotate(BMP image, double degree) {
BMP newImage = image;
unsigned char *pixels = new unsigned char[image.size_padded];

int height = image.height;
int width = image.width;
for (int x = 0; x < height; x++) {
for (int y = 0; y < width; y++) {
pixels[(x * width + y) * 3 + 0] = image.pixels[((height - 1 - x) * width + (width - 1 - y)) * 3 + 0];
pixels[(x * width + y) * 3 + 1] = image.pixels[((height - 1 - x) * width + (width - 1 - y)) * 3 + 1];
pixels[(x * width + y) * 3 + 2] = image.pixels[((height - 1 - x) * width + (width - 1 - y)) * 3 + 2];
}
}
newImage.pixels = pixels;
return newImage;
}

int main() {

BMP image = readBMP("Input-1.bmp");
image = rotate(image, 180);
writeBMP("Output.bmp", image);

return 0;
}

Answer

You have major memory leak. pixels = new unsigned char[size]; must be freed otherwise there is potentially several megabytes leak with every rotation. You have to rewrite the function to keep track of memory allocations.

When you rotate the image by 90 or 270 of the image, the widht/height of image changes. The size may change too because of padding. The new dimension has to be recorded in header file.

In C++ you can use fopen, but std::fstream is preferred.

Here is an example which works in Windows for 24bit images only. In Big-endian systems you can't use memcpy the way I used it below.

Note, this is for practice only. As @datenwolf explained you should use a library for real applications. Most standard libraries such Windows GDI library (basic drawing functions) offer solution for these common tasks.

#include <iostream>
#include <fstream>
#include <string>
#include <Windows.h>

bool rotate(char *src, char *dst, BITMAPINFOHEADER &bi, int angle)
{
    int pad = 4 - ((bi.biWidth * 3) % 4);
    if (pad == 4) pad = 0;

    switch (angle)
    {
    case 0: 
        break;
    case 90:
    case 270: 
        std::swap(bi.biWidth, bi.biHeight); 
        break;
    case 180:
        break;
    default:
        std::cout << "cannot handle this angle\n";
        return false;
    }

    int h = bi.biHeight;
    int w = bi.biWidth;
    for (int y1 = 0; y1 < h; y1++)
    {
        int n1, n2 = 0;
        for (int x1 = 0; x1 < w; x1++)
        {
            n1 = 3 * (x1 + w * y1) + y1 * pad;

            switch (angle)
            {
            case 0:
                n2 = n1;
                break;
            case 90:
                n2 = 3 * (h - y1 - 1 + h * x1) + x1 *pad;
                break;
            case 180:
                n2 = 3 * (x1 + w * (h - y1 - 1)) + (h - y1 - 1) * pad;
                break;
            case 270:
                n2 = 3 * (y1 + h * x1) + x1 * pad;
                break;
            }

            dst[n2 + 0] = src[n1 + 0];
            dst[n2 + 1] = src[n1 + 1];
            dst[n2 + 2] = src[n1 + 2];
        }
    }

    pad = 4 - ((bi.biWidth * 3) % 4);
    if (pad == 4) pad = 0;
    for (int y1 = 0; y1 < h; y1++)
        for (int x1 = 0; x1 < pad; x1++)
            dst[bi.biWidth * 3 + x1] = 0;
    bi.biSizeImage = (bi.biWidth * 3 + pad) * bi.biHeight;

    return true;
}

int main()
{
    std::string input = "input.bmp";
    std::string output = "output.bmp";

    BITMAPFILEHEADER bf = { 0 };
    BITMAPINFOHEADER bi = { sizeof(BITMAPINFOHEADER) };

    std::ifstream fin(input, std::ios::binary);
    fin.read((char*)&bf, sizeof(bf));
    fin.read((char*)&bi, sizeof(bi));

    int size = bi.biSizeImage;
    char *src = new char[size];
    char *dst = new char[size];
    fin.read(src, size);

    if (rotate(src, dst, bi, 270))
    {
        bf.bfSize = 54 + bi.biSizeImage;
        std::ofstream fout(output, std::ios::binary);
        fout.write((char*)&bf, 14);
        fout.write((char*)&bi, 40);
        fout.write((char*)dst, size);
    }

    delete[]src;
    delete[]dst;

    return 0;
}