Bmp image rotation and BMP to YUV (4:2:0)

Posted by hyngvesson on Fri, 21 Jan 2022 05:57:36 +0100

1, Briefly introduce ideas:

Brief description of BMP file: BMP file is composed of file header, information header, color information and graphic data. (since the 24 bit BMP here does not consider the color information), only the file header, information header and graphic data are left in the BMP file.

When reading BMP, the reading order is: 1 Read a file header 2 Read a header 3 Read graphic data

Predefined data types: BYTE (8 bits, i.e. one BYTE), WORD (16 bits, i.e. two bytes), DWORD (32 bits, i.e. four bytes)

typedef unsigned char  BYTE;//One byte
typedef unsigned short WORD;//Two bytes
typedef unsigned long DWORD;//Four bytes

The header data structure of BMP file is as follows:

//File header of BMP picture (14 bytes in total)
typedef struct HEAD
{
    WORD bfType; /*  file type*/
    DWORD bfSize; /*  file size*/
    WORD bfReserved1; /*  Reserved bit*/
    WORD bfReserved2; /*  Reserved bit*/
    DWORD bfOffBits; /*  Data offset position*/
}HEAD;

The header data structure of BMP file is as follows:

//BMP picture header (40 bytes)
typedef struct INFOHEAD
{
    DWORD biSize; /*  Size of this structure */
    DWORD biWidth; /*  Image width */
    DWORD biHeight; /*  Image height */
    WORD biPlanes; /*  Number of palettes */
    WORD biBitCount; /*  The number of bits corresponding to each pixel,*/
    DWORD biCompression; /*  compress */
    DWORD biSizeImage; /*  Image size */
    DWORD biXPelsPerMeter;/*  Lateral resolution */
    DWORD biYPelsPerMeter;/*  Longitudinal resolution */
    DWORD biClrUsed; /*  Number of colors used */
    DWORD biClrImportant; /*  Number of important colors */
}INFOHEAD;

Idea:

1. First read the original BMP image data: read a file header, read an information header, and read the graphics pixel data (note that in the graphics pixel data of BMP file, the data in each line is an integer multiple of 4 bytes, and zero bytes shall be supplemented if it is not an integer multiple of 4 bytes. Therefore, pay attention to this part of zero bytes when reading and writing graphics pixel data).

2. When converting BMP to YUV file, you only need to use YUV and BMP conversion formulas:

Y= 0.299*R+0.587*G+0.114*B

U= -0.168*R-0.332*G+0.5*G+128

V= 0.5*R-0.419*G-0.081*B+128

Because it is to be converted to YUV (4:2:0), after converting the B, G and R values of all BMP pixels to the values of Y, U and V, the values of U and V shall be sampled 4:1 (the average value is adopted in this paper), and Y does not need to be changed.

Precautions: (1) the storage order of BMP graphic pixel information is from left to right and from bottom to top (mainly note here). Therefore, when converting BMP to YUV, the top-down symmetry of BMP graphic pixel information shall be carried out first.

(2) BMP each pixel is stored in the order of B, G and R (not r, G and b).

3. When turning 180 degrees, the values of file header and information header are not changed, as long as the graphic pixel data is changed.

4. When turning 90 degrees, some values of file header and information header are changed. Pay attention to recalculate these values, and then change the graphic pixel data, i.e. 4 Yes.

II. Complete code: (BMP rotation and BMP to YUV420 can be operated separately)

1.header.h:

#pragma once
#define _CRT_SECURE_NO_WARNINGS


typedef unsigned char  BYTE;//One byte
typedef unsigned short WORD;//Two bytes
typedef unsigned long DWORD;//Four bytes

//File header of BMP picture (14 bytes in total)
typedef struct HEAD
{
    //WORD bfType; /*   File type*/
    DWORD bfSize; /*  file size*/
    WORD bfReserved1; /*  Reserved bit*/
    WORD bfReserved2; /*  Reserved bit*/
    DWORD bfOffBits; /*  Data offset position*/
}HEAD;    // The bfType here needs to be taken separately

//BMP picture header (40 bytes)
typedef struct INFOHEAD
{
    DWORD biSize; /*  Size of this structure */
    DWORD biWidth; /*  Image width */
    DWORD biHeight; /*  Image height */
    WORD biPlanes; /*  Number of palettes */
    WORD biBitCount; /*  The number of bits corresponding to each pixel,*/
    DWORD biCompression; /*  compress */
    DWORD biSizeImage; /*  Image size */
    DWORD biXPelsPerMeter;/*  Lateral resolution */
    DWORD biYPelsPerMeter;/*  Longitudinal resolution */
    DWORD biClrUsed; /*  Number of colors used */
    DWORD biClrImportant; /*  Number of important colors */
}INFOHEAD;


