BMP 이미지의 상하를 바꾸는 방법?

서지훈의 이미지

유틸을 이용한 방법 말고, BMP low-data에서 상하를 반전 하는 방법을 알고 싶습니다.
이미지 파일을 읽어들이긴 했는데...
뿌려보니 위아래가 바꿔 있네요.-_-ㅋ

혹시 상하 반전 하는 방법 아시는 분 방법 알켜 주세요^^

<어떠한 역경에도 굴하지 않는 '하양 지훈'>

futari의 이미지

그냥 line 단위로 뒤집으면 되지 않을까요 ㅡㅡ; 어차피 low-data인데 ㅎㅎ

옛날에 pcx-_-; 같은걸로 게임 만들때 상하,좌우,대각선, 뭐 이런거 대칭 함수 만들었던 기억이 -_-;;

-------------------------
The universe is run by the complex interweaving of three elements: matter, energy, and enlightened self-interest.
- G'kar, Babylon 5

casey25의 이미지

원래 비트맵은 bottom-up 이 디폴트 입니다.
1라인이 bottom 이고
마지막라인이 top 이죠.

bugiii의 이미지

뿌릴 때 거꾸로 뿌리시면... 안될까요?

제 기억이 맞다면 bmp 파일이 거꾸로 되는 이유가 그 옛날 프로세스들의 비교 연산 비용이 0 을 비교하는 것이 더 싸서 그랬다고 알고 있습니다.

thyoo의 이미지

여러 헤더가 있습니다.

그중에 BITMAPINFOHEADER 멤버 biHeight의 부호를 반대로 하세요.

자세한 것은
http://msdn.microsoft.com/

___________________________________
Less is More (Robert Browning)

정태영의 이미지

thyoo wrote:
여러 헤더가 있습니다.

그중에 BITMAPINFOHEADER 멤버 biHeight의 부호를 반대로 하세요.

자세한 것은
http://msdn.microsoft.com/

그걸 지원하는 이미지 뷰어가 그리 많지는 않을겁니다..
(예전에 해본바에 따르면 =3=33)

오랫동안 꿈을 그리는 사람은 그 꿈을 닮아간다...

http://mytears.org ~(~_~)~
나 한줄기 바람처럼..

차리서의 이미지

futari wrote:
그냥 line 단위로 뒤집으면 되지 않을까요 ㅡㅡ; 어차피 low-data인데 ㅎㅎ

옛날에 pcx-_-; 같은걸로 게임 만들때 상하,좌우,대각선, 뭐 이런거 대칭 함수 만들었던 기억이 -_-;;


어디서 줏어들은 제 알량한 지식으로는, BMP는 거의 raw data에 가깝긴하지만 순수한 raw data는 아니기 때문에 그냥 읽어들여서 곧바로 뒤집으시면 안된다고 알고 있습니다. BMP는 raw data에 BMP 헤더를 붙여준 형태라서, 일단 어디까지가 헤더인지를 찾아내서 그 부분 외의 나머지를 뒤집으셔야 할 것 같습니다.

(그런데, 바로 이 'BMP의 헤더가 어디까지인지 알아내기'가 생각보다 간단치 않다더군요. :?)

--
자본주의, 자유민주주의 사회에서는 결국 자유마저 돈으로 사야하나보다.
사줄테니 제발 팔기나 해다오. 아직 내가 "사겠다"고 말하는 동안에 말이다!

dragonkun의 이미지

제가 아는 바로는..헤더에 어디부터가 실제 이미지인지 offset이 나와 있습니다..
DDB의 경우는 전혀 아는 바 없지만..
DIB의 경우 헤더에..
처음 2바이트는 매직 넘버,
다음 4바이트는 BMP 파일의 크기,
다음 4바이트는 빈 공간.,
그 다음 4바이트에 실제 영상정보가 있는 오프셋 입니다..

BMP파일의 끝에서부터 위에 있는 오프셋까지..
뒤에서 부터 읽어서 출력하시면 됩니다..

