Pull to refresh

Pixel image rotation

Level of difficultyEasy
Reading time13 min
Views1.3K

Brief problem formulation

The program accepts as input the absolute path to the image in the bmp extension and the path where you save the result of the work. Then, it rotates the image by 90 degrees counterclockwise. Afterwards, the program saves the new image.

The program is executed on C.

BMP file structure

BMP-file comprises the heading (it weighs 14 bytes) and the raster array (it weighs 40 bytes). I make the heading by following structure:                                               

struct __attribute__((packed)) bmp_header {
    uint16_t bfType;
    uint32_t bfileSize;
    uint32_t bfReserved;
    uint32_t bOffBits;
    uint32_t biSize;
    uint32_t biWidth;
    uint32_t biHeight;
    uint16_t biPlanes;
    uint16_t biBitCount;
    uint32_t biCompression;
    uint32_t biSizeImage;
    uint32_t biXPelsPerMeter;
    uint32_t biYPelsPerMeter;
    uint32_t biClrUsed;
    uint32_t biClrImportant;
};

BMP-file heading contains the following information:

  • bfType: 2 bytes, file type;

  • bfSize: 4 bytes, file size;

  • bfReserved1: 4 bytes, reserved, must be settled to the value of 0;

  • bfOffBits: 4 bytes, offset from start to raster data;

Raster information heading (raster information). All in all, there are only 40 bytes of heading of raster image, including the following:

  • biSize: 4 bytes; the size of the information header, which is 40;

  • biWidth: 4 bytes, indicates the image width in pixels;

  • biHeight: 4 bytes, indicates the image height in pixels;

  • biPlanes: 2 bytes, indicates the number of color planes for the target device, always equal to 1;

  • biBitCount: 2 bytes, indicates byte/pixel quantity and takes the values 1, 2, 4, 8, 16, 24, 32;

  • biCompression: 4 bytes, indicates the type of image compression most commonly used 0 (BI_RGB), which means no compression;

  • biSizeImages: 4 bytes, indicates the size of the raster data; when using the BI_RGB format, it can be set to 0;

  • biXPelsPerMeter: indicates horizontal extension, unit of measurement - pixel/meter, signed integer;

  • biClrUsed: indicates the number of color indexes in the palette used by the bitmap(raster) image, 0 means to use all;

  • biClrImportant: indicates the number of color indexes that have an important effect on the display of the image, and 0 indicates that it is important;

Right after the heading the raster array follows in which pixels are saved consequently and by strokes. Each pixel is set by the following structure of 3 bytes size:

struct pixel {
uint8_t b;
uint8_t g;
uint8_t r;
};

Padding

If the width of the image in bytes is a multiple of four, then the lines go one after the other without gaps. If the width is not a multiple of four, then it is padded with garbage bytes to the nearest multiple of four. These bytes are called padding.

Example:

  1. Let an image be 10 pixels wide => 10 *3 bytes = 30 bytes, the closest number multiple of four is 32. So, followingly, each new stroke will have an indentation of 32 – 30 = 2 bytes before the next stroke starts.

  2. Let an image be 4 pixels wide => 4 * 3 = 12 bytes. The width is multiple of four, hence, each new stroke comes immediately after the previous without any indentation.

Image storage

I split the program into headed files and executed files. Let’s consider the image storage and its useful functions. Let’s write down image.h:

#ifndef UNTITLED_IMAGE_H
#define UNTITLED_IMAGE_H

#include <stdint.h>
#include <stdio.h>

struct pixel {
    uint8_t b;
    uint8_t g;
    uint8_t r;
};

struct image {
    uint64_t width;
    uint64_t height;
    struct pixel *data;
};

size_t findIndex(size_t x, size_t y, size_t width);
struct image emptyImage(const uint64_t width, const uint64_t height);
struct image createImage(const size_t image_width, const size_t image_height, struct pixel* pixels);
void imageDestroyer(struct image* image);


#endif //UNTITLED_IMAGE_H

