반응형

안녕하세요.

 

오랜만에 C언어에 대해 글을 씁니다. 

 

오늘은 제목과 같이 #pragma pack(push,1)을 사용하지 않고 구조체만으로 정렬하는 방법에 대해 이어쓰겠습니다.

 

혹시 이해가 안가신다면 전에 글을 읽어주시기 바랍니다.

 

https://coding-yoon.tistory.com/27

 

[C]BMP구조체 : #pragma pack(push, 1) 사용하지 않고 정렬하기

안녕하세요. 오랜만에 C언어에 대해 글을 써봅니다. 여러분들 BMP를 불러올 때 어떻게 하시나요? https://dojang.io/mod/page/view.php?id=703 C 언어 코딩 도장: 81.2 비트맵 구조체 작성하기 비트맵 파일의 구조..

coding-yoon.tistory.com

Lenna.bmp
0.75MB

#include   // strcpy 함수가 선언된 헤더 파일
#include 
#include 
#include 
#pragma warning(disable:4996)
#define _CRT_SECURE_NO_WARNINGS  // strcpy 보안 경고로 인한 컴파일 에러 방지


//pragma pack(push,1)
struct BMPINFO_14  //BMP 파일 구조체
{
//unsigned short bfType;           // BMP 파일 매직 넘버
unsigned int   bfSize;           // 파일 크기
unsigned short bfReserved1;      // 예약
unsigned short bfReserved2;      // 예약
unsigned int   bfOffBits;        // 비트맵 데이터의 시작 위치
};

struct BMP_FILE_44 // BMP 비트맵 정보 구조체
{
unsigned int   biSize;           // 현재 구조체의 크기
int            biWidth;          // 비트맵 이미지의 가로 크기
int            biHeight;         // 비트맵 이미지의 세로 크기
unsigned short biPlanes;         // 사용하는 색상판의 수
unsigned short biBitCount;       // 픽셀 하나를 표현하는 비트 수
unsigned int   biCompression;    // 압축 방식
unsigned int   biSizeImage;      // 비트맵 이미지의 픽셀 데이터 크기
int            biXPelsPerMeter;  // 그림의 가로 해상도(미터당 픽셀)
int            biYPelsPerMeter;  // 그림의 세로 해상도(미터당 픽셀)
unsigned int   biClrUsed;        // 색상 테이블에서 실제 사용되는 색상 수
unsigned int   biClrImportant;   // 비트맵을 표현하기 위해 필요한 색상 인덱스 수
};

struct RGB_3 //24비트 비트맵 구조체
{
unsigned char rgbtBlue;          // 파랑
unsigned char rgbtGreen;         // 초록
unsigned char rgbtRed;           // 빨강
};

//#pragma pack(pop)

int main()
{
int i, j, k, ii;

char image[80] = "Lenna.bmp";
char image_copy[80]="Lenna1.bmp";
printf("원본 이미지 : Lenna.bmp\n");
printf("편집 이미지 : Lenna1.bmp\n");

FILE* fp;

//binary read모드
fp = fopen(image, "rb");
if (fp == NULL)
return 1;
/////////////////////////////////BM/////////////////////////////////////
fseek(fp, 0, SEEK_SET);
int buffer = fgetc(fp);
fseek(fp, 1, SEEK_SET);
int buffer1 = fgetc(fp);

if ((buffer != 0x42 || buffer1 != 0x4D) )
{
printf("%x%x", buffer, buffer1);
fclose(fp);
return 3;
}
fseek(fp, 2, SEEK_SET);
////////////////////////////////BM/////////////////////////////////////
//binary write모드
FILE* outfp = fopen(image_copy, "wb");
if (outfp == NULL)
{
fclose(fp);
fprintf(stderr, "생성 불가능 %s.\n", image_copy);
return 2;
}

BMP_FILE_44 bitmapInfoHeader;
BMPINFO_14 bitmapFileHeader;

//
fread(&bitmapFileHeader, sizeof(BMPINFO_14), 1, fp);
fread(&bitmapInfoHeader, sizeof(BMP_FILE_44), 1, fp);
fwrite("BM", 1, 2, outfp);
fwrite(&bitmapFileHeader, sizeof(BMPINFO_14), 1, outfp);
fwrite(&bitmapInfoHeader, sizeof(BMP_FILE_44), 1, outfp);



int biHeight = abs(bitmapInfoHeader.biHeight);
int biWidth = bitmapInfoHeader.biWidth;

int padding = (4 - (biWidth * sizeof(RGB_3)) % 4) % 4; //패딩 처리

//**************************2차동적할당**********************************//
RGB_3** RGB;
RGB = (RGB_3**)malloc(sizeof(RGB_3*) * biHeight);
RGB[0] = (RGB_3*)malloc(sizeof(RGB_3) * biHeight * biWidth);
for (ii = 1; ii < biHeight; ii++)
{
RGB[ii] = RGB[ii - 1] + biWidth;
}
//**************************2차동적할당**********************************//

for (i = 0; i < biHeight; i++)
{
for (j = 0; j < biWidth; j++)
{
fread(&RGB[i][j], sizeof(RGB_3), 1, fp);
}
}

for (i = 0; i < biHeight; i++)
{
for (j = 0; j < biWidth; j++)
{
// 이미지 밝기
int x, y, z;
x = RGB[i][j].rgbtBlue + 75;
y = RGB[i][j].rgbtGreen + 75;
z = RGB[i][j].rgbtRed + 75;
RGB[i][j].rgbtBlue = ((x > 255) ? 255 : x);
RGB[i][j].rgbtGreen = ((y > 255) ? 255 : y);
RGB[i][j].rgbtRed = ((z > 255) ? 255 : z);
}
}

for (i = 0; i < biHeight; i++)
{
for (j = 0; j < biWidth; j++)
{

fwrite(&RGB[i][j], sizeof(RGB_3), 1, outfp);
}
fseek(fp, padding, SEEK_CUR);
for (k = 0; k < padding; k++)
{
fputc(0x00, outfp);
}
}


printf("Complete");



fclose(fp);

fclose(outfp);
free(RGB[0]);
free(RGB);
return 0;

}