예전에 BMP 출력하는 루틴을 만든 경험이 있어서;;
하드에 코드가 남아있길래...오래만에 훑어봤네요..^^

Emerging the World!

galien의 이미지

뒤집히는 게 endian이랑 관계 있는 걸까요?

futari의 이미지

차리서 wrote:
futari wrote:
그냥 line 단위로 뒤집으면 되지 않을까요 ㅡㅡ; 어차피 low-data인데 ㅎㅎ

옛날에 pcx-_-; 같은걸로 게임 만들때 상하,좌우,대각선, 뭐 이런거 대칭 함수 만들었던 기억이 -_-;;


어디서 줏어들은 제 알량한 지식으로는, BMP는 거의 raw data에 가깝긴하지만 순수한 raw data는 아니기 때문에 그냥 읽어들여서 곧바로 뒤집으시면 안된다고 알고 있습니다. BMP는 raw data에 BMP 헤더를 붙여준 형태라서, 일단 어디까지가 헤더인지를 찾아내서 그 부분 외의 나머지를 뒤집으셔야 할 것 같습니다.

(그런데, 바로 이 'BMP의 헤더가 어디까지인지 알아내기'가 생각보다 간단치 않다더군요. :?)

많이 쓰이는 포맷인데 헤더가 어디까지인지 알기 힘들게 만들지야 않았겠지요 ^^;

물론 depth에 따라서 point bit 수도 다를테고 resolution에 따라서 point 수도 다를테지만, 헤더에 그정도 정보야 다 있겠지요 ^^;

그냥 자동으로 헤더 계산해서 사용하는 대칭 출력 메쏘드를 만들어서 출력할때 뒤집어 출력... 하면 되지 않을까용.

자세한 파일 포맷들은 역시나 www.wotsit.org 같은곳을 ㅎㅎ

-------------------------
The universe is run by the complex interweaving of three elements: matter, energy, and enlightened self-interest.
- G'kar, Babylon 5

simpid의 이미지

BMP 파일은 BITMAPFILEHEADER와 BITMAPINFOHEADER로 구성됩니다.

typedef struct tagBITMAPFILEHEADER { 
  WORD    bfType; 
  DWORD   bfSize; 
  WORD    bfReserved1; 
  WORD    bfReserved2; 
  DWORD   bfOffBits; 
} BITMAPFILEHEADER, *PBITMAPFILEHEADER; 

typedef struct tagBITMAPINFOHEADER{
  DWORD  biSize; 
  LONG   biWidth; 
  LONG   biHeight; 
  WORD   biPlanes; 
  WORD   biBitCount; 
  DWORD  biCompression; 
  DWORD  biSizeImage; 
  LONG   biXPelsPerMeter; 
  LONG   biYPelsPerMeter; 
  DWORD  biClrUsed; 
  DWORD  biClrImportant; 
} BITMAPINFOHEADER, *PBITMAPINFOHEADER; 

1) BMP파일을 읽어서 가장 앞에 BITMAPFILEHEADER가 있고 바로 뒤에 BITMAPINFOHEADER가 이어집니다.

2) BITMAPFILEHEADER의 bfOffBits 부분이 이미지가 있는 위치입니다.

3) 이미지를 처리하실때 scanline 단위로 하면 되고 BMP 파일의 스캔라인은 4의 배수 크기입니다.

4) BITMAPINFOHEADER의 biWidth, biBitCount 를 사용해서 스캔라인을 계산하면 되지만 계산된 값을 4의 배수로 조정해 줘야 합니다.

5) 제가 사용하는 공식입니다.
(((pBMIH->biBitCount) * (pBMIH->biWidth) + 31) >> 5) << 2;

6) 각각의 스캔라인의 위치는 BfOffBits와 스캔라인의 크기로 알 수 있으므로 BMP파일의 가장 마지막 스캔라인부터 읽어 내면 Bottom-up을 top-down으로 바꿀 수 있습니다.

wafe의 이미지

simpid wrote:

5) 제가 사용하는 공식입니다.
(((pBMIH->biBitCount) * (pBMIH->biWidth) + 31) >> 5) << 2;

