STDIO
Tìm kiếm gần đây
    • Nội dung
    • QR Code
    • 0
    • 0
    • Sao chép

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

    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).
    13/09/2017
    06/09/2020
    5 phút đọc
    Định Dạng Ảnh Bitmap - Giới Thiệu và Các Thao Tác Cơ Bản

    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

    0 Bình luận

    Đề xuất

    EmguCV - OpenCV cho .NET - Một Số Thao Tác Xử Lý Ảnh Cơ Bản - I
    Hướng dẫn các thao tác xử lý ảnh cơ bản với thư viện EmguCV bao gồm: ...
    EmguCV - OpenCV cho .NET - Một Số Thao Tác Xử Lý Ảnh Cơ Bản - II
    Hướng dẫn thao tác xử lý ảnh cơ bản với thư viện EmguCV như: Gradient, ...

    Khám phá

    Các Thao Tác Cơ Bản Quản Lý Project Với Git
    Giới thiệu khái niệm và ưu điểm của Git. Hướng dẫn cài đặt và các thao ...
    05/08/2015
    Giới Thiệu Về Sprite Trong Cocos2d-x 3.x.x
    Giới thiệu tổng quan về khái niệm cơ bản, một số cách dùng để khởi tạo ...
    Box2D - Phần 1: Giới Thiệu - Một Số Thuật Ngữ và Khái Niệm
    Giới thiệu engine xử lý vật lý Box2D, các khái niệm, cách thành phần ...
    Hello MFC - Các Control Cơ Bản trong MFC
    Giới thiệu và hướng dẫn thao tác với control trong MFC.
    Tìm Hiểu và Tạo Tài Liệu XML Đầu Tiên
    Giới thiệu cấu trúc cơ bản của các tài liệu XML và các quy tắc cơ bản để ...
    21/09/2014
    Các Phép Đếm Cơ Bản
    Giới thiệu về các quy tắc đếm cơ bản: quy tắc cộng, quy tắc nhân, tổ ...
    26/09/2014
    Cơ Bản về Camera trong Unity
    Giới thiệu các thông số của camera trong Unity và các thao tác thường sử ...
    LINQ to SQL - Giới Thiệu
    Giới thiệu LINQ to SQL, ý nghĩa và cú pháp cơ bản sử dụng LINQ to SQL ...
    Khi bạn nhấn vào liên kết sản phẩm do STDIO đề xuất và mua hàng, STDIO có thể nhận được hoa hồng. Điều này hỗ trợ STDIO tạo thêm nhiều nội dung hữu ích. Tìm hiểu thêm.
    STDIO
    Trang chính
    Công ty TNHH STDIO

    30, Trịnh Đình Thảo, Hòa Thạnh, Tân Phú, Hồ Chí Minh
    +84 28.36205514 - +84 942.111912
    developer@stdio.vn

    383/1 Quang Trung, Phường 10, Quận Gò Vấp, Hồ Chí Minh
    Số giấy phép ĐKKD: 0311563559 do sở Kế hoạch và Đầu Tư TPHCM cấp ngày 23/02/2012

    ©STDIO, 2013 - 2020