void turn180(WORD bfType, BYTE* image_array, HEAD* filehead, INFOHEAD* infohead, int off);
void turn90(WORD bfType, BYTE* image_array, HEAD* filehead, INFOHEAD* infohead);
void rgb2yuv(int width, int heigth, unsigned char* rgbbuf, unsigned char* ybuf, unsigned char* ubuf, unsigned char* vbuf);

2.Image_rollover.cpp:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include"header.h"


FILE* FP, * FP_yuv;
HEAD* filehead = (HEAD*)malloc(sizeof(HEAD));
INFOHEAD* infohead = (INFOHEAD*)malloc(sizeof(INFOHEAD));
WORD bfType;  //  file type
BYTE* store;  // Store a byte of information
int i, j, k;
BYTE* image_array, * ybuf, * ubuf, * vbuf, * rgbbuf;
BYTE zero_fill = 0;
FILE* FP1;
int off, width, height;

void shan(WORD bfType, BYTE* image_array, HEAD* filehead, INFOHEAD* infohead);


int main()
{
    if ((FP = fopen("1.bmp", "rb")) == NULL)
    {
        printf("cann't find the file!");
        return 0;
    }

    fread(&bfType, sizeof(WORD), 1, FP); //Read bfType separately
    fread(filehead, sizeof(HEAD), 1, FP); //Read in file header
    fread(infohead, sizeof(INFOHEAD), 1, FP);  //Read in header
    /*
    printf("File header size:% d\n", sizeof(HEAD));
    printf("Header size:% d\n", sizeof(INFOHEAD));
    */
    int a = 0;
    if (bfType != 0X4d42)
    {
        printf("no BMP picture");  //When bfType is 0X4d42, it indicates the BMP picture read
        return 0;
    }
  
    width = infohead->biWidth;
    height = infohead->biHeight;
    image_array = (BYTE*)malloc(sizeof(BYTE) * infohead->biHeight * infohead->biWidth * 3);//Store pixel data of pictures
    store = (BYTE*)malloc(sizeof(BYTE));
    ybuf = (BYTE*)malloc(sizeof(BYTE) * infohead->biHeight * infohead->biWidth);
    ubuf = (BYTE*)malloc(sizeof(BYTE) * infohead->biHeight * infohead->biWidth / 4);
    vbuf = (BYTE*)malloc(sizeof(BYTE) * infohead->biHeight * infohead->biWidth / 4);
    rgbbuf= (BYTE*)malloc(sizeof(BYTE) * infohead->biHeight * infohead->biWidth * 3);
    
    off = (infohead->biWidth * 3) % 4;
    if (off != 0)
    {
        off - 4 - off;       //The pixel information of each line of BMP picture shall be supplemented by zero into an integer multiple of 4 bytes, and off is the complement
//Number of zero bytes
    }
    /*Because each line of the image is supplemented into an integer multiple of 4 bytes and zero bytes are supplemented during image storage, it is read byte by byte*/
    for (i = 0;i < infohead->biHeight;i++)    //Store graphics pixel data to image_array
    {
        for (j = 0;j < infohead->biWidth;j++)
        {
            fread(store, sizeof(BYTE), 1, FP);
            image_array[i * infohead->biWidth * 3 + j  * 3] = *store; 
            fread(store, sizeof(BYTE), 1, FP);
            image_array[i * infohead->biWidth * 3 + j  * 3 + 1] = *store;
            fread(store, sizeof(BYTE), 1, FP);
            image_array[i * infohead->biWidth * 3 + j  * 3 + 2] = *store;
        }
        for (k = 0;k < off;k++)
        {
            fread(store, sizeof(BYTE), 1, FP);   //Take the zero byte supplemented at the end of each line, but do not save
        }
    }
    fclose(FP);

    /*Recovered image from read data*/
    /*
    FP1 = fopen("2.bmp", "wb");
    fwrite(&bfType, sizeof(WORD), 1, FP1);
    fwrite(filehead, sizeof(HEAD), 1, FP1);
    fwrite(infohead, sizeof(INFOHEAD), 1, FP1);
    for (i = 0;i < infohead->biHeight;i++)    //The original data is stored in the array
    {
        for (j = 0;j < infohead->biWidth;j++)
        {
            fwrite(&image_array[i * infohead->biWidth * 3 + j  * 3], sizeof(BYTE), 1, FP1);
            fwrite(&image_array[i * infohead->biWidth * 3 + j  * 3+1 ], sizeof(BYTE), 1, FP1);
            fwrite(&image_array[i * infohead->biWidth * 3 + j  * 3+2], sizeof(BYTE), 1, FP1);
        }
        for (k = 0;k < off;k++)
        {
            fwrite(&zero_fill, sizeof(BYTE), 1, FP1);
        }
    }
    */
    /*
    Upside down
    for (i = 0;i < infohead->biHeight;i++)    //The original data is stored in the array
    {
        for (j = 0;j < infohead->biWidth;j++)
        {
            fwrite(&image_array[(infohead->biHeight-i-1) * infohead->biWidth * 3 + j * 3], sizeof(BYTE), 1, FP1);
            fwrite(&image_array[(infohead->biHeight - i - 1) * infohead->biWidth * 3 + j * 3 + 1], sizeof(BYTE), 1, FP1);
            fwrite(&image_array[(infohead->biHeight - i - 1) * infohead->biWidth * 3 + j * 3 + 2], sizeof(BYTE), 1, FP1);
        }
        for (k = 0;k < off;k++)
        {
            fwrite(&zero_fill, sizeof(BYTE), 1, FP1);
        }
    }
    fclose(FP1);
    */
    /*rgbbuf This is the normal sequence after flipping bmp up and down, because bmp stores data from left to right and from bottom to top*/
    for (i = 0;i < height;i++)
        for (j = 0;j < width;j++)
        {
            rgbbuf[j * 3 + (height - 1 - i) * width * 3] = image_array[j * 3 + width * 3 * i];
            rgbbuf[j * 3 + (height - 1 - i) * width * 3+1] = image_array[j * 3 + width * 3 * i+1];
            rgbbuf[j * 3 + (height - 1 - i) * width * 3+2] = image_array[j * 3 + width * 3 * i+2];
        }

    /*bmp The file is converted to a yuv file through a formula*/
    rgb2yuv(width, height, rgbbuf, ybuf, ubuf, vbuf);  //Call conversion function
    FP_yuv = fopen("3.yuv", "wb");
    fwrite(ybuf, 1, sizeof(unsigned char) * width * height, FP_yuv);
    fwrite(ubuf, 1, sizeof(unsigned char) * width * height/4, FP_yuv);
    fwrite(vbuf, 1, sizeof(unsigned char)* width* height/4, FP_yuv);
    printf("%d*%d of bmp File to yuv File complete\n", height, width);
    fclose(FP_yuv);

    /*Select to flip bmp*/
    printf("Please select the right BMP Image operation: input 1 to rotate 180 degrees, and input 2 bits to rotate 90 degrees:\n");
    int select;
    scanf("%d", &select);
    if (select == 1)
    {
        turn180(bfType, image_array, filehead, infohead,off);
    }
    else if (select == 2)
    {
        turn90(bfType, image_array, filehead, infohead);
    }
    else
    {
        printf("Wrong selection");
    }

}

