0

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;
}
Community
  • 1
  • 1
Mircea
  • 1,671
  • 7
  • 25
  • 41

2 Answers2

1

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.

datenwolf
  • 159,371
  • 13
  • 185
  • 298
  • The point is.. I need to make my code to work for this and just this kind of images how I've post in my question. I'm assuming all my images will be with 24bpp true color, tightly packed, no compression. All my inputs will be under this format. That suppose to be easy... but is not. So if you can help to rotate this kind of image I will be really glad. – Mircea Nov 18 '16 at 13:49
  • @Mircea: The problem is not doing the image rotation. The problem is correctly reading (and also writing) the file. And the simplemost solution would be to use an existing, well tested image file reader/writer library instead of rolling your own. Do that and you're golden. – datenwolf Nov 18 '16 at 13:57
  • I don't know any kind of library just for read and write an image. Everything I've found it's how to read and process the image with the same library, like CImage for example. Anyway, the problems with this type of libraries performance. I mean, if I'm doing in this way will be much faster then I'm using a library I think. So that's the reason I really need som help. Do you have any idea how can I resolve the read/write part without using libraries? – Mircea Nov 18 '16 at 14:03
  • And I don't know but I think the read/write are correctly because if I'm just read and write an image without rotated it will be write correctly – Mircea Nov 18 '16 at 14:18
  • @Mircea: No, it's not correct code. It's super fragile, broken code and as soon as something comes along that does not match its expectations _it **will** break!_ **Do not make the assumption that your code will only see image files you created, or vetted or selected!** The code you have there is actually dangerous and it's trivial to prepare a specially crafted file that, when read into with your program will take over your computer! You ask why? Because your code doesn't implement top-down stored images (which are indicated by a negative height value). – datenwolf Nov 18 '16 at 15:06
  • @Mircea: In the bmpsuite I linked you can find two "top down" images. Trying to read either of them with your program will cause a crash. *I tell you again: Use a proper image loader library!* Seriously, don't roll your own file format readers/writers if you don't know **exactly** what you're doing. There as several super easy to use image reader/writer libraries for C++, like Boost::GIL. *Use that!* – datenwolf Nov 18 '16 at 15:08
1

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;
}
Barmak Shemirani
  • 30,904
  • 6
  • 40
  • 77
  • I've appreciated you help but this function like rest of them... is not working... did you try this on my image? here it's the [result](http://imgur.com/NAbqU2P) this code executed with my image. I know this is just for practice, that's the point... this is the reason I don't want to use external libraries, I need to finish it and I want to understand this with out libraries. But from some reason... doesn't meter what function I've try... no one will works for this :/ – Mircea Nov 19 '16 at 01:02
  • I don't know your original image. What is the width & height of your image? What is the bitcount? What operating system are you on? – Barmak Shemirani Nov 19 '16 at 01:05
  • The original image is [here](https://www.dropbox.com/s/aggfmos2umttac2/Input-1.bmp?dl=0), width & height = 2650 both of them, Windows 10, and what do you mean with bitcount? – Mircea Nov 19 '16 at 01:07
  • Looks like a padding problem. See updated answer. I removed your make shift `BMP` structure and replaced it with standard header files, that was a waste of time... – Barmak Shemirani Nov 19 '16 at 06:03
  • Amazing, finally it's working. Thanks a lot for your effort :D. I lose a lot of this with this stupid problem – Mircea Nov 19 '16 at 09:32
  • Actually that was wrong, it only worked on square images. I fixed it in update. You should use GDI+ with rotation matrix rather than this code. – Barmak Shemirani Nov 19 '16 at 23:46