Search…

Đọc Ghi Tất Cả Các Định Dạng Ảnh với FreeImage trong C++

06/09/20209 min read
Hướng dẫn sử dụng FreeImage để đọc, ghi, xử lý ảnh trong C++.

Mỗi định dạng ảnh (*.jpg, *.png, *.tga, *.bmp ...) đều có những quy chuẩn về lưu trữ của riêng chúng. Tùy theo mỗi định dạng mà có các cách đọc, ghi khác nhau. Với những định dạng đơn giản như BMP hay TGA có thể thao tác thông qua mô tả về cách lưu. Tuy nhiên, đối với một số định dạng phức tạp hơn thì cài đặt lại các thao tác tốn một thời gian không nhỏ. Do đó với những định dạng như thế - ví dụ như PNG hay JPG / JPEG có thể sử dụng thư viện của bên thứ 3.

Việc sử dụng thư viện của bên thứ 3 giúp giải quyết được bài toán thao tác với các thư viện ảnh khác nhau, nhưng nếu ứng dụng cần làm việc với nhiều định dạng ảnh khác nhau thì vấn đề gặp phải là sự không đồng nhất trong việc sử dụng các thư viện. Ví dụ để load ảnh PNG với thư viện libpng sẽ có các hàm bắt đầu với png_*, trong khi load ảnh JPG với libjpeg thì cần phải dùng các hàm bắt đầu với jpeg_*.

Thư viện FreeImage

FreeImage là một thư viện được cung cấp miễn phí, chức năng chính của thư viện giúp thao tác với các định dạng ảnh phổ biến cũng như không được phổ biến. Theo như tài liệu của nhà phát triển, FreeImage hỗ trợ các 37+ định dạng ảnh khác nhau:

  • Windows or OS/2 Bitmap File (*.BMP)
  • Dr. Halo (*.CUT)
  • DirectDraw Surface (*.DDS)
  • ILM OpenEXR (*.EXR)
  • Raw Fax format CCITT G3 (*.G3)
  • Graphics Interchange Format (*.GIF)
  • High Dynamic Range (*.HDR)
  • Windows Icon (*.ICO)
  • Amiga IFF (*.IFF, *.LBM)
  • JPEG-2000 codestream (*.J2K, *.J2C)
  • JPEG Network Graphics (*.JNG)
  • JPEG-2000 File Format (*.JP2)
  • Independent JPEG Group (*.JPG, *.JIF, *.JPEG, *.JPE)
  • JPEG XR image format (*.JXR, *.WDP, *.HDP)
  • Commodore 64 Koala format (*.KOA)
  • Multiple Network Graphics (*.MNG)
  • Portable Bitmap (ASCII) (*.PBM)
  • Portable Bitmap (BINARY) (*.PBM)
  • Kodak PhotoCD (*.PCD)
  • Zsoft Paintbrush PCX bitmap format (*.PCX)
  • Portable Floatmap (*.PFM)
  • Portable Graymap (ASCII) (*.PGM)
  • Portable Graymap (BINARY) (*.PGM)
  • Macintosh PICT (*.PCT, *.PICT, *.PIC)
  • Portable Network Graphics (*.PNG)
  • Portable Pixelmap (ASCII) (*.PPM)
  • Portable Pixelmap (BINARY) (*.PPM)
  • Adobe Photoshop (*.PSD)
  • Sun Rasterfile (*.RAS)
  • RAW camera image (many extensions)
  • Silicon Graphics SGI image format (*.SGI)
  • Truevision Targa files (*.TGA, *.TARGA)
  • Tagged Image File Format (*.TIF, *.TIFF)
  • Wireless Bitmap (*.WBMP)
  • Google WebP image format (*.WEBP)
  • X11 Bitmap Format (*.XBM)
  • X11 Pixmap Format (*.XPM)