이 공식이 어떻게 되는 건지 설명을 부탁드려도 될까요? 이해할 수가 없어서..

Heejoon Lee

simpid의 이미지

wafe wrote:
simpid wrote:

5) 제가 사용하는 공식입니다.
(((pBMIH->biBitCount) * (pBMIH->biWidth) + 31) >> 5) << 2;

이 공식이 어떻게 되는 건지 설명을 부탁드려도 될까요? 이해할 수가 없어서..

이미지의 최소 단위는 픽셀입니다.
픽셀을 표현하는데 몇 비트를 사용하는냐에 따라 표현할 수 있는 색이 결정됩니다.
스캔라인은 CRT 모니터의 특성에서 만들어진 말로 픽셀이 가로로 이어져 만들어진 한줄을 의미합니다.

예를들어 645*480 이미지에 8비트 칼라를 사용한다면
640 * 8 / 8 = 645Bytes... 물론 4의 배수는 아니죠.
BMP에선 644Bytes가 되야 합니다.(4의 배수로 만들어 남는 공간에는 0이 들어갑니다.)
(((pBMIH->biBitCount) * (pBMIH->biWidth) + 31) >> 5) << 2;
(((8 * 641) + 31) / 32) * 2 = 644가 나옵니다.

꽤 오래전에 만들어 놓은거라...(몇년 됐습니다.)
지금 다시 보니까.. 좀 황당하군요.

(((pBMIH->biBitCount) * (pBMIH->biWidth) + 31) >> 3
이 더 간단한것 같군요.
8비트 칼라 : ((8 * 641) + 31) / 8 = 644
24비트 칼라 : ((24 * 641) + 31) / 8 = 1926
1비트 칼라 : ((1 * 641) + 31) / 8 = 84

이녀석이 더 간단한것 같습니다.
((pBMIH->biBitCount * pBMIH->biWidth) + 31) >> 3

barmi의 이미지

wafe wrote:
simpid wrote:

5) 제가 사용하는 공식입니다.
(((pBMIH->biBitCount) * (pBMIH->biWidth) + 31) >> 5) << 2;

이 공식이 어떻게 되는 건지 설명을 부탁드려도 될까요? 이해할 수가 없어서..

MSDN의 샘플에 나와 있는 코드는 다음과 같습니다.
((pbmi->bmiHeader.biWidth * cClrBits +31) & ~31) /8

코드의 효용성은 알아서 판단하시구요, 이해는 아랫 코드가 더 쉬울 겁니다.

일반적인 경우라면 colorbit가 하나의 픽셀이 요구하는 bit라고 이해하시면 되구요, 그러니 사이즈를 byte로 표현하려면, colorbit * width / 8입니다. 하지만 여기서 simpid님께서 말씀하신 4byte alignment가 적용됩니다.
따라서, 1bit라도 커지면 +4byte를 하는 효과때문에 (4*8-1)을 더해주는 겁니다.

예를 들어 2bit color에서 width의 크기가 28이라면, 위의 공식에 따라, scanline의 크기는 7byte가 아니라, 8byte가 된다는 겁니다.

barmi의 이미지

simpid wrote:

이녀석이 더 간단한것 같습니다.
((pBMIH->biBitCount * pBMIH->biWidth) + 31) >> 3

이 공식은 반드시 4의 배수를 만들지 못합니다.
(((pBMIH->biBitCount * pBMIH->biWidth) + 31) >> 3) & ~3
이렇게 하면 몰라도요....

vajna의 이미지

wafe wrote:
simpid wrote:

5) 제가 사용하는 공식입니다.
(((pBMIH->biBitCount) * (pBMIH->biWidth) + 31) >> 5) << 2;

이 공식이 어떻게 되는 건지 설명을 부탁드려도 될까요? 이해할 수가 없어서..

