티스토리 뷰

42/cub3d

cub3d - mlx_get_data_addr

stdbc 2020. 9. 23. 09:23

mlx_get_data_addr 함수를 살펴보는 페이지.

 

함수를 이해하는 데 필요했던 rgb, endian, bpp, 포인터 형변환 개념을 먼저 정리했습니다.

mlx_get_data_addr에서 정리된 내용은 벽과 천장, rgb는 투명도 페이지에서 사용됩니다.

 

1. rgb

2. endian

3. bpp

4. 포인터 형변환

5. mlx_get_data_addr

 

 


1. rgb

 

Red, Green, Blue (빨강, 초록, 파랑) 세 종류의 기본 색을 이용하여 색을 표현한다.

각각의 요소는 0 ~ 255 의 값을 가질 수 있다. 이 값은 해당 색이 얼마나 섞였는지를 나타낸다.

 

ex) 완전 빨강: R = 255, G = 0, B = 0

      완전 초록: R = 0, G = 255, B = 0

      완전 파랑: R = 0, G = 0, B = 255

      완전 검정: R = 0, G = 0, B = 0

      완전 하양: R = 255, G = 255, B = 255

 

이런 방식으로 256 * 256 *256, 총 16,777,216 개의 색을 표현할 수 있다.

 

 

16진법

색상을 표현할 때는 RGB를 16진법으로 변환해서 사용한다.

(0 ~ 255)를 (00 ~ FF)로 변환해 0xRRGGBB 형식으로 나타낸다.

 

ex) 완전 빨강: 0xFF0000

     완전 초록: 0x00FF00

     완전 파랑: 0x0000FF

     완전 검정: 0x000000

     완전 하양: 0xFFFFFF

 

 

10진법

특정 색상에서 R, G, B 값을 구해보자.

ex) 완전 빨강 (0xFF0000): 255 * (256²) + 0 * (256) + 0 * (1)

 

여기서 R, G, B 값을 각각 구해보면

 

B : 255 * (256²) + 0 * (256) + 0 * (1) 를 256으로 나눈 나머지 = 0

 

G : 255 * (256²) + 0 * (256) + 0 * (1) 를 256으로 나눈 몫을 다시 256으로 나눈 나머지 

      = 255 * (256) + 0 * (1) 를 256으로 나눈 나머지 = 0

 

R : 255 * (256²) + 0 * (256) + 0 * (1)를 256²으로 나눈 몫을 다시 256으로 나눈 나머지

      = 255 * (1) 를 256으로 나눈 나머지 = 255

 

 

2진법, 비트연산

이번엔 16진법 0xFF00000에서 R, G, B 값을 구해보자. 우리는 두 자리씩 딱 끊어서  R = FF, G = 00, B = 00 로 바로 파악할 수 있지만 컴퓨터는 못하니까..

이 계산을 위해 비트 연산이 필요하고 비트 연산을 이해하기 위해 색을 2진법으로 표현해본다.

 

16진법 각 자리의 수를 2진법으로 변환한다.

0xFF0000 : 1111 1111 / 0000 0000 / 0000 0000

 

 

B 값을 구할 땐 앞의 16자리는 필요 없고 마지막 8자리만 필요하기 때문에 0xFF(255)와 & 연산을 해준다

(10진법에서 256으로 나눠 나머지를 구하는 과정과 유사하다)

 

0xFF0000 : 1111 1111  0000 0000  0000 0000

&

0x0000FF :  0000 0000  0000 0000  1111 1111

결과:            0000 0000  0000 0000  0000 0000

 

 

G:  (10진법) 256으로 나눈 몫에서 256으로 나눈 나머지

       (2진법) 비트를 오른쪽으로 8칸 보내고 & 0xFF 

 

0xFF0000 >> 8 = 0x00FF00

0x00FF00 : 0000 0000  1111 1111  0000 0000

&

0x0000FF : 0000 0000  0000 0000  1111 1111

결과:           0000 0000  0000 0000  0000 0000

 

 