3. rgb2yuv.cpp:

#include "stdlib.h"
#include"header.h"

void rgb2yuv(int width, int heigth, unsigned char* rgbbuf, unsigned char* ybuf, unsigned char* ubuf, unsigned char* vbuf)
{
	int i, j;
	unsigned char* sub_ubuf, * sub_vbuf;//Amount of uv not discarded
	sub_ubuf = (unsigned char*)malloc(sizeof(unsigned char) * width * heigth);  
	sub_vbuf= (unsigned char*)malloc(sizeof(unsigned char) * width * heigth);

	/*Calculate the values of Y, U and V of each pixel through the conversion formula of YUV and RGB. Note that each pixel in RGB is stored in the order of B, G and R (not r, G and b)*/
	for(i=0;i<heigth;i++)
		for (j = 0;j < width;j++)
		{
			ybuf[i * width + j] = 0.299 * rgbbuf[i * width * 3 + j * 3+2] + 0.587 * rgbbuf[i * width * 3 + j * 3 + 1] + 0.114 * rgbbuf[i * width * 3 + j * 3 ];
			if (ybuf[i * width + j] > 235)
				ybuf[i * width + j] = 235;
			else if (ybuf[i * width + j] < 16)
				ybuf[i * width + j] = 16;
			sub_ubuf[i * width + j] = -0.168 * rgbbuf[i * width * 3 + j * 3+2] - 0.332 * rgbbuf[i * width * 3 + j * 3 + 1] + 0.5 * rgbbuf[i * width * 3 + j * 3 ]+128;
			sub_vbuf[i * width + j] = 0.5 * rgbbuf[i * width * 3 + j * 3+2] - 0.419 * rgbbuf[i * width * 3 + j * 3 + 1] - 0.081*rgbbuf[i * width * 3 + j * 3 ]+128;
		}
	/*sub_ubuf sub_vbuf reduced to 1 / 4 (4:2:0)*/
	for(i=0;i<heigth/2;i++)
		for (j = 0;j < width/2;j++)
		{
			ubuf[i * width / 2 + j] = (sub_ubuf[i * 2 * width + j * 2] + sub_ubuf[i * 2 * width + j * 2 + 1] + sub_ubuf[i * 2 * width + j * 2 + width] + sub_ubuf[i * 2 * width + j * 2 + width + 1]) / 4;
			if (ubuf[i * width / 2 + j] > 235)
				ubuf[i * width / 2 + j] = 235;
			else if (ubuf[i * width / 2 + j] < 16)
				ubuf[i * width / 2 + j] = 16;
			vbuf[i * width / 2 + j]= (sub_vbuf[i * 2 * width + j * 2] + sub_vbuf[i * 2 * width + j * 2 + 1] + sub_vbuf[i * 2 * width + j * 2 + width] + sub_vbuf[i * 2 * width + j * 2 + width + 1]) / 4;
			if (vbuf[i * width / 2 + j] > 235)
				vbuf[i * width / 2 + j] = 235;
			else if (vbuf[i * width / 2 + j] < 16)
				vbuf[i * width / 2 + j] = 16;
		}
}

