2

I'm trying to rotate an BMP image using C/C++ but it's not working.

I've made some functions for read, write and rotation. The read and write functions are working perfectly fine but not rotate from some reason.

EDIT(sin, cos and rotate function)

BMP struct:

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

WRITE:

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);
    int i;
    unsigned char tmp;
    for (i = 0; i < image.size; i += 3) {
        tmp = image.pixels[i];
        image.pixels[i] = image.pixels[i + 2];
        image.pixels[i + 2] = tmp;
    }
    fwrite(image.pixels, sizeof(unsigned char), image.size, out); // read the rest of the data at once
    fclose(out);
}

READ:

BMP readBMP(string filename) {
    BMP image;
    int i;
    string fileName = "Input Files/" + filename;
    FILE *f = fopen(fileName.c_str(), "rb");
    fread(image.header, sizeof(unsigned char), 54, f); // 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.size = 3 * image.width * image.height;
    image.pixels = new unsigned char[image.size]; // allocate 3 bytes per pixel
    fread(image.pixels, sizeof(unsigned char), image.size, f); // read the rest of the data at once
    fclose(f);

    for (i = 0; i < image.size; i += 3) {
        unsigned char tmp = image.pixels[i];
        image.pixels[i] = image.pixels[i + 2];
        image.pixels[i + 2] = tmp;
    }
    return image;
}

ROTATE:

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

    double radians = (degree * M_PI) / 180;
    int sinf = (int) sin(radians);
    int cosf = (int) cos(radians);

    double x0 = 0.5 * (image.width - 1);     // point to rotate about
    double y0 = 0.5 * (image.height - 1);     // center of image

    // rotation
    for (int x = 0; x < image.width; x++) {
        for (int y = 0; y < image.height; y++) {
            long double a = x - x0;
            long double b = y - y0;
            int xx = (int) (+a * cosf - b * sinf + x0);
            int yy = (int) (+a * sinf + b * cosf + y0);

            if (xx >= 0 && xx < image.width && yy >= 0 && yy < image.height) {
                pixels[(y * image.height + x) * 3 + 0] = image.pixels[(yy * image.height + xx) * 3 + 0];
                pixels[(y * image.height + x) * 3 + 1] = image.pixels[(yy * image.height + xx) * 3 + 1];
                pixels[(y * image.height + x) * 3 + 2] = image.pixels[(yy * image.height + xx) * 3 + 2];
            }
        }
    }
    newImage.pixels = pixels;
    return newImage;
}

MAIN:

int main() {
    BMP image = readBMP("InImage_2.bmp");
    image = rotate(image,180);
    writeBMP("Output-11.bmp", image);
    return 0;
}

That sin=0.8939966636(in radians) and cos=-0.44807361612(in radians) means this image should be rotated by 90 degree.

Here it's my original image:

original

And here it's moment my result in:

 result

Could someone help me understand what I'm doing wrong here? I really need this function to work.

I cannot use any third party libraries for this code.

Jim Hayes
  • 2,126
  • 1
  • 15
  • 21
Mircea
  • 1,671
  • 7
  • 25
  • 41
  • 1
    In rotate, newImage = image. So you are overwriting the image pixels as you rotate. – stark Nov 15 '16 at 23:03
  • 1
    PIxel addressing should be `[y * image.width + x]`, assuming a raster order – samgak Nov 15 '16 at 23:07
  • @samgak that should have some effect just for the direction of rotation I think. But anyway it's not working... I've edit my **rotation function** adding the new result and what did you said. And in this moment... I have a more strange result then before. – Mircea Nov 15 '16 at 23:31
  • I would say that the problem is because you are truncating the sin and cos: int sinf = (int) sin(radians); int cosf = (int) cos(radians); the common practice is keeping the floating point numbers until the very last moment. – Mauricio Cele Lopez Belon Sep 23 '19 at 18:38

3 Answers3

2

It must handle the rotation in the same pixel format the bmp uses. You are only transforming one byte for each pixel. The pixels look to be wider. This should be easy to fix now that the problem has been identified.

If you need more speed, notice that you have invariants (x and y) that are incremented for each iteration:

for (int y = 0; y < image.height; y++) {
        double a = x - x0;
        double b = y - y0;
        int xx = (int) (+a * cos - b * sin + x0);

Move the b outside of the loop and change the multiplication to addition:

double b = -y0;
for (int y = 0; y < image.height; ++y) {
    int xx = (int) (a * cos - b + x0);
    b += sin;

Notice that the a * cos is a constant for the whole y-loop? Fuse it to the b. Do the same for x0.

double b = a * cos - y0 + x0;
for (int y = 0; y < image.height; ++y) {
    int xx = (int) (- b);
    b += sin;

Notice that the -b costs as well? Negate b.

double b = -(a * cos - y0 + x0);
for (int y = 0; y < image.height; ++y) {
    int xx = (int) b;
    b -= sin;

See what we did there? Next: get rid of the doubles. Use fixed-point. Float-to-integer conversions can be costly. At best, they are useless here.

Last but not least, you are writing into memory vertically. This is very, very bad for the write combine and will dramatically reduce the performance. Consider changing the loop order so that x-loop is the innermost one.

Extra: use tiled memory layout for the image to improve memory locality when reading. Cache will work more efficiently. Not super important here as you only process the image once and tiling would be more expensive than the speed up. But if you want to animate the rotation then tiling should benefit you. Also, with tiling the performance does not fluctuate depending on the rotation angle so the animation will be more consistent (and faster).

EDIT: add explanation how to support more bytes per pixel:

pixels[(y * image.height + x) * 3 + 0] = image.pixels[(yy * image.height + xx) * 3 + 0];
pixels[(y * image.height + x) * 3 + 1] = image.pixels[(yy * image.height + xx) * 3 + 1];
pixels[(y * image.height + x) * 3 + 2] = image.pixels[(yy * image.height + xx) * 3 + 2];

This is starting to be a bit hard to read, but you do see what we are doing there?

t0rakka
  • 904
  • 9
  • 10
  • ok so... If you speak about speed, yeh I will change it in the future, first them all I need to make my rotation to works. `It must handle the rotation in the same pixel format the bmp uses.`, well... I have no idea how to do this. I mean I've thought I'm doing this already. Could you write for me this part of code please? Because I've have no idea how to do this, and I was trying few hour and I didn't get nothing... It will be really helpful if you will could do this for me. – Mircea Nov 16 '16 at 05:32
  • I added some code to demonstrate how to handle wider pixels. I notice that you happily write over the old image pointer w/o freeing the memory. You might want to fix that memory leak as well. I recommend you use some RAII-friendly type for storage if you are prone to errors like that (std::vector, std::array, std::unique_ptr or similar). – t0rakka Nov 16 '16 at 12:32
  • I will see what I will do about the memory leak after I will finish this problem. In this moment it's let's say... 50% of my problem done. I have just 2 more problem in this moment, **1. I don't know why but the image it's not rotated in the center and it's somewhere next to center(you can see my new result because I've update my question)** and the second one, **2. I don't know why but I lose the color property of the image, after I call the rotate function from some reason all my image iti just black and whait.** Could you help me to fix that 2 problems please? – Mircea Nov 16 '16 at 13:14
  • Fix the y * image.height to y * image.width, same for yy. You are addressing the image wrong. The for-loop to swap(red, blue) when reading and writing the bmp is also unnecessary, remove it. Last but not least, don't call your rotation function .. just read and write the bmp and see if the read-write breaks the color. You need confidence that things work up to a certain point so that you can identify the problem (now it could be anywhere). – t0rakka Nov 17 '16 at 13:11
0
BMP newImage = image;

This sets newImage to image. The BMP structure is:

     unsigned char *pixels;

So, both newImage and image have the same pixels pointer.

Unfortunately, the logic in your rotation code assumes that newImage and image are different, independent buffers.

You will need to do more work to create your newImage object. You will need to allocate its own image buffer, clear the memory to an empty image, and set its pixels to point to it.

To properly do this, your BMP class should be made Rule Of Three compliant.

Community
  • 1
  • 1
Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • Ok well if what did you said it's true then I need just to make a new set of pixels like `unsigned char *pixels = new unsigned char[image.size]; ` and work with it. And just in the end I need to do something like ` newImage.pixels = pixels;`. So I did this and I get a more strange result... I've edit my **rotation function** by adding that details and my new result, can you check it again please? – Mircea Nov 15 '16 at 23:29
  • "I get a more strange result" is not a valid problem description. – Sam Varshavchik Nov 16 '16 at 00:51
  • well I mean, I don't get the result I was expecting for. I've update my question so you can check what I've try but it's not working and you will see and the new result. – Mircea Nov 16 '16 at 08:38
  • You reading and writing function clearly show that each pixel occupies three bytes in the `pixels[]` array, and there are `width*3` bytes per row (the RGB components). Your rotation code assumes that each pixel occupies one byte, and there are `width` bytes per row. Fail. On each pass through the rotation loop you should be moving three bytes, at a time, comprising a single pixel. The calculation of each pixel in the `pixels[]` array must be adjusted accordingly. – Sam Varshavchik Nov 16 '16 at 11:37
  • ... and you need to make the same adjustment when allocating the new pixels array, etc... – Sam Varshavchik Nov 16 '16 at 11:48
  • Ok well I've fix this problem now, and my rotate it's almost done but I have just 2 more problems. **1. I don't know why but the image it's not rotated in the center and it's somewhere next to center(you can see my new result because I've update my question)** and the second one, **2. I don't know why but I lose the color property of the image, after I call the rotate function from some reason all my image iti just black and whait**. Could you help me to solve that 2 problem please? I've update my question wtth all the changed and my actual result. – Mircea Nov 16 '16 at 13:16
  • This has become too deep. You'll need to figure it out yourself. My suggestion: prepare a simple 5x5 pixel image. Use your debugger to step through the algorithm, one line at a time and examine how the pixels get moved. This is small enough to be able to look at the raw data and figure out each pixel. Knowing how to use a debugger is a required skill for every C++ developer. – Sam Varshavchik Nov 16 '16 at 13:21
0

You could copy your pixels (buffer) to a new, separate and same type/sized one, (as said before) then rotate it using something like this:

http://www.sourcetricks.com/2012/07/rotate-matrix-by-90-degrees.html

you could then recopy the new buffer back to your original pixel buffer, clean (free) it (edit:it means the copy ) and finally redraw all.

EDIT:

I was thinking in pseudo code: 
    rotate image 
    { 
    imagecopy = duplicate (image) 
    (do the rotation on imagecopy)  
    copypixels (imagecopy (to), image) 
    free imagecopy 
    } 

copy and dup are for loops but dups adds a malloc

and "newImage.pixels = pixels;" will not work, you must loop through both arrays and copy the values one by one like in "writeBMP"

oh and by

It must handle the rotation in the same pixel format the bmp uses.

i think he meant use INTEGERS, like int or long and not float or double that complicates your code for no benifit

kiwe
  • 11
  • 5
  • Well if you are checking my rotate function again.. you will see I've already add a new buffer for pixels, here `unsigned char *pixels = new unsigned char[image.size];` and on the end of this function I will do `newImage.pixels = pixels;` So I don't have this problem anymore – Mircea Nov 16 '16 at 00:13
  • rotate image { imagecopy = duplicate (image) (do the rotation on imagecopy) – kiwe Nov 16 '16 at 07:58
  • image = copy ( imagecopy) free imagecopy } sry i suck with all my comments i'm new :) copy and dup are for loops but dups adds a malloc – kiwe Nov 16 '16 at 08:00
  • and "newImage.pixels = pixels;" is not that easy, you must loop through both arrays and copy the values one by one like in "writeBMP" – kiwe Nov 16 '16 at 08:06
  • Ok but I don't get it... I mean, I've make a new array of pixels with this `BMP newImage = image; unsigned char *pixels = new unsigned char[image.size]; ` and after this I'm working just with this new array of pixels. And on the end of this function I'm doing image.pixels = pixels. It's not ok? I mean it's like a duplicate....? No? – Mircea Nov 16 '16 at 08:18
  • Ok well I've try and a new method. I've change the declaration `unsigned char *pixels = new unsigned char[image.size];` with this one `memcpy(newImage.pixels, image.pixels, (size_t) image.size);` that suppose to copy my entire array into the new one. Or I'm wrong? Anyway it's not working... – Mircea Nov 16 '16 at 08:33
  • so whe i say copy i think about your : ` for (i = 0; i < image.size; i += 3) { tmp = image.pixels[i]; image.pixels[i] = image.pixels[i + 2]; image.pixels[i + 2] = tmp; }` and dup is the SAME thing but before it you must call memcpy, new or malloc, so you do the rotation on a separate buffer of pixels Then if still is weird try and outputing the new image buffer right before the end of the function "rotate",before copy back. you'll then see if you're issue is with the "copy back to" or the "rotate" part – kiwe Nov 16 '16 at 19:10
  • finally i do not think "memcpy" allocates memory for you pixels, it merely copies over the content so you NEED a malloc/new before it – kiwe Nov 16 '16 at 19:11
  • and again: you should NOT USE SIN OR COS WHEN DEALING WITH ARRAY INDEXES, THEY MIGHT(will) RETURN FLOATS AND ARE NOT SUITABLE FOR ARRAYS /!\ look at the site i linked, there are MANY ways to do this without, you'll also save (some)computing time :) – kiwe Nov 16 '16 at 19:14