R : (10진법) 256²으로 나눈 몫에서 256으로 나눈 나머지

      (2진법) 비트를 오른쪽으로 16칸 보내고 & 0xFF

 

0xFF0000 >> 16 = 0x0000FF

0x00FF00 : 0000 0000  0000 0000  1111 1111

&

0x0000FF : 0000 0000  0000 0000  1111 1111

결과:           0000 0000  0000 0000  1111 1111

 

 

비트연산 정리

RGB color에서  

R = (color >> 16) & 0xFF

G = (color >> 8 ) & 0xFF

B = color & 0xFF

 

같은 원리로 R, G, B 값이 주어졌을 때 color를 구하는 방법

color = R * 256² + G * 256 + B

color = (R << 16)  | ( G << 8) | B

 

 

ARGB

RGB에 투명도(Alpha)값을 추가해 ARGB 형식(0xAARRGGBB)으로 나타내기도 한다.

A 또한 0(완전 불투명) ~ FF(완전 투명)의 값을 갖는다.

 

예를 들어 0xFF0000은 0x00FF0000와 동일하기 때문에 완전 불투명한 빨강

0xCCFF0000: 50% 투명한 빨강

0xFFFF0000: 완전 투명한 빨강

 

ARGB가 가질 수 있는 값은 0x00000000 ~ 0xFFFFFFFF 로 (unsigned int) 범위와 동일하다.

 

 


2. endian

 

unsigned int color = 0x12345678; 를 선언하면 메모리에 아래처럼 저장된다.

 

 

우리가 눈으로 보는 것과 반대로 저장되는 이유는 리틀엔디언이라는 방식으로 값을 저장하기 때문이다.

 

엔디언은 숫자를 1바이트씩 쪼개서 저장할 때 저장순서를 나타낸다.

빅엔디언: 큰 자릿수가ㅇ 앞에 저장됨

리틀엔디언: 작은 자릿수가 앞에 저장됨

 

 

출처 코딩도장

 

리틀엔디언은 숫자를 읽는 방식이랑 반대라서 생소하게 보이지만, 우리가 보통 사용하는 x86(x86-64) 계열 CPU는 다 리틀 엔디언 방식이라고 한다.

 

리틀엔디언에서 color = 0x12345678 는 78 / 56 / 34 / 12 로 저장이 되고

순서대로 color = 0x78 + 0x5600 + 0x340000 + 0x12000000 = 0x12345678 로 계산이 된다.

 

우리는 0xF00FFF1111 0000 / 0000 1111 / 1111 1111 로 표현하지만

리틀엔디언에서는 실제로 1111 1111 / 0000 1111 / 1111 0000 으로 저장한다.

 

그래도 비트연산, 쉬프트연산 같은 계산은 엔디언의 영향을 받지 않기 때문에 그냥 사용하면 된다.

 

본 페이지에서는

0xRRGGBB 는 B / G / R

0xAARRGGBB는 B / G / R / A 순서로 저장되는 것을 알면 충분하다.

 

cf) 엔디언이 계산에 영향을 미치는 예시:  developer.ibm.com/articles/au-endianc/#list4

 

 


3. bpp

 

[mlx man]

bits_per_pixel will be filled with the number of bits needed to represent a pixel color 
(also called the depth of the image).

bpp = 픽셀 하나(색상 하나)를 표현하는 데 필요한 비트 수 (= depth of the image)

 

따라서

RGB: 색상을 표현할때 BB / GG / RR 3바이트가 필요하기 때문에 bpp = 24비트

ARGB : BB / RR / GG / AA 4바이트가 필요하기 때문에 bpp = 32비트

 

24비트 비트맵의 픽셀 저장. 출처 코딩도장

 

 


4. 포인터 형변환

 

색상을 char *에 쭉 저장해놓고 주소값으로 접근해보자.

 

char *data = 할당;

unsigned int color_red = 0x00ff0000;
*(unsigned int *)data = color_red;

 

color_red를 0번에 넣을 때 형변환 없이 *data = 0x00ff0000; 로 대입하면 1바이트 자리에 4바이트 값을 넣고 있기 때문에 오류가 난다.