4.turn90.cpp:

#include"header.h"
#include<stdlib.h>
#include<stdio.h>


void turn90(WORD bfType, BYTE* image_array, HEAD* filehead, INFOHEAD* infohead)
{


    FILE* FP = fopen("1.bmp", "wb");
    int i, j, k;
    int off_new;
    BYTE zero_fill = 0;
    DWORD width, heigth;
    width = infohead->biWidth;
    heigth = infohead->biHeight;   //Width and height of original drawing
    off_new = infohead->biHeight * 3 % 4;
    if (off_new != 0)
    {
        off_new = 4 - off_new;   //The number of bytes that need to be zeroed in each line
    }

    //When flipping 90 degrees, some values of BMP file header and information header are changed, which should be recalculated
    filehead->bfSize = sizeof(WORD) + sizeof(HEAD) + sizeof(INFOHEAD) + infohead->biHeight * infohead->biWidth * 3 + off_new * infohead->biWidth;
    DWORD swsw;
    /*The width and height of the original image should be interchanged*/
    swsw = infohead->biHeight;
    infohead->biHeight = infohead->biWidth;
    infohead->biWidth = swsw;
    infohead->biSizeImage = filehead->bfSize - (sizeof(WORD) + sizeof(HEAD) + sizeof(INFOHEAD));

    fwrite(&bfType, sizeof(WORD), 1, FP);    
    fwrite(filehead, sizeof(HEAD), 1, FP);
    fwrite(infohead, sizeof(INFOHEAD), 1, FP);
    /*Write flipped BMP pixel information*/
    for (i = 0;i < width;i++)
    {
        for (j = heigth - 1;j >= 0;j--)
        {
            fwrite(&image_array[j * width * 3 + i * 3], sizeof(BYTE), 1, FP);
            fwrite(&image_array[j * width * 3 + i * 3 + 1], sizeof(BYTE), 1, FP);
            fwrite(&image_array[j * width * 3 + i * 3 + 2], sizeof(BYTE), 1, FP);
        }
        for (k = 0;k < off_new;k++)
        {
            fwrite(&zero_fill, sizeof(BYTE), 1, FP);  //Zero bytes to be filled in each line in order to become an integral multiple of 4 bytes
        }
    }

    fclose(FP);

}

5.turn180cpp:

#include"header.h"
#include<stdlib.h>
#include<stdio.h>

void turn180(WORD bfType, BYTE* image_array, HEAD* filehead, INFOHEAD* infohead, int off)
{

    FILE* FP = fopen("1.bmp", "wb");
    BYTE zero_fill = 0;
    int i, j, k;
    /*Because the image is flipped 180 degrees, the length and width of the image do not change, and the values of the file header and information header do not change, the file header and information header of the original image are written directly*/
    fwrite(&bfType, sizeof(WORD), 1, FP);
    fwrite(filehead, sizeof(HEAD), 1, FP);
    fwrite(infohead, sizeof(INFOHEAD), 1, FP);
    for (i = infohead->biHeight - 1;i >= 0;i--)
    {
        for (j = infohead->biWidth - 1;j >= 0;j--)
        {
            fwrite(&image_array[i * infohead->biWidth * 3 + j * 3], sizeof(BYTE), 1, FP);
            fwrite(&image_array[i * infohead->biWidth * 3 + j * 3 + 1], sizeof(BYTE), 1, FP);
            fwrite(&image_array[i * infohead->biWidth * 3 + j * 3 + 2], sizeof(BYTE), 1, FP);
        }
        if (off != 0)
        {
            for (k = 0;k < off;k++)
            {
                fwrite(&zero_fill, sizeof(BYTE), 1, FP);  //Each line is filled with zero bytes in order to become an integer multiple of 4 bytes
            }
        }
    }
    fclose(FP);
}

Topics: C C++ BMP yuv