Search…

Định Dạng Ảnh Bitmap - Giới Thiệu và Các Thao Tác Cơ Bản

06/09/20205 min read
Trong đời thường, bạn sẽ gặp các file ảnh có định dạng .PNG, .JPG, .TGA, .BMP,...Với những lập trình viên đặc biệt là trong lập trình games, đồ họa,... cũng sẽ phải thường xuyên thao tác với những file ảnh. Mỗi file đều sẽ có cấu trúc, dữ liệu theo một chuẩn nhăm giúp cho việc truy xuất, thao tác dễ dàng, và mục đích cuối cùng là công việc đạt được thành quả tốt nhất. Trong bài viết này, tôi sẽ giới thiệu về file ảnh có định dạng bitmap (BMP).

Trong quá trình sử dụng máy tính, chúng ta thường gặp các file ảnh có định dạng .PNG, .JPG, .TGA, .BMP,...Với những lập trình viên đặc biệt là trong lập trình games, đồ họa,... cũng sẽ phải thao tác với những file ảnh nhiều. Vậy bạn có bao giờ tự đặt câu hỏi:

Cấu trúc lưu trữ của một file ảnh ra sao? File ảnh lưu trữ dữ liệu như thế nào? Làm sao để lấy được dữ liệu đó?

Trong bài viết này, tôi sẽ giới thiệu qua về cấu trúc và thao tác cơ bản (đọc, ghi) của định dạng ảnh bitmap (có phần đuôi mở rộng là .BMP hay .DIB).

Môi trường thử nghiệm

Mã nguồn trong bài viết được tôi soạn thảo và biên dịch sử dụng Visual Studio 2019 trên Windows 10 và ảnh được sử dụng trong bài viết là ảnh bitmap 24-bit được tạo ra bởi phần mềm MS Paint hoặc bạn có thể sử dụng ảnh sau để thử nghiệm.

Tải ảnh bmp để thử: STDIOIoT.zip

Thành phần của file bitmap

TÊN KÍCH THƯỚC ĐẶC TẢ
Header 14 Giúp nhận diện định dạng file (bitmap) cũng như kiểm tra xem file có bị hỏng hay không?
Information 40(*) Lưu trữ thông tin hình ảnh.
Color Table 4*x Với x là số màu sử dụng, Color table chứa danh sách màu sắc được dùng trong hình ảnh. Giá trị của x có thể tìm thấy trong Information.
Image Data   Dữ liệu của hình ảnh.

Chú ý: (*) Có nhiều cấu trúc Information khác nhau nhưng thông dụng nhất là loại 40 bytes.

Header

struct BitmapHeader
{
	char          m_type[2];
	unsigned char m_size[4];
	unsigned char m_reserved1[2];
	unsigned char m_reserved2[2];
	unsigned char m_data_offset[4];
};

Trong đó:

  • m_type: Kiểu file (File bitmap thì sẽ có giá trị BM)
  • m_size: Kích thước của file (byte)
  • m_reserved: Phần dự trữ, có giá trị được quyết định bởi phần mềm tạo ra file bitmap
  • m_data_offset: offset của dữ liệu của hình ảnh (Tính từ vị trí bắt đầu của file bitmap là 0)

Chú ý:

Thông số m_size sử dụng một mảng char có kích thước là 4. Tôi không dùng kiểu int cho trường hợp này vì:

  • Nếu sử dụng char sẽ giúp đơn giản hóa đoạn code này và bạn không chạm vào các kiến thức sâu hơn. 
  • Trong trường hợp bạn thay thế unsigned char m_size[4] bằng int m_size bạn không thể lấy được dữ liệu ra (giá trị của sizeof(BitmapHeader) sẽ không như mong đợi). Bạn có thể tham khảo bài viết Struct Alignment Trong C để hiểu rõ hơn về vấn đề này.

Vậy làm lấy dữ liệu ra khỏi struct thì sao khi mà các trường dữ liệu đều là kiểu char[]? Bạn có thể sử dụng một con trỏ và ép kiểu để lấy dữ liệu ra như sau:

int image_size = *(int*)m_bitmap_header.m_size;

Information

struct BitmapInformation
{
	unsigned char m_size[4];
	unsigned char m_width[4];
	unsigned char m_height[4];
	unsigned char m_planes[2];
	unsigned char m_bit_depth[2];
	unsigned char m_compression_type[16];
	unsigned char m_color_used[4];
	unsigned char m_color_important[4];
};