Where:

struct image {
    uint64_t width;
    uint64_t height;
    struct pixel *data;
};

Is responsible for storage keeping image’s size and pixels.

  • findIndex() function allows to find an index in a new already rotated image where the former pixel with initial indices x and y is located.

  • emptyImage() function creates an empty image of a certain size.

  • createImage() function builds a full image of a certain size.

  • imageDestroyer() function frees the memory occupied by the image.

Let’s consider the implementations of all functions in image.c:

#include "image.h"
#include <stdint.h>
#include <stdlib.h>

size_t findIndex(const size_t x, const size_t y, const size_t width){
    return y * width + x;
}

struct image emptyImage(const uint64_t width, const uint64_t height){
    struct pixel* pixels = malloc(sizeof(struct pixel) * height * width);
    return createImage(width, height, pixels);
}

struct image createImage(const size_t width, const size_t height, struct pixel* pixels){
    return (struct image) {.data = pixels, .width = width, .height = height};
}

void imageDestroyer(struct image* image){
    free(image->data);
}

Opening and closing of images

This part looks at implemented functions to open an image and record, and after finishing the work, at the function for closing an image.

openFile.h:

#ifndef UNTITLED_OPENFILE_H
#define UNTITLED_OPENFILE_H

#include <stdbool.h>
#include <stdio.h>
enum openStatus{
    OPEN_ERROR,
    OPEN_OK
};

enum openStatus openFile(const char* fileName, FILE** file, const char* type);
#endif //UNTITLED_OPENFILE_H

openFile.c:

#include "openFile.h"

enum openStatus openFile(const char* fileName, FILE** file, const char* type){
    *file = fopen(fileName, type);
    if(*file == NULL){
        return OPEN_ERROR;
    }
    return OPEN_OK;
}

closeFile.h:

#ifndef UNTITLED_CLOSEFILE_H
#define UNTITLED_CLOSEFILE_H

#include <stdbool.h>
#include <stdio.h>
enum closeStatus{
    CLOSE_OK,
    CLOSE_ERROR
};

enum closeStatus closeFile(FILE* file);
#endif //UNTITLED_CLOSEFILE_H

closeFile.c:

#include "closeFile.h"

enum closeStatus closeFile(FILE* file){
    if(fclose(file) == EOF){
        return CLOSE_ERROR;
    }
    return CLOSE_OK;
}

Utilities for converting BMP to image and vice versa

To ease the file transformation, I have decided to create a separate section for necessary functions.

utilsForBmp.h:

#ifndef UNTITLED_UTILSFORBMP_H
#define UNTITLED_UTILSFORBMP_H

#include "../inputOutput/fromToBmp.h"
size_t size_of_padding(const size_t width);
bool head_read(FILE* file, struct bmp_header* header);
size_t size_of_image(const struct image* image);
size_t size_of_file(const struct image* image);
struct bmp_header create_header(const struct image* image);

#endif //UNTITLED_UTILSFORBMP_H
  • size_of_padding() function decides whether it is necessary to add padding to a file or not.

  • head_read() function allows to read only the file’s heading. This is done for splitting the header and the raster array easier.

  • size_of_file() function returns the size in bytes that a certain file will occupy (image size + header size).

  • create_header() allows to create a header for a particular image for further storage.

All implementations of functions in utilsForBmp.c:

#include "../inputOutput/fromToBmp.h"

size_t size_of_padding(const size_t width){
    return width % 4;
}

bool head_read(FILE* file, struct bmp_header* header){
    return fread(header, sizeof(struct bmp_header), 1, file);
}

size_t size_of_image(const struct image* image){
    return (image->width * sizeof(struct pixel) + size_of_padding(image->width)) * image->height;
}

size_t size_of_file(const struct image* image){
    return size_of_image(image) + sizeof(struct bmp_header);
}

