Computer Graphics/SCOP

SCOP - 5. stb 대체하는 bmp 로더 만들기

surkim 2024. 9. 29. 17:31

이번 포스트에서는 stb 라이브러리를 대체하기 위해 추가한 BMP 로더에 대해 설명한다. stb_image를 사용해 이미지를 로드하는 기능을 직접 구현함으로써 라이브러리 의존성을 제거했다. 여기서 BMP 파일을 로드하고 처리하는 LoadWithBmp() 함수가 이번에 추가된 부분이다.

BMP 로더 핵심 로직

BMP 파일 로딩은 기존 stb_image를 사용한 로딩 방식과 다르게 직접 바이너리 데이터를 읽어와 이미지의 정보를 추출하고, 이를 m_data 버퍼에 저장하는 과정을 거친다. 자세한 로직은 다음과 같다.

bool Image::LoadWithBmp(const std::string& filepath) {
    std::ifstream file(filepath, std::ios::binary);
    if (!file) {
        std::cerr << "Failed to open BMP file: " << filepath << std::endl;
        return false;
    }

    BMPFileHeader fileHeader;
    BMPInfoHeader infoHeader;

    // 파일 헤더 읽기
    file.read(reinterpret_cast<char*>(&fileHeader), sizeof(fileHeader));
    if (fileHeader.fileType != 0x4D42) { // "BM"인지 확인
        std::cerr << "Invalid BMP file: " << filepath << std::endl;
        return false;
    }

    // 정보 헤더 읽기
    file.read(reinterpret_cast<char*>(&infoHeader), sizeof(infoHeader));
    if (infoHeader.bitsPerPixel != 24 && infoHeader.bitsPerPixel != 32) {
        std::cerr << "Unsupported BMP bit depth: " << infoHeader.bitsPerPixel << std::endl;
        return false;
    }

    // 이미지 크기 및 채널 수 설정
    m_width = infoHeader.width;
    m_height = abs(infoHeader.height); // BMP 높이는 음수일 수 있음
    m_channelCount = infoHeader.bitsPerPixel / 8;

    // 이미지 데이터에 대한 메모리 할당
    m_data = (uint8_t*)malloc(m_width * m_height * m_channelCount);
    if (!m_data) {
        std::cerr << "Failed to allocate memory for BMP image" << std::endl;
        return false;
    }

    // 파일 커서를 픽셀 데이터 위치로 이동
    file.seekg(fileHeader.dataOffset, file.beg);

    // 픽셀 데이터를 행 단위로 읽기 (BMP 데이터는 기본적으로 아래에서 위로 저장됨)
    int rowSize = (m_width * m_channelCount + 3) & ~3; // 행의 크기를 4바이트 경계로 맞춤
    std::unique_ptr<uint8_t[]> rowData(new uint8_t[rowSize]);

    for (int y = 0; y < m_height; ++y) {
        file.read(reinterpret_cast<char*>(rowData.get()), rowSize);
        memcpy(m_data + y * m_width * m_channelCount, rowData.get(), m_width * m_channelCount);
    }

    // BGR을 RGB로 변환
    if (m_channelCount >= 3) {
        for (int i = 0; i < m_width * m_height; ++i) {
            uint8_t* pixel = m_data + i * m_channelCount;
            std::swap(pixel[0], pixel[2]); // R과 B 스왑
        }
    }

    return true;
}

함수 상세 설명

  • 파일 읽기 및 헤더 파싱:
    BMP 파일을 바이너리로 열어 BMPFileHeaderBMPInfoHeader를 파싱한다. 파일 타입이 "BM"이 아닌 경우나 지원하지 않는 비트 깊이(24비트, 32비트 이외)를 가진 경우 로딩을 실패하도록 설정했다.
  • 이미지 데이터 메모리 할당:
    m_width, m_height, m_channelCount를 헤더에서 가져와 이미지를 저장할 메모리를 할당한다.
  • 픽셀 데이터 읽기:
    BMP 파일의 데이터 오프셋에 해당하는 위치로 커서를 이동시킨 후, 이미지 데이터를 행 단위로 읽어들인다. BMP 파일은 하단부터 상단으로 데이터를 저장하기 때문에, 각 행을 읽은 후 m_data 버퍼에 순서대로 복사한다.
  • RGB 변환:
    BMP 파일은 기본적으로 BGR 형식이므로, 이미지 채널이 3개 이상일 경우 R과 B 채널을 교환하여 RGB로 변환한다.

 


문제점 및 향후 계획

현재 과제에서 사용되는 오브젝트의 중심축이 치우쳐 있어 모델이 정확하게 회전하지 않는 문제가 있다. 이는 오브젝트의 중심을 모델의 중심축으로 맞춰야 하는데, 이를 수정하는 것이 우선 과제다.

또한, 이제 더 이상 필요하지 않은 stbassimp 라이브러리를 완전히 제거하고 관련 코드를 정리한 후, 정상적으로 작동하는지 검증하는 작업이 남아있다. 마지막으로, 최적화와 코드 리팩토링을 통해 sAssimp 라이브러리와 BMP 로더의 완성도를 높여 나갈 예정이다.