따라서 data가 가리키는 곳의 타입을 unsigned int로 바꾸고 값을 넣어주어야 한다.

 

 

결과는 아래처럼 저장된다.

 

두 번째 색을 저장하려면 1번이 아니라 4번 주소에 저장한다. 첫 번째 색이 4바이트를 사용했기 때문에.

unsigned int color_2 = 0x12345678;
*(unsigned int *)(data + 4) = color_2;

 

 

값을 가져올 때도 형 변환에 따라 결과가 달라진다. (data + 4)를 살펴보자.

*(data + 4) : 0x78
*(short *)(data + 4) : 0x5678
*(int *)(data + 4) : 0x12345678
*(unsigned int *)(data + 4) : 0x12345678

 

색상과 색상 사이에 있는 중간 주소에 접근할 수도 있다.

다만 (data + 2)에서 unsigned int로 색상을 뽑으면 ff / 00 / 78 / 56 → 0x567800ff 가 뽑혀 저장했던 색상이 아닐 뿐.

 

 

정리:

char * 포인터에 연속으로 색상을 저장할 때는 4칸 간격으로 저장하고

저장한 값을 뽑아올 때도 4칸 간격으로 가져온다.

(각각 unsigned int 캐스팅)

 

 


MiniLibX

 

This library is a simple framework to help 42 students create simple graphical apps.

 

mlx에서는 색상을 ARGB, 0xAARRGGBB 형식으로 사용한다.

반투명한 색상을 넣고 띄워보면 투명도가 지원되는 것을 확인할 수 있다.

 

 

mlx함수로 뒷 배경이 투명한 X 그림 위에 투명도를 다르게 해서 0xAA00FF00(초록색)을 짝수 줄마다 입혀본 짤

투명도 참고 davidwalsh.name/hex-opacity

 

 

투명도 50%일 때 코드는 다음과 같다.

int main(void)
{
    void *mlx, *win, *img;
    unsigned int *data;
    int img_width, img_height;
    int bpp, size_l, endian;

    mlx = mlx_init();
    win = mlx_new_window(mlx, 640, 480, "mlx_title");
    img = mlx_xpm_file_to_image(mlx, "./textures/mark.xpm", &img_width, &img_height);
    data = (unsigned int *)mlx_get_data_addr(img, &bpp, &size_l, &endian);
    
    for(int i = 0; i < img_height; i++)
        for (int j = 0; j < img_width; j++)
            if (i % 2 == 0)
                data[size_l / 4 * i + j] = 0xCC00FF00;

    mlx_put_image_to_window(mlx, win, img, 0, 0);
    mlx_loop(mlx);
}

 

여기서 [size_l / 4 * i + j] 이 무엇인지 알아보자. 해당 식은 cub3d에서 텍스쳐에 접근할 때 계속 사용된다.

 


5. mlx_get_data_addr

 

 

 

이 4pixel * 2pixel xpm 파일은 mlx에서 아래처럼 저장된다.

 

 

 

 

가로로 4칸씩 저장되는 것은 앞의 포인터 형변환 파트에서 본 것과 동일하다.

그 과정은 라이브러리에서 확인할 수 있다. 

 

[라이브러리 - mlx_xpm.c]

void    *mlx_int_parse_xpm(void *xvar, void *info, int info_size, char *(*f)(), int *width, int *height)
{
    ...
    
    opp = 4;
    
    ...
    
    mlx_int_xpm_set_pixel(data, opp, col, x);
}

void    mlx_int_xpm_set_pixel(char *data, int opp, int col, int x)
{
    *((unsigned int *)(data + 4 * x)) = col; ← 4 대신 opp를 써야 했는데 하드코딩된 듯
}

 

 

세로를 살펴보자.

 

원본 이미지를 생각하면 15번 주소에 이어서 16번 주소에 흰색이 들어갈 것 같지만

16부터 255는 건너 뛰고 256부터 두 번째 라인이 저장된다. 이때 첫 번째 줄과 두 번째 줄의 간격이 바로 size_line이다. 

 