이를 실행시켜보면

 

아주 잘읽혀집니다.

 

구조체가 어떻게 정렬하는지는 앞 글에 있고, 조심해야할 점에 대해 적도록 하겠습니다. 

 

Lenna.bmp를 1바이트 정렬없이 읽었으며, 우리가 출력할 Lenna1.bmp를 자세히 보아야 합니다.

 

Lenna1.bmp를 출력하기 위해 Binary Write로 열고 구조체를 사용해야 합니다.

 

하지만 구조체에는 다음과 같이 

 

'BM' 부분이 비어 있게 되어 bmp로 작성할 수 없게 됩니다. 

 

그러면 어떻게 해야 될까요? 

 

아주 간단합니다. 우리가 다시 수동으로 적어주면 됩니다. 이렇게

bitmap 파일 헤더를 읽고,

bitmap 정보 헤더를 읽고,

 

당연히 우리는 bitmap파일헤더에 BM이 존재하지 않기 때문에

 

bmp의 가장 첫 2바이트는 BM이라는거 기억나시죠?

 

fwrite를 통해 BM을 적어주면 됩니다. 

 

그리고 이어서 bitmap파일 헤더, bitmap정보 헤더에 대해 fwrite를 해줍니다. 

 

어떻게 보면 당연한 이야기일 수 있겠지만, 저는 파일 입출력 구조에 대해 이해를 못했을 땐, 왜 이렇게 사용하는지 조차

 

이해가 가지 않았습니다. 파이썬을 하면 이런거 할 일은 없지만, 파일 구조에 대해 공부할 땐 bmp만한게 없는 것 같습니다.

 

 

728x90
반응형
반응형

안녕하세요. 오랜만에 C언어에 대해 글을 써봅니다.

 

여러분들 BMP를 불러올 때 어떻게 하시나요? 

 

https://dojang.io/mod/page/view.php?id=703

 

C 언어 코딩 도장: 81.2 비트맵 구조체 작성하기

비트맵 파일의 구조를 알아보았으니 이제 비트맵 구조체를 작성합니다. 이때 주의할 점은 반드시 구조체를 1바이트 크기로 정렬해야 한다는 점입니다. 즉, 비트맵 파일에서 각 정보는 위치와 크기가 정확하게 정해져 있으므로 반드시 구조체의 크기와 형태 그대로 읽어야 합니다. #pragma pack(push, 1) // 구조체를 1바이트 크기로 정렬 typedef struct _BITMAPFILEHEADER // BMP 비트맵 파일 헤더 구조체 { unsigned

dojang.io

아마 위 본 예제가 가장 많이들 사용하실겁니다. 

 



#pragma pack(push, 1)                // 구조체를 1바이트 크기로 정렬

typedef struct _BITMAPFILEHEADER     // BMP 비트맵 파일 헤더 구조체
{
    unsigned short bfType;           // BMP 파일 매직 넘버
    unsigned int   bfSize;           // 파일 크기
    unsigned short bfReserved1;      // 예약
    unsigned short bfReserved2;      // 예약
    unsigned int   bfOffBits;        // 비트맵 데이터의 시작 위치
} BITMAPFILEHEADER;

typedef struct _BITMAPINFOHEADER     // BMP 비트맵 정보 헤더 구조체(DIB 헤더)
{
    unsigned int   biSize;           // 현재 구조체의 크기
    int            biWidth;          // 비트맵 이미지의 가로 크기
    int            biHeight;         // 비트맵 이미지의 세로 크기
    unsigned short biPlanes;         // 사용하는 색상판의 수
    unsigned short biBitCount;       // 픽셀 하나를 표현하는 비트 수
    unsigned int   biCompression;    // 압축 방식
    unsigned int   biSizeImage;      // 비트맵 이미지의 픽셀 데이터 크기
    int            biXPelsPerMeter;  // 그림의 가로 해상도(미터당 픽셀)
    int            biYPelsPerMeter;  // 그림의 세로 해상도(미터당 픽셀)
    unsigned int   biClrUsed;        // 색상 테이블에서 실제 사용되는 색상 수
    unsigned int   biClrImportant;   // 비트맵을 표현하기 위해 필요한 색상 인덱스 수
} BITMAPINFOHEADER;

