Tài trợ bài viết này và giới thiệu dịch vụ, sản phẩm, thương hiệu, nhu cầu tuyển dụng của doanh nghiệp đến với cộng đồng.
Vũ Quang Huy Mỗi định dạng ảnh (*.jpg, *.png, *.tga, *.bmp ...) đều có những quy chuẩn về lưu trữ của chính bản thân định dạng ảnh đó. Vì vậy, ứng với mỗi định dạng ta phải có các cách đọc, ghi riêng và đôi khi phải sử dụng chính các thư viện bên ngoài chỉ để phục vụ tính năng đó (libpng, libjpeg ...). Bài viết này hướng dẫn bạn đọc sử dụng thư viện FreeImage để làm tất cả các việc này.
Nội dung bài viết

Giới thiệu

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 ta phải có các cách đọc, ghi khác nhau. Với những định dạng đơn giản như BMP hay TGA thì ta có thể dễ dàng 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ì việc 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 ta hay sử dụng thư viện của hãng thứ 3.

Việc sử dụng thư viện của hãng thứ 3 giúp ta 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ủa chúng ta cần phải làm việc với nhiều định dạng ảnh khác nhau thì vấn đề gặp phải sẽ là sự không đồng nhất trong việc sử dụng các thư viện. Ví dụ như để load ảnh PNG với thư viện libpng thì ta 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_*.

Vì vậy, để khắc phục vấn đề này, ta cần viết thêm một wrapper cho toàn bộ các lib này. Hay đơn giản hơn, thư viện FreeImage sẽ giúp ta làm việc đó. Bạn đọc có thể sử dụng thư viện FreeImage để xây dựng các ứng dụng xử lý ảnh cho riêng mình, ví du như blur ảnh theo giải thuật Blur - Ý Tưởng Và Giải Thuật Làm Mờ Ảnh Đơn Giản của tác giả La Kiến Vinh.

Tiền đề bài viết

Trong đồ họa máy tính, một RENDER ENGINE hỗ trợ nhiều định dạng ảnh khác nhau sẽ rất thuận tiện cho người sử dụng. Bài viết ra đời trong quá trình tôi viết một engine có khả năng hỗ trợ nhiều định dạng ảnh khác nhau.

Đối tượng hướng đến

Nội dung trong bài viết này hướng đến bạn đọc muốn tìm một giải pháp tổng quát cho việc tương tác với nhiều định dạng ảnh khác nhau.

Yêu cầu hệ thống

Trong bài viết này tôi sử dụng hệ điều hành Windows 8.1 Pro (x64)Visual Studio 2013 Community with Update 4.

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 cho người sử dụng có thể 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 định dạng ảnh (37 định dạng 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.17. Trang download của FreeImage (http://freeimage.sourceforge.net/download.html) có 3 phần chính bạn đọc cần lưu ý

ss_1 

  • Source distribution: mã nguồn của thư viện FreeImage. Ta 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 thì bạn đọc 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.

Trong bài viết này, tôi 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 ta thu được:

ss_2
 
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 32bit cũng như 64bit. Folder 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 người sử dụng có thể thao tác một cách thuận tiện nhất. Chỉ cần 3 file sau ta đã 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 ta tạo một project C++ trống với tên STDIO_FreeImage_Demo.

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

ss_14

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

ss_4

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 32bit. Vì vậy, tại thư mục chứa project, ta 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.

ss_5
 
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

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

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

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

Do thư viện ta sử dụng là thư viện liên kết động, nên bước cuối cùng ta 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 sẽ là folder Debug. Nếu folder này chưa tồn tại, bạn đọc thử biên dịch chương trình lần đầu tiên để folder này được sinh ra.

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

Bạn đọc quan tâm đến vấn đề thư viện liên kết tĩnh / liên kết động, cách tạo ra và sử dụng chúng thế nào có thể tham khảo bài viết Static Link Library Và Dynamic Link Library.

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

FreeImage là một thư viện đọc ảnh khá đơn giản trong việc sử dụng. Việc đọc một 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 (bạn đọc có để dễ dàng tìm thấy ở Logo Và Nhận Dạng Stdio). Đoạn code sau mô tả việc đọc file này 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);
}

Trong đoạn code bên trên, dòng 9 – 14 sẽ 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 đó, ta cũng kiểm tra xem định dạng ảnh này có hợp lệ không.

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

Dòng code 16 – 21 giúp ta 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.

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

Dòng code 23 – 27 có tác dụng lấy thông tin của ảnh

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);

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

ss_10 

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

ss_11 

Ví dụ minh họa

Với hai thao tác đọc và ghi ảnh ở trên, để minh họa cho việc sử dụng. Ta có đoạn code sẽ làm các công việc như 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

Ảnh trước chỉnh sửa Ảnh sau chỉnh sửa

ss_12

ss_13

Download Demo

STDIO_FreeImageDemo_VS2013.zip

Tham khảo

http://freeimage.sourceforge.net/ - 1/8/2015

THẢO LUẬN
Mọi người nghĩ khoa học máy tính là kỹ thuật thiên tài nhưng thực tế thì ngược lại, chỉ cần nhiều người cùng xây dựng, nó sẽ thành bức tường đá thu nhỏ. Brian W. Kernighan.
ĐÓNG