[mlx man]

size_line is the number of bytes used to store one line of the image in memory. 
This information is needed to move from one line to another in the image.


size_line : 이미지 한 줄을 저장하는 데 쓰는 바이트. 줄을 넘어갈 때 이 값이 필요하다.

 

 

'줄을 넘어간다'는 부분도 라이브러리에서 찾을 수 있다.

[라이브러리 - mlx_xpm.c]

void    *mlx_int_parse_xpm(void *xvar,void *info,int info_size,char *(*f)(), int *width, int *height)
{
    ...
    
    opp = 4;
    
    ...
    
    i = *height;
    while (i--)
    {
        x = 0;
        while (x < *width)
        {
            mlx_int_xpm_set_pixel(data, opp, col, x);
            x++;
        }
        data += size_line; //img->width*4;
    }
     ...
}

안쪽 while문에서 width로 한 줄을 끝낸 후에 size_line 간격만큼 주소값을 건너 뛰고 있다.

 

 

그리고 이때 size_line의 값은 아래 함수에서 정해져서 오는 값이다.

[라이브러리 - mlx_img.swift]

public class MlxImg
{
    ...
    texture_sizeline = width * 4
    texture_sizeline = 256 * (texture_sizeline / 256 + (texture_sizeline % 256 >= 1 ? 1 : 0))
    ...
}

0 ~ 64 : size_line = 256 * 1 = 256

65 ~ 128 : size_line = 256 * 2 = 512

129 ~ 192 : size_line = 256 * 3 = 768

193 ~ 256 : size_line = 256 * 4 = 1024

 

정리하자면 size_line은 이미지 한 줄이 저장된 후에 추가되는 간격, 이 값은 width 64마다 달라진다.

 


이를 바탕으로 데이터에서 [색상이 저장된 주소]를 계산하는 방법은 다음과 같다.

 

 

 

이미지는 첫 줄에서 다음 줄로 넘어갈 때 width를 더하겠지만, 데이터 접근은 size_line을 더해야 한다.

따라서 흰색의 주소는

[width * 1 + 0] (x)
[size_line * 1 + 0] (o)

 

그리고 가로로 접근할 때도 1이 아니라 4씩 더해야 한다.

노란색의 주소는

[size_line * 0 + 2] (x)
[size_line * 0 + 4 * 2] (o)

 

이 부분도 man에 나와있다.

From this adress, the first bits_per_pixel bits represent the color of the first pixel in the first line of the image.
The second group of bits_per_pixel bits represent the second pixel of the first line, and so on.
Add size_line to the adress to get the begining of the second line. You can reach any pixels of the image that way.


처음 32비트(4바이트)는 이미지 첫 줄의 첫 번째 픽셀을 나타낸다.
그 다음 32비트는 이미지 첫 줄의 두 번째 픽셀을 나타낸다.
처음 주소 값에 size_line을 더하면 이미지 둘째 줄로 갈 수 있다.

 

 

각 색상 데이터에 접근하는 방법은 아래처럼 정리된다.

int main(void)
{
    char    *data;

    data = mlx_get_data_addr(img, &bpp, &size_l, &endian);
    
    for(int i = 0; i < img_height; i++)
        for (int j = 0; j < img_width; j++)
            *(unsigned int *)(data + (size_l * i + 4 * j))
}

 


여기까지 데이터에 접근하는 계산식은 찾았지만

바로 4라는 값을 어떻게 알지? 항상 라이브러리를 뜯어봐야 하나? 라는 의문이 생겼다.

 

 

사실 man에서는 4바이트라는 정확한 수치 대신 bits_per_pixel bits 단어를 사용한다.

From this adress, the first bits_per_pixel bits represent the color of the first pixel in the first line of the image.
The second group of bits_per_pixel bits represent the second pixel of the first line, and so on.
Add size_line to the adress to get the begining of the second line. You can reach any pixels of the image that way.

------------------------
아래는 bits_per_pixel bits를 곧바로 32비트로 해석한 것.
------------------------

