PNG/BMP 투명 채널 이미지를 Framebuffer에叠加하는 방법

서론

PNG 파일 형식은 가장 널리 사용되는 투명 이미지를 위한 포맷이다. PNG-32 형식이 가장 일반적인데, 이는 RGBA-8-8-8-8 형식, 즉 32비트 트루컬러 픽셀을 의미한다. 여기서 A는 Alpha 투명 채널을 나타낸다.

BMP 프레임 파싱

BMP는 가장 단순한 이미지 파일 형식으로, 파일 저장을 위해 최소한의 헤더만 추가되어 있다. 위키피디아의 BMP 파일 형식 테이블을 참고하여 구조체를 정의하고 직렬화/역직렬화하면 RGBA 픽셀을 추출할 수 있다.

PNG 프레임 파싱

PNG 인코딩은 BMP보다 복잡하지만 충분히 파싱이 가능하다. no_std Rust 환경에서는 minipng 라이브러리를 사용하는 것이 편리하다. C语言이 필요한 경우 RFC 2083 표준 문서를 참고하여 직접 구현할 수 있다.

PNG 파일 구조

PNG 파일는 기본적으로 단일 레이어 TLV(Type-Length-Value) 방식으로 구성된다.

RFC 2083 구조:

  • Header: 파일 형식 헤더로, PNG 파일 여부를 식별하는 파일 서명 영역이다.
  • 데이터 블록: Length, Type, Data, CRC로 구성된다.

PNG는 표준 중요 데이터 블록(Critical Chunk)과 선택적 보조 데이터 블록(Ancillary Chunk)을 정의한다. 일반적으로 중요 데이터 블록만 있으면 된다.

주요 데이터 블록

  • IHDR: 이미지 헤더로, 너비, 높이, 비트 심도, 색상 유형, 압축 알고리즘, 필터 방법, 인터레이스 방법을 포함한다.
  • PLTE: 팔레트 블록(선택 사항)
  • IDAT: 이미지 데이터 블록으로, RGBA 픽셀이 포함된다. 여러 블록이 존재할 수 있다.
  • IEND: 이미지 종료 블록

압축 알고리즘: LZ77, Huffman, 양자화

알파 블렌딩 알고리즘

투명 채널 A는 화면에 직접 존재하지 않는다. 이 채널은 배경과의 연산에만 사용된다. 즉, A 채널은 배경색이 있어야 연산이 가능하다.

화면의 RGB 픽셀과 PNG의 RGBA 픽셀을 병합하는 알고리즘은 다음과 같다:

struct ColorPixel {
    red: u8,
    green: u8,
    blue: u8,
}

impl ColorPixel {
    fn apply_alpha(&mut self, foreground: &ForegroundPixel) {
        let alpha_ratio = foreground.alpha as f32 / 255.0;
        let fg_red = foreground.red as f32;
        let fg_green = foreground.green as f32;
        let fg_blue = foreground.blue as f32;

        self.red = ((1.0 - alpha_ratio) * self.red as f32 + alpha_ratio * fg_red) as u8;
        self.green = ((1.0 - alpha_ratio) * self.green as f32 + alpha_ratio * fg_green) as u8;
        self.blue = ((1.0 - alpha_ratio) * self.blue as f32 + alpha_ratio * fg_blue) as u8;
    }
}

사각형叠加

다음은 사각형 영역에 이미지를叠加하는 코드이다:

// 화면 버퍼에 PNG를 사각형으로 렌더링
// image_width, image_height: PNG 이미지 크기
// dest_buffer: 대상 화면 프레임버퍼
// dest_x, dest_y: 렌더링 시작 좌표

fn render_rectangle(image_data: &[PixelRGBA], image_width: usize, image_height: usize,
                   dest_buffer: &mut [ColorPixel], screen_width: usize, 
                   dest_x: usize, dest_y: usize) {
    for y in 0..image_height {
        for x in 0..image_width {
            let src_idx = x + y * image_width;
            let dst_idx = (dest_x + x) + (dest_y + y) * screen_width;
            
            if dst_idx < dest_buffer.len() {
                dest_buffer[dst_idx].apply_alpha(&image_data[src_idx]);
            }
        }
    }
}

PNG 파일에서 추출한 RGBA 픽셀을 화면 프레임버퍼에 사각형으로叠加하면 PNG 이미지가 표시된다. 전체 화면 버퍼를 가져온 후 시작 좌표偏移를 계산하여 필요한 영역만叠加할 수도 있다.

응용 분야

이러한 기술은 다음과 같은 곳에서 사용된다:

  • 마우스 커서 렌더링
  • OSD(On-Screen Display) 메뉴
  • 디스플레이 버튼 메뉴

결론

가장 많이 사용되는 파일 시스템, 파티션 형식, 네트워크 프로토콜의 프레임 형식은 대부분 유사한 구조를 가진다: HDR(Length-Type-Checksum) + Payload(여러 데이터 블록) + END. 이러한 구조와 TLV 방식, 알고리즘 Indication, 상태 머신 Indication을 결합하면 다양한 파일 형식을 처리할 수 있다.

태그: Rust image-processing PNG bmp framebuffer

7월 11일 01:11에 게시됨