리눅스 프레임버퍼에 선과, 사각형, 원을 그려보는 예제를 다룬다. 기본적인 설명은 이미 앞에서 했으니 생략하기로 하고, 소스에 대해서도 간단히 설명하도록 하겠다. 필자는 수학관련 과목을 별로 좋아하지 않는다. 통계하고 물리는 재미있었는데.. 이상하게 수학은 그냥 싫다. 그런데 하다보면 필요한 경우가 많이 생기는데... 이럴때 AI 서비스는 정말 도움이 된다. 

     

    1. 선 그리기 예제

      선을 그리려면 시작 포인트(x1,y1)와 끝 포인트(x2, y2)가 필요하다.  그리고 그 포인트들 사이를 pixel로 채우면 선이 된다. 수평이나 수직인경우 x나 y의 값이 고정되겠지만 대각선인 경우 사선을 계산해서 그려야 한다. 

    1.1. 선 그리기 소스

      선을 그릴때 색상을 받아야되는데, 색상은 RGB 하나씩 모두 받아서 색상을 표시하도록 한다. 예를들면 255,255,255를 입력 하도록 말이다. 다만, 실행할 때 인자수가 너무 많은게 좀 그렇긴 하네.. 

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/ioctl.h>
    #include <sys/mman.h>
    #include <linux/fb.h>
    #include <math.h>

    #define FB_MAXX 1919
    #define FB_MAXY 1079

    // #include <stdint.h> // C99 표준 자료타입을 사용하기 위함 이걸 안쓰고 줄여쓸라면 아래와 같이 지정.

    typedef unsigned char    u8;     // unsigned char
    typedef unsigned short   u16;   // unsigned short
    typedef unsigned int       u32;    // unsigned int


    void usage(char* str)
    {
        printf("%s x0 y0 x1 y1 r g b\n",str);
        printf("   position : x0, y0, x1, y1\n");
        printf("   color    : r,g,b\n");
        printf("   ex) %s 10 10 40 40 255 255 255\n\n",str);
    }

    u16 RGB565(u8 r, u8 g, u8 b)
    {
        return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
    }

    // 입력된 좌표값이 최소 또는 최대값이 넘지않도록 한다.
    int check_range(int value, int min, int max)
    {
        if (value < min)
            return min;
        if (value > max)
            return max;

        return value;
    }

    // Bresenham's 알고리즘을 사용. gemini로 쉽게 찾아서 도움 받음. 기억 안났었는데..
    void draw_line(u16 *buffer, int width, int height, int x0, int y0, int x1, int y1, u16 color)
    {
        int dx = abs(x1 - x0); // 가로축 거리
        int dy = abs(y1 - y0); // 세로축 거리
        int sx = x0 < x1 ? 1 : -1;  // Sign-X step으로 x축 증/감 값
        int sy = y0 < y1 ? 1 : -1;  // Sign-Y step으로 x측 증/감 값
        int err = (dx > dy ? dx : -dy) / 2, e2; // 오차 보정값

        while (1)
        {
            // 반복문을 돌면서 증가된 프레임버퍼의 좌표 위치에 색상값을 넣는다.
            buffer[y0 * width + x0] = color; // 프레임 버퍼의 배열에서 좌표를 계산한다.

            if (x0 == x1 && y0 == y1) {
                // 증가 위치가 지정된 위치까지 도달하면 반복문을 빠져나간다.
                break;
            }

            e2 = err;   
            if (e2 > -dx) { // -dx와 오차보정값을 비교해서 오차보정값이 큰 경우
                err -= dy;  // 최초 오차보정 값에서 dy를 뺀다.
                x0 += sx;   // 그리고 sx값을 시작값에 누적한다.
            }

            if (e2 < dy) {  // -dy값과 오류보정값을 비교 한다.
                err += dx;  // dx값을 더한다.
                y0 += sy;   // y0에 sy값을 누적.
            }
        }
        printf("\n");
    }

    int main(int argc, char *argv[])
    {
        if (argc != 8) {
            usage(argv[0]);
            return -1;
        }

     
        // argv[0] : 프로그램 이름, argv[1] : x축 시작포인트, argv[2] : y축 시작 ... 
        int x0 = atoi(argv[1]); // 커맨드라인에서 인자값 첫번째 문자열을 숫자로 
        int y0 = atoi(argv[2]);
        int x1 = atoi(argv[3]);
        int y1 = atoi(argv[4]);

        u8 red = atoi(argv[5]);
        u8 green = atoi(argv[6]);
        u8 blue = atoi(argv[7]);

       // x0가 1920(-1)을 범위에 들어오지 않으면 최대 또는 최소값으로 지정.
        x0 = check_range(x0, 0, FB_MAXX);
        y0 = check_range(y0, 0, FB_MAXY);
        x1 = check_range(x1, 0, FB_MAXX);
        y1 = check_range(y1, 0, FB_MAXY);

        // Open framebuffer device
        int fd = open("/dev/fb0", O_RDWR);
        if (fd < 0) {
            perror("open");
            return -1;
        }

        // 프레임 버퍼 정보를 가져옴
        struct fb_var_screeninfo scr_info;
        if (ioctl(fd, FBIOGET_VSCREENINFO, &scr_info) < 0) {
            perror("ioctl");
            close(fd);
            return -1;
        }

        // 프레엠버퍼 크기를 계산 - 16비트 컬러라 2바이트를 곱함.
        size_t fb_size = scr_info.yres_virtual * scr_info.xres_virtual * 2; 

        // 메모리를 매핑(연결)
        void *fb_ptr = mmap(NULL, fb_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        if (fb_ptr == MAP_FAILED) {
            perror("mmap");
            close(fd);
            return -1;
        }

        u16 *pbuf = (u16 *) fb_ptr;

        // 색상 만들기
        u16 color = RGB565(red, green, blue);

        // 라인 그리기
        draw_line(pbuf, scr_info.xres_virtual, scr_info.yres_virtual, x0, y0, x1, y1, color);

        munmap(fb_ptr, fb_size);

        close(fd);

        return 0;
    }

     

     

    1.2. 컴파일

      컴파일 방법은 기존과 동일하다. 

     

      $ gcc -o fb_line fb_line.c

     

     

    1.3. 실행결과

      아래의 명령은 시작점은 100,100이며 끝점은 300, 300이다. 그리고 선의 색상은 255,255,255 즉, 흰색으로 그리도록 인자를 넣어주었다. 

     

      $ ./fb_line 100 100 300 300 255 255 255

     

     

     

    2. 사각형 그리기 예제 

      사각형을 그리려면 시작포인트(x1,y1)와 사각형의 넓이(x2)와 높이(y2)가 필요하다. 그리고 선으로 끝낼지 아니면 사각형 내부를 채우는 사각형을 그릴지 처리하는 예제이다. 

    2.1. 사각형 그리는 소스 (fb_rect.c)

      사각형을 그리되 특정 인자를 받으면 해당 영역은 지정된 색으로 채우면된다. 

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/ioctl.h>
    #include <sys/mman.h>
    #include <linux/fb.h>
    #include <math.h>

    #define FB_MAXX 1920
    #define FB_MAXY 1080

    // #include <stdint.h> // C99 표준 자료타입을 사용하기 위함

    typedef unsigned char    u8;    // unsigned char
    typedef unsigned short   u16;   // unsigned short
    typedef unsigned int     u32;   // unsigned int

    void usage(char* str)
    {
        printf("%s x y rect_w rect_h r g b\n",str);
        printf("   position  : x, y\n");
        printf("   rect size : rect_x, rect_h\n");
        printf("   color    : r,g,b\n");
        printf("   ex) %s 10 10 40 40 255 255 255\n\n",str);
    }

    u16 RGB565(u8 r, u8 g, u8 b)
    {
        return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
    }

    int check_range(int value, int min, int max)
    {
        if (value < min)
            return min;
        if (value > max)
            return max;

        return value;
    }

    // 사각형을 그린다.
    void draw_rect(u16 *buffer, int width, int height, int x, int y, int rect_width, int rect_height, u16 color, u8 option)
    {
        int x0 = check_range(x, 0, width-1);
        int y0 = check_range(y, 0, height-1);
        int x1 = check_range(x + rect_width-1, 0, width-1);
        int y1 = check_range(y + rect_height-1, 0, width-1);

        if(option) {
            for (int row = y0; row <= y1; ++row)
            {
                for (int col = x0; col <= x1; ++col) {
                    buffer[row * width + col] = color;
                }
            }
        } else {
            for (int col = x0; col <= x1; ++col) {
                buffer[y0 * width + col] = color;
                buffer[y1 * width + col] = color;
            }

            for (int row = y0; row <= y1; ++row) {
                buffer[row * width + x0] = color;
                buffer[row * width + x1] = color;
            }
        }
    }

    int main(int argc, char **argv)
    {
        if (argc != 9) {
            usage(argv[0]);
            return -1;
        }

        int pos_x = atoi(argv[1]);
        int pos_y = atoi(argv[2]);
        int rect_w = atoi(argv[3]);
        int rect_h = atoi(argv[4]);
        u16 r = atoi(argv[5]);
        u16 g = atoi(argv[6]);
        u16 b = atoi(argv[7]);
        char opt = atoi(argv[8]);

        if(opt<0||opt>1)
        {
            usage(argv[0]);
            return -1;
        }

        pos_x = check_range(pos_x, 0, FB_MAXX-1);
        pos_y = check_range(pos_y, 0, FB_MAXY-1);
        rect_w = check_range(rect_w, 0, (FB_MAXX-1)-pos_x);
        rect_h = check_range(rect_h, 0,(FB_MAXY-1)-pos_y);

        int fd = open("/dev/fb0", O_RDWR);
        if (fd < 0) {
            perror("open");
            return -1;
        }

        struct fb_var_screeninfo scr_info;
        if (ioctl(fd, FBIOGET_VSCREENINFO, &scr_info) < 0) {
            perror("ioctl");
            close(fd);
            return -1;
        }

        size_t fb_size = scr_info.yres_virtual * scr_info.xres_virtual * 2; // 2 bytes per pixel (RGB 565)

        void *fb_ptr = mmap(NULL, fb_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        if (fb_ptr == MAP_FAILED) {
            perror("mmap");
            close(fd);
            return -1;
        }

        u16 *pbuf = (unsigned short *) fb_ptr;
        u16 color = RGB565(r, g, b);

        draw_rect(pbuf, scr_info.xres_virtual, scr_info.yres_virtual, pos_x, pos_y, rect_w, rect_h, color, opt);

        munmap(fb_ptr, fb_size);

        close(fd);

        return 0;
    }

     

     

    2.2. 컴파일

      컴파일 방법은 기존과 동일하다.

     

      $ gcc -o fb_rect fb_rect.c

     

     

    2.3.  실행결과

      이번에는 두번을 실행했다. 사각형이 서로 붙게 위치를 잘 선정해서 채움인자인 마지막 인자를 0과 1을 번갈아서 실행한 결과를 확인해보자.

     

     

     

    3. 원 그리기 예제 

      원을 그리기 위해서는 중심포인트(x, y)에서 반지름(r)을 입력하여 원을 그리도록 한다. 그리고 사각형과 마찬가지로 선으로 끝낼지 아니면 내부를 채우는 원을 그릴것인지 처리하는 예제이다. 

     

    3.1. 원그리기 소스 (fb_circle.c)

      원을 그리되 특정 인자를 받으면 해당 영역은 지정된 색으로 채우면된다. 

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/ioctl.h>
    #include <sys/mman.h>
    #include <linux/fb.h>
    #include <math.h>

    #define FB_MAXX 1920
    #define FB_MAXY 1080

    // #include <stdint.h> // C99 표준 자료타입을 사용하기 위함

    typedef unsigned char    u8;    // unsigned char
    typedef unsigned short   u16;   // unsigned short
    typedef unsigned int     u32;   // unsigned int

    void usage(char* str)
    {
        printf("%s x y radius r g b opt\n",str);
        printf("   position : x, y\n");
        printf("   size     : radius \n");
        printf("   color    : r,g,b\n");
        printf("   opt      : 0(Outline) or 1(fill)\n");
        printf("   ex) %s 100 100 50 255 255 255 1\n\n",str);
    }

    u16 RGB565(u8 r, u8 g, u8 b)
    {
        return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
    }

    int check_range(int value, int min, int max)
    {
        if (value < min)
            return min;
        if (value > max)
            return max;

        return value;
    }

    // 원 그리기
    void draw_circle(u16 *buffer, int width, int height, int cx, int cy, int radius, u16 color, u8 option)
    {
        int x = radius;
        int y = 0;
        int err = 0;

        while (x >= y)
        {
            if(option) {
                // 원을 채워야 하기 때문에 그려야 되는 라인 별로 데이터를 채운다.
                // 수평의 라인 그리기
                for (int i = check_range(cx - x, 0, width - 1); i <= check_range(cx + x, 0, width - 1); i++)
                {
                    if (cy + y >= 0 && cy + y < height) buffer[(cy + y) * width + i] = color;
                    if (cy - y >= 0 && cy - y < height) buffer[(cy - y) * width + i] = color;
                }

                // 수직 라인 그리기
                for (int i = check_range(cx - y, 0, width - 1); i <= check_range(cx + y, 0, width - 1); i++)
                {
                    if (cy + x >= 0 && cy + x < height) buffer[(cy + x) * width + i] = color;
                    if (cy - x >= 0 && cy - x < height) buffer[(cy - x) * width + i] = color;
                }

            } else {
                 // 외곽선만 필요한 경우에는 원의 외곽 위치에서만 선을 그린다.
                if (cx + x >= 0 && cx + x < width && cy + y >= 0 && cy + y < height) {
                    buffer[(cy + y) * width + cx + x] = color;
                }
                if (cx - x >= 0 && cx - x < width && cy + y >= 0 && cy + y < height) {
                    buffer[(cy + y) * width + cx - x] = color;
                }
                if (cx + x >= 0 && cx + x < width && cy - y >= 0 && cy - y < height) {
                    buffer[(cy - y) * width + cx + x] = color;
                }
                if (cx - x >= 0 && cx - x < width && cy - y >= 0 && cy - y < height) {
                    buffer[(cy - y) * width + cx - x] = color;
                }
                if (cx + y >= 0 && cx + y < width && cy + x >= 0 && cy + x < height) {
                    buffer[(cy + x) * width + cx + y] = color;
                }
                if (cx - y >= 0 && cx - y < width && cy + x >= 0 && cy + x < height) {
                    buffer[(cy + x) * width + cx - y] = color;
                }
                if (cx + y >= 0 && cx + y < width && cy - x >= 0 && cy - x < height) {
                    buffer[(cy - x) * width + cx + y] = color;
                }
                if (cx - y >= 0 && cx - y < width && cy - x >= 0 && cy - x < height) {
                    buffer[(cy - x) * width + cx - y] = color;
                }
            }

            y++;
            err += 1 + 2 * y;
            if (2 * (err - x) + 1 > 0)
            {
                x--;
                err += 1 - 2 * x;
            }
        } 
    }

    int main(int argc, char **argv)
    {
        if (argc != 8)
        {
            usage(argv[0]);
            return -1;
        }

        int pos_x = atoi(argv[1]);
        int pos_y = atoi(argv[2]);
        int radius = atoi(argv[3]);
        u16 r = atoi(argv[4]);
        u16 g = atoi(argv[5]);
        u16 b = atoi(argv[6]);
        char opt = atoi(argv[7]);

        if(opt<0 || opt>1)
        {
            usage(argv[0]);
            return -1;
        }

        pos_x = check_range(pos_x, radius, (FB_MAXX-1)-radius);
        pos_y = check_range(pos_y, radius, (FB_MAXY-1)-radius);
        radius = check_range(radius, 0, ((FB_MAXX-1) - (FB_MAXY-1)/2));

        int fd = open("/dev/fb0", O_RDWR);
        if (fd < 0) {
            perror("open");
            return 1;
        }

        struct fb_var_screeninfo scr_info;
        if (ioctl(fd, FBIOGET_VSCREENINFO, &scr_info) < 0) {
            perror("ioctl");
            close(fd);
            return 1;
        }

        size_t fb_size = scr_info.yres_virtual * scr_info.xres_virtual * 2; // 2 bytes per pixel (RGB 565)

        void *fb_ptr = mmap(NULL, fb_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        if (fb_ptr == MAP_FAILED) {
            perror("mmap");
            close(fd);
            return 1;
        }

        u16 *pbuf = (u16 *) fb_ptr;
        u16 color = RGB565(r, g, b);

        draw_circle(pbuf, scr_info.xres_virtual, scr_info.yres_virtual, pos_x, pos_y, radius, color, opt);

        munmap(fb_ptr, framebuffer_size);

        close(fd);

        return 0;
    }

     

     

    3.2. 컴파일

      컴파일 방법은 뭐.. 동일하다.

     

      $ gcc -o fb_circle fb_circle.c

     

     

    3.3. 결과 확인

      결과를 확인해보자. 이번엔 중첩해서 뿌릴 수 있도록 좌표를 넣었다. 그런데 캡쳐해상도가 뭉개져서인지 픽셀 몇개가 안보인다. 실제 모니터에서하면 그럴싸하게 보이니 이해하고 넘어가도록 하자 .

     

     

     

    4. 컬러바 그리기

      TV 조정화면과 같은 색상을 프레임버퍼에 그리는 예제이다. 보통 이런 예제는 FGPA 샘플에서 DVI등으로 영상을 출력해서 해당 출력이 정상적으로 출력되는지 확인하는 샘플로 많이 쓰인다. TV에서는 영상 표시가 정상적인지, 색상이 정상적인지 검사하는데도 쓰이기도한다. 대신 라인과 선, 격자등 많은 요소를 넣어서 테스트에 이용하는데... 뭐 비슷하면 됐다.

    4.1. 컬러바 소스 (fb_colorbar)

      컬러바 소스는 별것 없다. 앞서 fb_fill.c에서 조건만 조금더 들어가서 특정 위치에서 색상을 바꿔주기만 하면된다.

     

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/ioctl.h>
    #include <sys/mman.h>
    #include <linux/fb.h>

    typedef unsigned char    u8;    // unsigned char
    typedef unsigned short   u16;   // unsigned short
    typedef unsigned int     u32;   // unsigned int


    u16 RGB565(u8 r, u8 g, u8 b)
    {
        return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
    }

    int main(int argc, char** argv) 
    {
        int fd; 

        fd = open("/dev/fb0", O_RDWR);
        if (fd < 0) {
            perror("open");
            return -1;
        }

        struct fb_var_screeninfo scr_info;
        if (ioctl(fd, FBIOGET_VSCREENINFO, &scr_info) < 0) {
            perror("ioctl");
            close(fd);
            return -1;
        }

        size_t fb_size = scr_info.yres_virtual * scr_info.xres_virtual * 2; // 2 bytes per pixel (RGB 565)

        void *fb_ptr = mmap(NULL, fb_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        if (fb_ptr == MAP_FAILED) {
            perror("mmap");
            close(fd);
            return -1;
        }

        u16 *pbuf = (unsigned short *) fb_ptr;

        int width = scr_info.xres_virtual;
        int height = scr_info.yres_virtual;

        int split_width = width / 5; // 색갈별 넓이(평균)

        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                int offset = y * width + x;
                unsigned short color;

                if (x < split_width) {
                    // Red stripe
                    color = RGB565(255, 0, 0);      // 적색
                } else if (x < 2 * split_width) {               
                    color = RGB565(0, 255, 0);      // 녹색
                } else if (x < 3 * split_width) {
                    color = RGB565(0, 0, 255);      // 파란색
                } else if (x < 4 * split_width) {
                    color = RGB565(0, 255, 255);  // 하늘색
                } else {
                    color = RGB565(255, 255, 0);  // 노란색
                }
                pbuf[offset] = color;
            }
        }

        // Unmap framebuffer memory
        munmap(fb_ptr, fb_size);

        // Close framebuffer device
        close(fd);

        return 0;
    }

     

     

     

    4.2. 컴파일

      컴파일 방법...  이쯤 했으면 앞으로 안해도 괜찮겠지라고 생각하고 있다. 

     

      $ gcc -o fb_colorbar fb_colorbar  .c

     

     

    4.3. 결과 확인

       자 결과를 확인해보자. TV 화면조정 했을때보다는 심플하지만 그래도 느낌이 오지 않는가? 혹시나 시간이 되는 독자가 계시면 좀더 복잡하게 멋잇게 한번 만들어서 소스좀 공유해주기 바란다. 나중에 필요하면 좀 가져다 쓰게 말이다. 

     

    전체 화면으로 보면 그럴싸하다. 위에는 결과 볼려고 자르긴 했는데... 아래 그림의 상단의 커서(검은색 사각형)는, 커널 옵션에서 또는 환경 변수에서 안보이게 하거나 없애거나 할 수 있다. 

     

      뭐 어쨋든 단순한 드로잉은 여기서 마치도록 하겠다.  다음번에는 마지막으로 bmp파일을 읽어와서 프레임버퍼에 뿌리는 예제를 다룰 예정이다. 이미지 파일을 불러와서 백그라운드로 깔고, 일부 이미지를 특정좌표에 오버레이 하는것으로 동작 상태를 표시해주는 간단한 애플리케이션은 충분히 개발이 가능하다. 

     

     

    반응형
    • 네이버 블러그 공유하기
    • 네이버 밴드에 공유하기
    • 페이스북 공유하기
    • 카카오스토리 공유하기