BMP는 한라인이 저장될때 DWORD단위로 패딩되게 되어 있습니다.
위의 식은 라인 총 비트수를 32bit(4byte==DWORD)으로 나누어(나머지는 무조건 1로 올립니다.) 필요한 DWORD값을 계산하여 4를 곱하면 파일에서 한 라인이 차지하는 바이트 수가 나옵니다. 그러면 이 수에 원하는 라인의 수를 곱하여 로 데이터 시작위치를 더하면 원하는 라인의 첫 픽셀의 파일오프셋이 계산됩니다.

저는 이렇게 하기도 합니다.

realCount = (((width*bitsPerPixel)+7)>>3)
physicalWidth = (realCount+3)&(~3);
scanPadding = physicalWidth-(realCount);

머나먼 땅으로 우리 동무가 이제 떠나간다네.
고향의 바람이 불어와 동무 뒤를 따르고
사랑스러운 도시가 푸른 이내 속에서 사라지네.
정든 집, 푸른 동산, 부드러운 눈길도...

simpid의 이미지

barmi wrote:
simpid wrote:

이녀석이 더 간단한것 같습니다.
((pBMIH->biBitCount * pBMIH->biWidth) + 31) >> 3

이 공식은 반드시 4의 배수를 만들지 못합니다.
(((pBMIH->biBitCount * pBMIH->biWidth) + 31) >> 3) & ~3
이렇게 하면 몰라도요....

그렇군요.

그래서 전에 제가 처음에 개발했던 코드가
(((pBMIH->biBitCount) * (pBMIH->biWidth) + 31) >> 5) << 2;
였나 봅니다.

몇년만에 봤던 코드라... 왜 이럴까 싶어 간단하게 고쳐본건데...
2번에 걸쳐 쉬프트 했던 이유가 있었군요.
제가 만들고 기억못했군요.. ^^;

shjoung의 이미지

bmp화일의 경우
헤더가 있고
사용하는 색의 사이즈에 따라서
파레트가 있는 경우가 있고 없는 경우가 있고
그리고 파레트가 있는 경우는 그 파레트를 지시하고 있는 인덱스데이타
파레트가 없는 경우는 rgb값이 저장되어 있습니다.

여기서 파레트란 bmp데이타의 양을 줄이기 위해 색값을 미리 지정해 둔 것이지요
예를들어 일곱가지 무지개 색을 사용해서 100*100크기의 그림을 그리는 경우를 생각해 보도록 하죠.
우선 색을 표현하는데는 RGB 각각 1바이트씩 3바이트와 추가로1바이트(4바이트 맞추기 위해? 오래되서 기억이 가물가물..)로 4바이트가 필요합니다.

그럼 파레트를 사용하지 않는 경우는
---------------------------------------------
헤더의 바이트수 + 픽셀수(100*100)*4
---------------------------------------------

이번에는 파레트에를 사용할 경우를 봅시다.
파레트에는
빨 주 노 초 파 남 보의 순의로 필요한 색을 파레트에 저장해 두고..
데이터에는 이 필요한 색의 순서를 지정하는 방식이므로
7개의 순서를 표현하려면 3비트면 충분하죠?
계산의 편의를 위해 걍 인덱스 데이타를 1바이트식 잡으면
------------------------------------------------
헤더의 바이트수 + 색의수(7)*4 + 픽셀수(100*100)*1
------------------------------------------------

훨씬 줄어 들었죠?

그럼 그림을 뒤집으려면 어떻게 하면 될까요?
당연히 데이타 부분만 뒤집어 주면 됩니다.
bmp데이타는
실제 보이는 그림에서의
가장 윗줄의 픽셀들이 데이타의 아래쪽에 저장되어 있고
가장 아랫줄의 픽셀들이 데이타의 위쪽에 저장되어 있습니다.

1a 1b 1c
2a 2b 2c
3a 3b 3c
4a 4b 4c
5a 5b 5c

라고 보이는 그림의 실제 데이타는

5a 5b 5c
4a 4b 4c
3a 3b 3c
2a 2b 2c
1a 1b 1c

로 저장되어 있습니다.
이때 1a와 1c는 바뀌여 있지 않습니다.
단지 줄만 바뀌여 있을 따름입니다.