처음 32비트(4바이트)는 이미지 첫 줄의 첫 번째 픽셀을 나타낸다.
그 다음 32비트는 이미지 첫 줄의 두 번째 픽셀을 나타낸다.
처음 주소 값에 size_line을 더하면 이미지 둘째 줄로 갈 수 있다.

 

 

bpp는 앞에서 정리했었다.

 

색을 RGB로 표현하면 3바이트, bpp = 24

색을 ARGB로 표현하면 4바이트, bpp = 32

 

 

mlx에서는 색을 ARGB로 사용하기 때문에 bpp도 32고, 아래 문장은 전부 동일한 의미다.

ARGB 형식을 사용한다 
= 색상을 4바이트로 표현한다 
= opp를 4로 둔다(4칸씩 띄워서 색상을 저장한다) 
= bits_per_pixel이 32다

 

 

그리고 get_data_addr함수에서 bpp에 32를 넣어서 우리한테 위 정보를 노출하고 있다.

(cf) 엔디언을 0으로 주고 있는 것도 확인 가능)

[라이브러리 - interface.swift]

@_cdecl("mlx_get_data_addr")
public func mlx_get_data_addr_swift(_ imgptr:UnsafeRawPointer, _ bpp:UnsafeMutablePointer<Int32>, _ sizeline:UnsafeMutablePointer<Int32>, _ endian:UnsafeMutablePointer<Int32>) -> UnsafeMutablePointer<UInt32>
{
	let img:MlxImg = _mlx_bridge(ptr:imgptr)
	bpp.pointee = 32
	sizeline.pointee = Int32(img.texture_sizeline)
	endian.pointee = Int32(0)
	return img.texture_data
}

 

 

그래서 "뜯어보지 않고 어떻게 4라는 값을 바로 알지?" 라는 의문에 대해서는

get_data_addr 함수에서 bpp값(32)을 주고 있고, [size_line * y + (bpp / 8) * x] 가 정확한 접근이다. 라고 답할 수 있다.

int main(void)
{
    char    *data;

    data = mlx_get_data_addr(img, &bpp, &size_l, &endian);
    
    for(int i = 0; i < img_height; i++)
        for (int j = 0; j < img_width; j++)
            *(unsigned int *)(data + (size_l * i + (bpp / 8) * j))
}

 


(unsigned int *) 캐스팅

 

매 4번째마다 색상이 들어있기 때문에 mlx_get_data_addr 전체를 (unsigned int *)로 형변환해도 문제없이 사용 가능하다.

이때 데이터 저장 상황은 아래와 같다. 전체적으로 1/4 사이즈로 줄었다고 보면 된다. (4 = bpp / 8)

 

 

(unsigned int *)로 캐스팅했을 때 데이터 접근방법

int main(void)
{
    unsigned int   *data;

    data = (unsigned int *)mlx_get_data_addr(img, &bpp, &size_l, &endian);
    
    for(int i = 0; i < img_height; i++)
        for (int j = 0; j < img_width; j++)
            data[size_l / (bpp / 8) * i + j]
}

 


cub3d에서 이미지 띄우는 흐름

 

1. 윈도우 사이즈의 새 이미지를 만든다.

2. 각 xpm 파일을 불러와 각 이미지 데이터를 저장해놓는다. (img_width, img_height, bpp, size_l도 각각 저장)

3. floor, wall, sprite 순서로 스캔하면서, 저장한 이미지 데이터들에서 색상을 뽑아와 윈도우 사이즈의 버퍼에 저장한다.

4. 스캔이 모두 끝난 버퍼를 처음에 만들었던 이미지 데이터에 넣는다.

5. 이미지를 윈도우에 띄운다. 

 

 

3번 단계에서 데이터 접근, [size_l / (bpp / 8) * y + x]가 사용된다.

 

'42 > cub3d' 카테고리의 다른 글

cub3d - 완성본  (0) 2020.10.19
cub3d - lodev 레이캐스팅, 카메라행렬  (0) 2020.10.19
cub3d - transparency  (0) 2020.10.17
cub3d - wall  (0) 2020.10.15
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함