struct bmp_header create_header(const struct image* image){
    return (struct bmp_header){
            .bfType = 19778,
            .bfileSize = size_of_file(image),
            .bfReserved = 0,
            .bOffBits = 54,
            .biSize = 40,
            .biWidth = image->width,
            .biHeight = image->height,
            .biPlanes = 1,
            .biBitCount = 24, //quantity of bits in bpm file
            .biCompression = 0,
            .biSizeImage = size_of_image(image),
            .biXPelsPerMeter = 0,
            .biYPelsPerMeter = 0,
            .biClrImportant = 0,
            .biClrUsed = 0
    };
}

BMP into image and back

After I have successfully opened the file for reading, I have to read it and transform into an image for an easier work with pixels. I have decided not to split this section into 4 files (as have been done with opening and closing) as I have figured it would complex the reading.

#ifndef UNTITLED_FROMTOBMP_H
#define UNTITLED_FROMTOBMP_H

#include "../image/image.h"
#include <stdbool.h>
#include <stdio.h>

struct __attribute__((packed)) bmp_header {
    uint16_t bfType;
    uint32_t bfileSize;
    uint32_t bfReserved;
    uint32_t bOffBits;
    uint32_t biSize;
    uint32_t biWidth;
    uint32_t biHeight;
    uint16_t biPlanes;
    uint16_t biBitCount;
    uint32_t biCompression;
    uint32_t biSizeImage;
    uint32_t biXPelsPerMeter;
    uint32_t biYPelsPerMeter;
    uint32_t biClrUsed;
    uint32_t biClrImportant;
};

enum read_status  {
    READ_OK = 0,
    READ_INVALID_SIGNATURE,
    READ_INVALID_BITS,
    READ_INVALID_HEADER,
    READ_INVALID_PADDING
};

enum write_status  {
    WRITE_OK = 0,
    WRITE_ERROR,
    WRITE_INVALID_BITS,
    WRITE_INVALID_SIGNATURE,
    WRITE_INVALID_HEADER,
    WRITE_INVALID_DATA,
    WRITE_INVALID_PADDING
};


enum read_status fromBmp(FILE* in, struct image* image);
enum write_status toBmp(FILE* out, const struct image* image);
#endif //UNTITLED_FROMTOBMP_H

bmp_header() structure returns an empty heading.

read_status and write_status enumerations return the results of fromBmp and toBmp execution respectively.

Implementation of functions in fromToBmp.c:

#include "fromToBmp.h"
#include "../util/utilsForBmp.h"

enum read_status fromBmp(FILE* in, struct image* image){
    struct bmp_header header = {0};

    if(!head_read(in, &header)){
        imageDestroyer(image);
        return READ_INVALID_HEADER;
    }
    if(header.bfType != 19778){
        imageDestroyer(image);
        return READ_INVALID_SIGNATURE;
    }

    *image = emptyImage(header.biWidth, header.biHeight);
    const size_t padding = size_of_padding(image->width);

    for(size_t i = 0; i < image->height; i++){
        for(size_t j = 0; j < image->width; j++){
            if(!fread(&(image->data[findIndex(j, i, image->width)]), sizeof(struct pixel), 1, in)){
                imageDestroyer(image);
                return READ_INVALID_BITS;
            }
        }
        if(padding != 0) {
            if (fseek(in, padding, SEEK_CUR)) {
                imageDestroyer(image);
                return READ_INVALID_PADDING;
            }
        }
    }

    return READ_OK;
}

enum write_status toBmp(FILE* out, const struct image* image){
    struct bmp_header header = create_header(image);

    if(!fwrite(&header, sizeof(struct bmp_header), 1, out)){
        return WRITE_INVALID_HEADER;
    }

    if(fseek(out, header.bOffBits, SEEK_SET)){
        return WRITE_INVALID_SIGNATURE;
    }

    const uint8_t paddings[3] = {0};
    const size_t padding = size_of_padding(image->width);

    if(image->data == NULL){
        return WRITE_INVALID_DATA;
    }