Trong đó:

  • m_size: Kích thước của struct (khác nhau với các cấu trúc Information)
  • m_width, m_height: Chiều dài và chiều rộng của bức ảnh (tính theo pixel)
  • m_planes: Đối với bitmap, giá trị buộc phải bằng 1
  • m_bit_depth: Độ sâu màu của hình ảnh, thông dụng có giá trị 24 (8-bit cho mỗi mỗi kênh R-G-B)
  • m_compression_type: Kiểu nén của ảnh
  • m_color_used: Số màu có trong color table (Thường là 0 do color table không được sử dụng)
  • m_color_important: Số index cần để hiện thị hình ảnh (Đối với index color table)

Color Table

Bảng chứa các màu được sử dụng trong hình ảnh, thành phần này có chức năng nếu là indexed color, color table có chức năng như bảng tra màu (Dùng trong trường hợp có nén ảnh).

Trong bài viết này, tôi chỉ giới thiệu về ảnh bitmap không nén. Vì thế, tôi không đi thêm vào việc giới thiệu về color table.

Image Data

Đây là phần chính, chứa khối dữ liệu của file ảnh. Một số lưu ý khi đọc file bitmap:

  • Pixel đầu tiên là điểm đầu tiên góc trái phía dưới của hình ảnh.
  • Nếu bitdepth có giá trị 24 tức có 3 kênh màu thì dữ liệu lưu trữ với cấu trúc B-G-R.
  • Dữ liệu không được nối liên tục mà ở cuối mỗi hàng sẽ có (hoặc không có) padding sao cho độ dài mỗi hàng chia hết cho 4 (byte). Kích thước (tính cả padding) của mỗi dòng có thể được tính bằng công thức:

Thao tác cơ bản

Đọc file

void loadImage(const char * _filePath, BitmapHeader & _header, BitmapInformation & _information, unsigned char * &_data)
{
	FILE* _imageFile = fopen(_filePath, "rb");

	if (_imageFile == nullptr)
	{
		return;
	}

	fread(&_header, sizeof(BitmapHeader), sizeof(char), _imageFile);
	fread(&_information, sizeof(BitmapInformation), sizeof(char), _imageFile);

	int _width = *(int*)_information.m_width;
	int _height = *(int*)_information.m_height;
	int _bitdepth = *(int*)_information.m_bitDepth;
	int data_offset = *(int*)_header.m_data_offset;

	int _rowSize = (_bitdepth * _width + 31) / 32 * 4;

	if (_data != nullptr)
	{
		delete[] _data;
        _data = nullptr;
	}
	_data = new unsigned char[_rowSize * _height];

	fseek(_imageFile, data_offset, SEEK_SET);
	fread(_data, _rowSize * _height, sizeof(char), _imageFile);

	fclose(_imageFile);
}

Ghi file

void saveImage(const char * _filePath, BitmapHeader & _header, BitmapInformation & _information, const unsigned char * _data)
{
	int _width = *(int*)_information.m_width;
	int _bitdepth = *(int*)_information.m_bitDepth;
	int _offset = *(int*)_header.m_data_offset;

	FILE * _imageFile = fopen(_filePath, "wb");

	fwrite(&_header, sizeof(BitmapHeader), sizeof(char), _imageFile);
	fwrite(&_information, sizeof(BitmapInformation), 1, _imageFile);

	int _rowSize = (_bitdepth * _width + 31) / 32 * 4;

	fseek(_imageFile, _offset, SEEK_SET);
	fwrite(_data, _rowSize * _height, sizeof(char), _imageFile);
	fclose(_imageFile);
}

Không phủ nhận sự đóng góp rất lớn lao của các cá nhân, tập thể trong việc xây dựng và phát triển các thư viện ngoài, các công cụ giúp lập trình viên có thể nhanh chóng hoàn thành sản phẩm với độ chính xác, chất lượng tốt nhất. Nhưng tùy từng thời điểm và nhu cầu mà cần phải áp dụng phù hợp.

Việc tìm hiểu một vấn đề cặn kẽ hơn sẽ giúp kinh nghiệm của bạn tăng lên, lập trình là một nghệ thuật và nó là phải là thứ được trau dồi, rèn luyện thường xuyên.

Download project hoàn chỉnh

BitmapSample.zip

IO Stream

IO Stream Co., Ltd

30 Trinh Dinh Thao, Hoa Thanh ward, Tan Phu district, Ho Chi Minh city, Vietnam
+84 28 22 00 11 12
developer@iostream.vn

383/1 Quang Trung, ward 10, Go Vap district, Ho Chi Minh city
Business license number: 0311563559 issued by the Department of Planning and Investment of Ho Chi Minh City on February 23, 2012

©IO Stream, 2013 - 2024