0
votes

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;
}
2
You'd have an easier time using OpenCV - RyanP

2 Answers

1
votes

The BMP file format is a complicated, convoluted beast and there's no such thing as a "simple" BMP file reader. The code you have there makes certain hard coded assumptions on the files you're trying to read (24bpp true color, tightly packed, no compression) that it will flat (on its face) when it encounters anything that isn't that specific format. Unfortunately, for you, the majority of BMP files out there is not of that kind. To give you an idea of what a fully conforming BMP reader must support have a look at this page:

http://entropymine.com/jason/bmpsuite/bmpsuite/html/bmpsuite.html

And the code you have up there does not even check if there's a valid file magic bytes signature and if the header is valid. So that's your problem right there: You don't have a BMP file reader. You have something that actually spits out pixels if you're lucky enough the feed it something that by chance happens to be in the right format.

1
votes

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)
{
    //In 24bit image, the length of each row must be multiple of 4
    int padw = 4 - ((bi.biWidth * 3) % 4);
    if(padw == 4) padw = 0;

    int padh = 4 - ((bi.biHeight * 3) % 4);
    if(padh == 4) padh = 0;

    int pad2 = 0;
    if(padh == 1 || padh == 3) pad2 = 2;

    bi.biHeight += padh;

    int w = bi.biWidth;
    int h = bi.biHeight;
    if(angle == 90 || angle == 270)
    {
        std::swap(bi.biWidth, bi.biHeight);
    }
    else
    {
        bi.biHeight -= padh;
    }

    for(int row = 0; row < h; row++)
    {
        for(int col = 0; col < w; col++)
        {
            int n1 = 3 * (col + w  * row) + padw * row;
            int n2 = 0;

            switch(angle)
            {
            case 0:   n2 = 3 * (col + w * row) + padw * row; break;
            case 90:  n2 = 3 * ((h - row - 1) + h * col) + pad2 * col; break;
            case 180: n2 = 3 * (col + w * (h - row - 1)) + padw * (h - row - 1); break;
            case 270: n2 = 3 * (row + h * col) + pad2 * col; break;
            }

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

    for(int row = 0; row < bi.biHeight; row++)
        for(int col = 0; col < padw; col++)
            dst[bi.biWidth * 3 + col] = 0;

    bi.biSizeImage = (bi.biWidth + padw) * bi.biHeight * 3;

    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);
    if(!fin) return 0;

    fin.read((char*)&bf, sizeof(bf));
    fin.read((char*)&bi, sizeof(bi));

    int size = 3 * (bi.biWidth + 3) * (bi.biHeight + 3);
    char *src = new char[size];
    char *dst = new char[size];

    fin.read(src, bi.biSizeImage);

    //use 0, 90, 180, or 270 for the angle
    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, bi.biSizeImage);
    }

    delete[]src;
    delete[]dst;

    return 0;
}