    for(size_t i = 0; i < image->height; i++){
        for(size_t j = 0; j < image->width; j++) {
            if (!fwrite(&image->data[findIndex(j, i, image->width)], sizeof(struct pixel), 1, out)) {
                return WRITE_INVALID_BITS;
            }
        }
        if(padding != 0){
            if(!fwrite(paddings, padding, 1, out)){
                return WRITE_INVALID_PADDING;
            }
        }
    }
    return WRITE_OK;
}

In fromBmp(), I, firstly, check whether I can read the file’s heading successfully. Afterwards, I verify whether file’s type (bfType) satisfies my case. Then, I create an empty image and get the padding size. After, I start to fill an image, skipping padding if it is present.

In toBmp(), I, firstly, try to write down the necessary heading of my image. Next, I get padding size of my image. Then, if our image isn’t empty, I start writing down all data into initial file, remembering to add padding after each new stroke (if needed).

Image rotation

This section overviews the image rotation by 90 degrees counterclockwise.

rotate.h:

#ifndef UNTITLED_ROTATE_H
#define UNTITLED_ROTATE_H
#include "../image/image.h"

struct image rotate(struct image source);

#endif //UNTITLED_ROTATE_H

Implementation is stored in rotate.c:

#include "../image/image.h"
#include <stdlib.h>

struct image rotate(const struct image source){
    if(source.data == NULL){
        return emptyImage(source.width, source.height);
    }

    struct pixel* pixels = malloc(sizeof(struct pixel) * source.width * source.height);

    for(size_t y = 0; y < source.height; y++){
        for(size_t x = 0; x < source.width; x++){
            pixels[findIndex(source.height - y - 1, x, source.height)] = source.data[findIndex(x, y, source.width)];
        }
    }

    return createImage(source.height, source.width, pixels);
}

To begin with, unless the image isn’t full, I return an empty image in which height and width dimensions have been changed.

Next, I allocate the memory necessary to store the pixels. Because the program is generally simple, it just rotates an image by 90 degrees. By this I get two two-dimensional arrays – one full, another empty. To accomplish the program, I just need to input pixels from a full array into an empty one correctly. Lastly, all work results is returned by createImage().

Main file:

#include <stdio.h>

#include "inputOutput/fromToBmp.h"
#include "openClose/closeFile.h"
#include "openClose/openFile.h"
#include "rotation/rotate.h"

int main(int cntArg, char* args[]){
    printf("%d", cntArg);

    if(cntArg != 3){
        perror("incorrectly passed arguments");
        return -1;
    }

    FILE *file;
    if (openFile(args[1], &file, "rb") != OPEN_OK) {
        perror("couldn't open the file");
        return -2;
    }

    struct image img = {0};

    if (fromBmp(file, &img) != READ_OK) {
        perror("couldn't convert from bmp");
        return -3;
    }

    struct image res = rotate(img);
    FILE *res_file;

    if (openFile(args[2], &res_file, "wb") != OPEN_OK) {
        perror("couldn't open the file");
        imageDestroyer(&res);
        return -2;
    }

    if (toBmp(res_file, &res) != WRITE_OK) {
        perror("couldn't convert to bmp");
        imageDestroyer(&res);
        closeFile(res_file);
        return -5;
    }


    if (closeFile(file) != CLOSE_OK) {
        perror("couldn't close the file");
        imageDestroyer(&img);
        return -4;
    }
    if (closeFile(res_file) != CLOSE_OK) {
        perror("couldn't close the file");
        imageDestroyer(&img);
        return -6;
    }
    imageDestroyer(&img);
    imageDestroyer(&res);

    return 0;
}

Firstly, I check if the number of program arguments is two (the path to the image to be rotated and the path to save the result). Then, I open file and transform it into image. Next, I rotate it, open file for recording, transform image into bmp and save it. Lastly, I free the allocated memory and close all the files.

It could have been done so the ways would be read through the standard entrance, however, you do whatever pleases you.

Example 1:

Example 2:

Tags:
Hubs:
Total votes 7: ↑5 and ↓2+3
Comments0

Articles