bmp.c 6.39 KB
//
// Created by ricar on 16.12.2022.
//

#include "bmp.h"
#include "file.h"


#include<string.h>


#define PLANES 1
#define HEADER_SIZE 40
#define TYPE (19778)
#define BIT_COUNT (24)

#pragma pack(push,1)

// bmp_header represents the header for a BMP file
struct bmp_header
{
uint16_t bfType; // type of BMP file (0x4d42 | 0x4349 | 0x5450)
uint32_t bfileSize; // size of the file
uint32_t bfReserved; // reserved field, should be 0
uint32_t bOffBits; // offset to data field
uint32_t biSize; // size of the structure in bytes (40 for BITMAPINFOHEADER, 108 for BITMAPV4HEADER, 124 for BITMAPV5HEADER)
uint32_t biWidth; // width in pixels
uint32_t biHeight; // height in pixels
uint16_t biPlanes; // should always be 1
uint16_t biBitCount; // number of bits per pixel (0, 1, 4, 8, 16, 24, 32)
uint32_t biCompression; // type of compression (BI_RGB, BI_RLE8, BI_RLE4, BI_BITFIELDS, BI_JPEG, BI_PNG) (only BI_RGB is commonly used)
uint32_t biSizeImage; // size of data field in bytes (usually set to 0)
uint32_t biXPelsPerMeter; // horizontal resolution, pixels per inch
uint32_t biYPelsPerMeter; // vertical resolution, pixels per inch
uint32_t biClrUsed; // number of colors used (if there is a color table)
uint32_t biClrImportant; // number of significant colors (can be considered 0)
};

#pragma pack(pop)


// find_padding calculates the number of padding bytes needed at the end of each row of pixels in the BMP file
static uint32_t find_padding(uint64_t a) {
// the number of bytes in each row of pixels must be a multiple of 4
  if (a % 4 == 0) {
    return 0;
  } else {
    return  (a % 4);
  }
}

// Function to check if the BMP header fields are valid
bool check_header_fields(struct bmp_header header) {
    return (header.bfileSize != header.biSizeImage + sizeof(struct bmp_header)) ||
           (header.bfReserved != 0) ||
           (header.biPlanes != 1) ||
           ((header.biSize != 40) && (header.biSize != 25) && (header.biSize != 124)) ||
           (header.bOffBits != 14 + header.biSize) ||
           (header.biWidth < 1) || (header.biWidth > 10000) ||
           (header.biHeight < 1) || (header.biHeight > 10000) ||
           (header.biBitCount != 24) ||
           (header.biCompression != 0);
}

// from_bmp reads a BMP file and stores its data in the given image struct
// it returns a read_status enum value indicating the success or failure of the operation
enum read_status read_bmp(FILE *in, struct image const *img) {
    // create a variable to store the BMP file header
    struct bmp_header header;

    // read the BMP file header from the input file
    size_t res = fread(&header, 1, sizeof(struct bmp_header), in);
    // if the header is not the expected size, return an error status
    if (res != sizeof(struct bmp_header)) {
        fprintf(stderr, "The header is invalid");
        return READ_INVALID_HEADER;
    }

    // check the signature of the BMP file to ensure it is a valid BMP file
    if (header.bfType != 0x4d42 && header.bfType != 0x4349 && header.bfType != 0x5450) {
        fprintf(stdout, "The value of bfType is: %d\n", header.bfType);
        fprintf(stderr, "The type is invalid");
        return READ_INVALID_SIGNATURE;
    }

    // check the header fields to ensure they are valid
    bool invalidHeader = check_header_fields(header);

    // if any of the header fields are invalid, return an error status
    if (invalidHeader) {
        fprintf(stderr, "The header is invalid");
        return READ_INVALID_HEADER;
    }

    // the BMP file header has been read and checked, and the image is a BGR-24 format with known width and height
    struct image *img_nonconst = (struct image *)img;
    configure_image(img_nonconst, header.biWidth, header.biHeight);

    // if the image data could not be allocated, return an error status
    if (((*img).data) == NULL) { return READ_INVALID_BITS; }
    // read the pixel data from the input file and store it in the image struct
    size_t i = 0;
    while (i < ((*img).height)) {
        // read a row of pixels from the input file
        res = fread(&(((*img).data)[i * ((*img).width)]), sizeof(struct pixel), (*img).width, in);
        // if the number of pixels read is not equal to the image width, return an error status
        if (res != ((*img).width)) return READ_ERROR;
        // skip the padding bytes at the end of the row
        fseek(in, find_padding((*img).width), SEEK_CUR);
        ++i;
    }

    // close the input file and return a status indicating success or failure
    return READ_OK;
}


struct bmp_header create_new_header(struct image const* img){
    struct bmp_header bmpHeader;
    bmpHeader.bfType = TYPE;
    //Find the length of the line in the fine
    unsigned long width_full = (3* ((*img).width) +3) &(-4);
    bmpHeader.biSizeImage = ((*img).height) * width_full;
    bmpHeader.bfileSize = sizeof (struct bmp_header) + bmpHeader.biSizeImage;
    bmpHeader.bfReserved = 0;
    bmpHeader.biPlanes = PLANES;
    bmpHeader.biSize = HEADER_SIZE;
    bmpHeader.bOffBits = sizeof (struct bmp_header);
    bmpHeader.biWidth = (*img).width;
    bmpHeader.biHeight = (*img).height;
    bmpHeader.biBitCount = BIT_COUNT;
    bmpHeader.biCompression = 0;
    bmpHeader.biYPelsPerMeter = 0;
    bmpHeader.biXPelsPerMeter = 0;
    bmpHeader.biClrImportant = 0;
    bmpHeader.biClrUsed = 0;
    return bmpHeader;
}

// to_bmp writes the data from the given image struct to a BMP file
// it returns a write_status enum value indicating the success or failure of the operation
enum write_status to_bmp(const char* output_name, struct image const* img ){
    //create a file pointer and open the output file
FILE *out = NULL;
if(check_file_opened(&out,output_name,WRITE) == false) return WRITE_ERROR;

//create a BMP file header struct and initialize it with the image data
struct bmp_header bmpHeader = create_new_header(img);
//write the BMP file header to the output file
fwrite(&bmpHeader, sizeof(bmpHeader), 1, out);
//write the pixel data to the output file
size_t i = 0;
while (i < ((*img).height)) {
    //write a row of pixels to the output file
    fwrite(&(((*img).data)[i * ((*img).width)]), sizeof(struct pixel), (*img).width, out);
    //write the padding bytes at the end of the row
    unsigned char padding[3] = {0, 0, 0};
    fwrite(padding, sizeof(unsigned char), find_padding((*img).width), out);
    ++i;
}

    // close the output file and return a status indicating success or failure
    if(check_file_closed(&out) == false){return WRITE_ERROR;}
    return WRITE_OK;
}