typedef struct _RGBTRIPLE            // 24비트 비트맵 이미지의 픽셀 구조체
{
    unsigned char rgbtBlue;          // 파랑
    unsigned char rgbtGreen;         // 초록
    unsigned char rgbtRed;           // 빨강
} RGBTRIPLE;

#pragma pack(pop)

#pragma pack(push, 1)

 

구조체 데이터를 강제로 1바이트 단위로 쪼개서 정렬한다는 뜻입니다. 

가장 최소로 쪼개면 빈공간이 없이 정렬이 되기 때문에 메모리가 100퍼센터 사용할 수 있게 됩니다.

 

하지만 1바이트 씩 정렬하면 너무 낭비가 되지 않을까요? 

cpu가 한 번에 32bit, 64bit를 한 번에 수행하는 반면에 1바이트씩이면은... 분명 낭비가 됩니다. 그래서 많은 블로그에 

글쓰신 분들께서도  추천하지는 않습니다. 

 

오늘은 pack#pragma pack(push, 1)를 쓰지 않고 정렬하여 사용하는 방법을 알아보도록 하겠습니다.

 

 

1. BMP 바이너리 모드

BMP에 대해 알아보겠습니다. bmp를 binary mode로 열어봅시다.

 

1. visual studio

visual studio를 열어줍니다.

2. 파일 열기
3. binary mode
BMP Binary mode

파일을 열고나서 BMP인지 아닌지를 판별하는 데이터가 시작주소에서 2바이트가 존재합니다.

0x424D(16진수) => BM을 의미합니다. (bfType)

BM으로 BMP파일인지 아닌지 확인하게 됩니다. (BM이면 BMP파일) 

 

 

 

 

 

그럼 BMP파일인지 아닌지 확인해보았고.

BMP파일 구조체에 대해 알아보도록 하겠습니다.

 

1. pragma pack(push, 1)

 

2. 정렬 없음.

첫번 째 사진인 pragma pack(push,1)가 있고,

 

두번 째 사진은 pragma pack(push,1)이 없습니다.

 

첫번 째 경우는 당연히 가장 최소 단위로 쪼개기 때문에 바로 사용가능합니다.

 

하지만 두번 째 경우는 어떻게 될까요?

 

두번 째 사진을 디버깅한 결과

포인터 오류가 뜨네요. 널포인터... 아주 무서운 녀석입니다.

 

왜 이런 결과가 나올까요? 그 이유는 포인터정렬이 제대로 되지 않아 NULL 포인터가 생겼기 때문입니다.

 

구조체는 #pragma가 정의가 되어 있지 않으면 가장 큰 변수의 사이즈를 기준으로 정렬하게 됩니다.

 

2. 정렬 없음.

다시 한 번 볼까요? 가장 큰 바이트가 4바이트입니다.  그렇다면 4바이트 기준으로 정렬을 하겠네요. 

그림보시면 단박에 이해가 가실겁니다.

그림 죄송합니다 ㅜㅜ

4바이트 씩 맞춰 정렬하게 되면 이와 같이 NULL 포인터가 2바이트 발생하게 되어 저 위와 같은 오류가 뜨게 됩니다.

 

어떻게 하면 NULL 포인터를 없애면서 4바이트씩 정렬할 수 있을까요?

 

bfType을 지워버리면 됩니다.

 

요렇게요. 그렇다면 4바이트씩 정렬이 되겠죠?

이렇게요.

어? 그렇다면 bfType을 없앴는데 어떻게 bmp파일인지 아닌지 어떻게 구별하나?

 

그래서 위에 BMP파일을 바이너리 모드로 연 것입니다.

 

기억하시나요? 

 

맨 첫 주소에서 2바이트가 BM인지 아닌지 판별하는 bfType입니다. 

 

bfType이 맨 첫주소에 있기 때문에 파일 입출력 문제로 바뀌게 되는것입니다. 

 

fread로 읽어도 되지만... 파일입출력 공부겸 연습을 위해 굳이... 이렇게 써봤습니다. 

여러분들은 저 방법 말고 fread를 사용해서 읽으시길 바랍니다.~

 

아까와 똑같은 bmp파일으 열고 

 

bmp파일을 fseek함수를 이용해서 맨 첫주소로 이동하여, 0x42, 0x4D를 확인합니다. 

 

if문은 0x424D(==BM) 이면 첫 두 바이트를 출력하게 됩니다

잘 나오네요?

이 방법을 사용하면 1바이트씩이 아닌 4바이트씩 사용하여 BMP를 입출력하는 것이 가능하게 됩니다.

 

다음 C언어 글은 저 위 방법을 사용하면서, BMP를 사용해서 size를 키우는 실습(upscaling)을 해보도록 하겠습니다. 

728x90
반응형

+ Recent posts