Phiên bản hiện tại của FreeImage là 3.18. Trang download của FreeImage (http://freeimage.sourceforge.net/download.html) có 3 phần chính cần lưu ý:

  • Source distribution: mã nguồn của thư viện FreeImage, cần phải biên dịch thành định dạng thư viện mong muốn trước khi sử dụng, như *.lib đối với Windows hay *.a đối với Linux.
  • FreeImage DLL: mã nguồn đã được biên dịch thành các file thư viện. Nếu ứng dụng không yêu cầu phải cross-platform nên sử dụng tùy chọn này để giảm thiểu sự phức tạp cũng như độ lớn cho chương trình. 
  • FreeImage Documentation: tài liệu kỹ thuật liên quan đến việc sử dụng thư viện FreeImage.

Bài viết này sử dụng thư viện FreeImage đã được biên dịch ra sẵn các file DLL. Tập tin khi tải về sẽ có dạng FreeImage3170Win32Win64.zip. Giải nén tập tin này:

Sử dụng thư viện Freeimage để load ảnh với C++.

Với các folder đã giải nén, folder quan trọng nhất là:

  • Dist: chứa thư viện FreeImage được compile dành do máy tính 32-bit cũng như 64-bit.
  • Example: chứa ví dụ mẫu hướng dẫn cách sử dụng thư viện.
  • Wrapper: chứa thư viện FreeImage đã được port qua cho các ngôn ngữ khác như .NET, VB.

Sử dụng thư viện FreeImage

Thư viện FreeImage đã được tổ chức sao cho có thể thao tác một cách thuận tiện nhất. Chỉ cần 3 file sau đã có thể sử dụng được thư viện

  • FreeImage.h
  • FreeImage.lib
  • FreeImage.dll

Để minh họa việc sử dụng thư viện này, bước đầu tiên tạo một project C++ trống với tên STDIO_FreeImage_Demo.

Tạo project để sử dụng FreeImage trong C++.

Sau đó tạo thêm file Source.cpp và đưa vào solution:

Tạo project sử dụng FreeImage.

Kiểm tra thư mục chứa project sau khi tạo sẽ có:

Thêm thư viện FreeImage vào project

Project khi được tạo ra mặc định của Visual Studio sẽ được compile cho hệ thống 32-bit. Vì vậy, tại thư mục chứa project, tạo thêm 1 folder tên Lib - chứa thư viện FreeImage - và copy toàn bộ nội dung trong folder <extracted_path>\Dist\x32 đã giải nén ở trên vào folder Lib vừa tạo.

Thư viện FreeImage trong C++.

Và ta tiến hành add 3 file trên vào project qua option Project Properties > Configuration Properties. Tại C/C++ > General > Additional Includes Directory, thêm vào đường dẫn $(SolutionDir)\Lib

Thêm thư viện FreeImage vào project Visual Studio.

Tại Linker > General > Additional Library Directories, ta cũng thêm vào đường dẫn $(SolutionDir)\Lib

Vẫn tại Linker, chọn mục Input > Additional Dependencies và thêm vào FreeImage.lib

Thêm thư viện liên kết FreeImage vào Visual Studio C++.

Chọn OK để chấp nhận các chỉnh sửa.

Do thư viện được sử dụng là thư viện liên kết động, nên bước cuối cùng cần copy file FreeImage.dll vào thư mục chứa file thực thi của chương trình. Trong trường hợp này là folder Debug. Nếu folder này chưa tồn tại, thử biên dịch chương trình lần đầu tiên để folder này được sinh ra.

FreeImage.dll vào project.

Đến đây đã kết thúc quá trình thêm thư viện FreeImage vào project. Tiếp theo tiến hành các bước để đọc và ghi file ảnh.

Đọc ảnh với thư viện FreeImage

FreeImage là 1 thư viện đọc ảnh khá đơn giản trong việc sử dụng. Đọc 1 file ảnh từ các thiết bị lưu trữ với FreeImage có thể được gói gọn trong những bước sau:

  • Xác định định dạng ảnh và load lên thông qua hàm FreeImage_GetFileTypeFreeImage_Load.
  • Lấy các thông tin liên quan bằng tổ hợp các hàm FreeImage_GetWidth, FreeImage_GetHeight, ...
  • Lấy dữ liệu ảnh thông qua hàm FreeImage_GetBits.
  • Xử lý ảnh.
  • Lưu lại ảnh (sẽ được đề cập bên dưới).
  • Giải phóng vùng nhớ sau khi sử dụng với hàm FreeImage_Unload.

Giả sử ta có file stdio-logo.png (tải tại đây). Đoạn code sau đọc file ảnh và hiển thị các thông tin liên quan.

#include <stdio.h>
#include "FreeImage.h"

int main()
{
	char *img_path = "stdio-logo.png";
	int img_width = 0, img_height = 0, img_bpp = 0;
	
	FREE_IMAGE_FORMAT img_format = FreeImage_GetFileType(img_path, 0);
	if (img_format == FREE_IMAGE_FORMAT::FIF_UNKNOWN)
	{
		printf("Error: Unknown format !");
		return 1;
	}

	FIBITMAP* img_bm = FreeImage_Load(img_format, img_path);
	if (img_bm == NULL)
	{
		printf("Error: Image load FAIL !");
		return 1;
	}		

	img_width = FreeImage_GetWidth(img_bm);
	img_height = FreeImage_GetHeight(img_bm);
	img_bpp = FreeImage_GetBPP(img_bm);

	char *pixels = (char*)FreeImage_GetBits(img_bm);

	// Print image's info
	printf("Image path: %s\n", img_path);
	printf("Image width: %d\n", img_width);
	printf("Image height: %d\n", img_height);
	printf("Image BPP (bit-per-pixel): %d", img_bpp);

	// Clean up
	FreeImage_Unload(img_bm);
}

Đoạn code...

FREE_IMAGE_FORMAT img_format = FreeImage_GetFileType(img_path, 0);
if (img_format == FREE_IMAGE_FORMAT::FIF_UNKNOWN)
{
    printf("Error: Unknown format !");
    return 1;
}

...lấy định dạng của file ảnh qua hàm FreeImage_GetFileType. Hàm này nhận vào đường dẫn của file ảnh và trả về định dạng của nó. Song song đó, kiểm tra xem định dạng ảnh này có hợp lệ không.

Đoạn code...

FIBITMAP* img_bm = FreeImage_Load(img_format, img_path);
if (img_bm == NULL)
{
    printf("Error: Image load FAIL !");
    return 1;
}

...load ảnh vào bộ nhớ dùng hàm FreeImage_Load với định dạng ảnh cho trước. FIBITMAP là kiểu dữ liệu được xây dựng sẵn của FreeImage, chứa toàn bộ thông tin của ảnh được load.

Đoạn code...

img_width = FreeImage_GetWidth(img_bm);
img_height = FreeImage_GetHeight(img_bm);
img_bpp = FreeImage_GetBPP(img_bm);

char *pixels = (char*)FreeImage_GetBits(img_bm);

... có tác dụng lấy thông tin của ảnh.

Biên dịch và chạy thử, nếu không có lỗi xảy ra, thu được kết quả:

Đọc thông tin ảnh trong C++ với FreeImage.

Tạo / Lưu ảnh với FreeImage

Với FreeImage, file ảnh được tạo theo các bước sau:

  • Cấp phát vùng nhớ cho bức ảnh dùng hàm FreeImage_Allocate, thông tin bao gồm chiều dài, chiều rộng và số lượng bit trên mỗi pixel. Hàm này trả về đối tượng FIBITMAP
  • Ghi dữ liệu vào ảnh
  • Lưu lại file ảnh sử dụng hàm FreeImage_Save với thông tin về định dạng ảnh, đường dẫn, ...

Đoạn code sau minh họa cho việc tạo một bức ảnh có màu đồng nhất.

#include <stdio.h>
#include "FreeImage.h"

int main()
{
    char *img_path = "stdio-image-demo.png";
    int img_width = 32, img_height = 32, img_bpp = 32;

    FIBITMAP* img_bm = FreeImage_Allocate(img_width, img_height, img_bpp);
    int* pixels = (int*)FreeImage_GetBits(img_bm);

    int size = img_width * img_height;
    for (int i = 0; i < size; i++)
        pixels[i] = 0xffffbb33;
    
    if (FreeImage_Save(FREE_IMAGE_FORMAT::FIF_PNG, img_bm, img_path))
    {
        printf("Info: Save success !!");
    }
    else
    {
        printf("Info: Save fail !!");
    }

    // Clean up
    FreeImage_Unload(img_bm);
}

Bức ảnh được sinh ra có kích thước 32x32 pixel và mã màu là 0xffbb33

Ví dụ minh họa

Với hai thao tác đọc và ghi ảnh ở trên, để minh họa cho cách dùng, có thẻ sử dụng đoạn code sau:

  • Đọc file ảnh stdio-logo.png
  • Đổi màu thành trắng đen
  • Lưu lại thành stdio-logo-gray.png
#include <stdio.h>
#include "FreeImage.h"

int main()
{
    char *img_path = "stdio-logo.png";
    char *img_out_path = "stdio-logo-gray.png";
    int img_width = 0, img_height = 0, img_bpp = 0;

    // Load original image
    FREE_IMAGE_FORMAT img_format = FreeImage_GetFileType(img_path, 0);
    if (img_format == FREE_IMAGE_FORMAT::FIF_UNKNOWN)
    {
        printf("Error: Unknown format !");
        return 1;
    }

    FIBITMAP* img_bm = FreeImage_Load(img_format, img_path);
    if (img_bm == NULL)
    {
        printf("Error: Image load FAIL !");
        return 1;
    }

    // Get neccessary infomation
    img_width = FreeImage_GetWidth(img_bm);
    img_height = FreeImage_GetHeight(img_bm);
    img_bpp = FreeImage_GetBPP(img_bm);

    // Modify image content
    unsigned int *pixels = (unsigned int*)FreeImage_GetBits(img_bm);
    int img_size = img_width * img_height;

    for (int i = 0; i < img_size; i++)
    {
        unsigned char gray_level = (((pixels[i] & 0x00ff0000) >> 16) + 
                                        ((pixels[i] & 0x0000ff00) >> 8) + 
                                        (pixels[i] & 0x000000ff)) / 3;
        unsigned char alpha_level = (pixels[i] & 0xff000000) >> 24;
        pixels[i] = (alpha_level << 24) | (gray_level << 16) | (gray_level << 8) | gray_level;
    }

    // Save modified image
    if (FreeImage_Save(FREE_IMAGE_FORMAT::FIF_PNG, img_bm, img_out_path))
    {
        printf("Info: Save success !!");
    }
    else
    {
        printf("Info: Save fail !!");
    }

    // Clean up
    FreeImage_Unload(img_bm);
}

Và đây là kết quả thu được:

stdio-logo.png stdio-logo-gray.png
Logo STDIO STDIO Logo xám

Download Demo

STDIO_FreeImage2019.zip

Tham khảo

  • https://freeimage.sourceforge.io/ - 6/9/2020
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.co

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