데이타의 줄 위치만 바꾸어 주면 화면은 뒤집힙니다.
단 한 줄을 나타내는 데이타 수를 계산해 주셔야 합니다.
100*100의 그림이라면
한줄을 나타내는 데이타 수는
경우에 따라서 매우 다양히 틀려 지겠지만
100*4바이트가 되겠죠.

서지훈의 이미지

/*******************************************************************************
 *
 * Description  : Change BMP Image Bottom-Up
 * Argument     : *data - BMP Image Data
 *                biSizeImage - the Size, in bytes, of the Image.
 *                biWidth -  the Width of the Bitmap.
 *                biHeight - the Height of the Bitmap.
 * Return       : If successful, returns changed bmp data. Otherwise, returns 
 *                NULL.
 * Date         : 2004/08/26/Thr.
 *
 ******************************************************************************/
unsigned char *bottom_up(unsigned char *data,
        const unsigned long biSizeImage,
        const long biWidth, const long biHeight)
{
    register int    i, j;
    unsigned char   *buff;
    unsigned char   temp[biWidth/2];


    if (!(buff = (unsigned char *) malloc(biSizeImage))) {
        debug_msg("%s %s %d: malloc(\"%d\"): %s",
            __FILE__, __FUNCTION__, __LINE__, biSizeImage, strerror(errno));

        return NULL;
    }

    for (i = 0; i < biHeight; i++) {
        memcpy(buff+((biWidth/2) * i),
                data+(biSizeImage - (biWidth/2)*(i+1)), (biWidth/2));
        memcpy(temp, buff+((biWidth/2) * i), biWidth/2);
        left_right(temp, biWidth);
        memcpy(buff+((biWidth/2) * i), temp, biWidth/2);
    }

    memcpy(data, buff, biSizeImage);

    Free(buff);


    return data;
}


/*******************************************************************************
 *
 * Description  : Change BMP Image Left-Right in Half module
 * Argument     : *data - BMP Image Data(Line)
 *                biWidth -  the Width of the Bitmap.
 * Return       : If successful, returns changed bmp data. Otherwise, returns 
 *                NULL.
 * Date         : 2004/08/26/Thr.
 *
 ******************************************************************************/
unsigned char *left_right(unsigned char *data, const long biWidth)
{   
    register int    i;
    int             temp = biWidth / 8; // Number of Half Module
    unsigned char   buff[4];


    for (i = 0; i < temp; i++) {
        memcpy((unsigned char *)buff, (unsigned char *)(data+(i*4)), 4);

        if (!reverse_ustr((unsigned char *)buff, 4)) {
            debug_msg("%s %s %d: reverse_ustr(data): error.",
                    __FILE__, __FUNCTION__, __LINE__);

            return NULL;
        }

        memcpy((unsigned char *)(data+(i*4)), (unsigned char *)buff, 4);
    }


    return data;
}

걍...
읽어 들인 값들을 위와 같이 뒤집고 좌우를 바꿔서 해결을 했습니다.
이미지 파일은 96x16x4, BMP 파일입니다.

혹시라도 다음에 하시는 분 참고라도 하시라고...-_-ㅋ

그리고 많은 답변글들 감사드립니다...^^

<어떠한 역경에도 굴하지 않는 '하양 지훈'>

#include <com.h> <C2H5OH.h> <woman.h>
do { if (com) hacking(); if (money) drinking(); if (women) loving(); } while (1);

atie의 이미지

http://www.javaworld.com/javaworld/javatips/jw-javatip43.html

밑에 인용된 것 외에도 헤더 정보를 어떻게 읽는지 보실 수 있습니다.

// Some bitmaps do not have the sizeimage field calculated
// Ferret out these cases and fix 'em.
if (nsizeimage == 0)
    {
    nsizeimage = ((((nwidth*nbitcount)+31) & ~31 ) >> 3);
    nsizeimage *= nheight;
    System.out.println("nsizeimage (backup) is"+nsizeimage);
    }

----
I paint objects as I think them, not as I see them.
atie's minipage

shins의 이미지

저같은 경우 biHeight의 부호를 -로 바꾸어주고 해결했습니다.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.