FreeImage 库介绍 / FreeImage Library Overview
本文档介绍 BN Tech Virtual Scanner 选用的图像处理库 FreeImage:功能、特性、与同类库的对比、许可分析、商用要求,以及常用代码示例(加载、放大、保存、BMP → PNG 转换)。
1. FreeImage 简介
FreeImage 是一个开源、跨平台的 C/C++ 图像加载、保存与基本图像处理库。它最早由 Hervé Drolon 发起,专注于"开发者友好的多格式图像 I/O":用一致的 API 屏蔽 PNG、JPEG、TIFF、BMP、GIF、JPEG2000、RAW、HDR 等 30+ 种格式的差异。
最新稳定版本为 3.18.0(2018 年发布;社区一直在打补丁维护)。本项目使用的 FreeImage 二进制位于 pub/external/freeimage/ 目录,附带 32 位 / 64 位 DLL、import lib 与头文件。
主要应用场景:
- 桌面应用的图像加载 / 缩略图生成 / 格式转换。
- TWAIN / 扫描类项目的图像 I/O(本项目即典型案例)。
- 游戏和 3D 渲染引擎的纹理预处理工具链。
- 科学影像、HDR 处理的轻量级前端。
2. 主要功能
2.1 文件格式支持
读取 / 写入(部分格式仅读):
- 位图类:BMP、PNG、JPEG、JPEG 2000、GIF、TIFF、TARGA、PCX、PSD、ICO、Webp 等。
- 矢量 / 元文件:WBMP、XBM、XPM、PFM。
- HDR / 浮点:HDR(Radiance)、EXR(OpenEXR)、TIFF Float。
- 相机 RAW:CR2、NEF、ARW、DNG 等(基于 LibRaw / dcraw 集成)。
- 其他:JBIG、KOALA、IFF、SGI、PICT、PNM 等。
共约 30 余种格式,覆盖几乎所有常见与历史格式。
2.2 像素 / 位深
支持位深:
- 1-bit(黑白 / 调色板)
- 4-bit、8-bit(调色板 / 灰度)
- 16-bit(555 / 565、灰度、float)
- 24-bit / 32-bit(BGR / BGRA)
- 48-bit / 64-bit(每通道 16-bit RGB / RGBA)
- 浮点(每通道 32-bit / 96-bit / 128-bit)
支持的逻辑像素类型 (FREE_IMAGE_TYPE):FIT_BITMAP、FIT_UINT16、FIT_INT16、FIT_UINT32、FIT_INT32、FIT_FLOAT、FIT_DOUBLE、FIT_COMPLEX、FIT_RGB16、FIT_RGBA16、FIT_RGBF、FIT_RGBAF。
2.3 图像处理
不是大而全的图像处理库,但内置一组实用功能:
- 几何变换:
FreeImage_Rescale(多种滤波器)、FreeImage_Rotate、FreeImage_FlipHorizontal/Vertical、FreeImage_Copy、FreeImage_Paste。 - 色彩转换:
FreeImage_ConvertTo24Bits、FreeImage_ConvertTo32Bits、FreeImage_ConvertToGreyscale、FreeImage_ConvertToRGBF。 - 二值化与抖动:
FreeImage_Threshold、FreeImage_Dither(多种算法)。 - 颜色调整:
FreeImage_AdjustGamma、FreeImage_AdjustBrightness、FreeImage_AdjustContrast、FreeImage_Invert。 - 通道操作:
FreeImage_GetChannel、FreeImage_SetChannel。 - 调色板:
FreeImage_GetPalette、FreeImage_SetPalette。 - DPI 元数据:
FreeImage_GetDotsPerMeterX/Y、FreeImage_SetDotsPerMeterX/Y。 - 元数据(EXIF / IPTC / XMP / GeoTIFF)读写。
- 多页:TIFF / GIF / ICO 多页 / 多帧读写。
- 内存 I/O:从
FIMEMORY流式读写而非走文件系统。
2.4 滤波器(重采样)
FreeImage_Rescale 支持滤波器:
| 滤波器 | 特点 |
|---|---|
FILTER_BOX |
最简单的盒式平均;速度最快、质量最差 |
FILTER_BILINEAR |
双线性;平滑但偏糊 |
FILTER_BICUBIC |
双三次;通用、稍锐 |
FILTER_BSPLINE |
B 样条;平滑、低振铃 |
FILTER_CATMULLROM |
Catmull-Rom;锐度较高 |
FILTER_LANCZOS3 |
Lanczos3;最高质量、可能轻微振铃 |
本项目按 DPI 重采样时统一选 FILTER_LANCZOS3,参见 docs/implement_dpi_design.md §4.3。
2.5 多线程
FreeImage 自身不提供线程池;但是 API 是可重入的,前提是不同线程操作不同的 FIBITMAP。同一个 FIBITMAP 在多线程读写需要调用方加锁。
3. 特性
- 轻量:核心 DLL ~3 MB(含主要解码器);可裁剪。
- 跨平台:Windows、Linux、macOS、FreeBSD、Solaris。
- C 风格 API:句柄
FIBITMAP*+ 大量FreeImage_*函数;上手快、绑定到其他语言简单。 - 同时提供 C++ 封装 (
FreeImagePlus):RAII / 模板化封装,但项目少用。 - 格式插件架构:内部按 FIF (
FREE_IMAGE_FORMAT) 注册插件,可手动添加自定义格式。 - 统一调色板模型:所有索引图都按 RGBQUAD 调色板看待,避免格式特异性代码。
- 支持完整元数据链路:EXIF / IPTC / XMP / Animation / GeoTIFF;扫描场景常用的 DPI 字段自动读写。
- 从
FIMEMORY工作:把图像加载 / 保存到内存缓冲区,方便集成网络 / 流式场景。
4. 与同类库的对比
4.1 对比对象
| 库 | 类型 | 主要用途 |
|---|---|---|
| FreeImage | C 库 + DLL | 多格式 I/O + 轻量处理 |
| libpng / libjpeg / libtiff | C 库(按格式) | 单一格式专精 |
| stb_image | 单头文件 C 库 | 极简图像加载 |
| OpenCV | 大型 C++ 库 | 计算机视觉 + 图像处理 |
| ImageMagick / GraphicsMagick | C/C++ + CLI | 全功能图像处理 |
| WIC(Windows Imaging Component) | COM | Windows 原生图像 I/O |
| Pillow (PIL) | Python 库 | Python 生态图像处理 |
| Skia | C++ 渲染库 | 2D 渲染 / Chrome 用 |
| DevIL | C 库 | 多格式 I/O |
4.2 特性对比
| 维度 | FreeImage | OpenCV | stb_image | libpng+jpeg+tiff | ImageMagick | WIC |
|---|---|---|---|---|---|---|
| 支持格式数量 | 30+ | 几十种(依赖 libjpeg 等) | 6 种(PNG/JPG/BMP/TGA/PSD/GIF, 仅加载) | 各自 1 种 | 100+ | 10+(含 RAW 插件) |
| 多页 (TIFF / GIF) | ✅ | 部分 | ❌ | TIFF ✅ | ✅ | ✅ |
| HDR / 浮点 | ✅ | ✅ | 部分 | TIFF 浮点 | ✅ | 部分 |
| 元数据 (EXIF/XMP/IPTC) | ✅ | 有限 | ❌ | ❌ | ✅ | ✅ |
| 跨平台 | ✅ | ✅ | ✅ | ✅ | ✅ | Windows-only |
| API 复杂度 | 中(C 风格) | 高(C++/Python) | 极低 | 中(按格式各异) | 高(多 API + CLI) | 高(COM) |
| 二进制体积 | ~3 MB | 数十 MB | 0(头文件) | 各 ~1 MB | ~30 MB | 系统自带 |
| 商业友好度 | 复杂(见 §5) | ✅ Apache 2.0 | ✅ Public Domain / MIT | ✅ 各自宽松 | ✅ ImageMagick License | ✅ 系统组件 |
| 处理功能 | 基本 | 强(CV 全套) | ❌(只加载) | ❌ | 强 | 基本 |
| 维护活跃度 | 低(2018 后只补丁) | 高 | 中 | 高 | 高 | 高(微软) |
4.3 选择策略(针对扫描类项目)
- 只需要"加载几种主流格式"的极简场景:用
stb_image更简单。 - Windows-only 且能接受 COM 复杂度:WIC 性能极佳、无第三方依赖。
- 需要 CV / 阈值 / 边缘检测:OpenCV 一站式但体积大。
- 需要广格式 + 基本处理 + 跨平台 + DLL 形式且代码体积小:FreeImage 仍是性价比最佳选择 —— 这也是本项目选它的原因。
- 需要 100% 商用安全:libpng + libjpeg-turbo + libtiff 组合更可控(见 §5)。
4.4 性能简评
- 单图加载 / 保存性能:与 libpng / libjpeg 同量级(FreeImage 内部就是封装它们)。
- 重采样:
LANCZOS3在大图上明显慢于 OpenCV 的 SIMD 实现;扫描类小批量场景无压力。 - 多线程:FreeImage 不并行;OpenCV 内部 TBB / OpenMP 加速。
5. 许可分析
5.1 双重许可
FreeImage 采用 双重许可(dual license):
- FreeImage Public License (FIPL),基于 Mozilla Public License 1.1 (MPL 1.1)。
- GNU General Public License (GPL) v2 或 v3。
使用者可以选择其中之一。
5.2 FIPL(基于 MPL 1.1)要点
MPL 1.1 是 file-level copyleft(文件级 copyleft),约束如下:
- 修改 FreeImage 源文件后必须公开修改内容(即对 FreeImage 自身代码的变动需开源)。
- 链接 FreeImage 的应用本身可以保持私有 / 闭源(这点与 LGPL 类似)。
- 必须在分发文档或软件中包含 FIPL 许可声明,告知用户使用了 FreeImage。
- 不能用作者 / 贡献者名义为产品背书。
通俗结论:只用 FreeImage(不改它的源码),你的应用可以闭源商用;但你必须随产品分发许可声明。
5.3 GPL 路径要点
如果选择 GPL,则整个使用 FreeImage 的应用必须以 GPL 兼容许可证发布并提供源码。商用闭源软件几乎不会选这条路径。
5.4 内部依赖的许可
FreeImage 内部静态链接了若干第三方解码库,这是 FreeImage 商用风险的真正所在:
| 第三方组件 | 许可 | 风险等级 |
|---|---|---|
| libpng | libpng License (BSD-style) | 低 |
| libjpeg / libjpeg-turbo | IJG License / BSD | 低 |
| libtiff | BSD-like | 低 |
| zlib | zlib License | 低 |
| OpenJPEG | BSD 2-clause | 低 |
| LibRaw | LGPL 2.1 / CDDL / Commercial | 中(LGPL 触发动态链接义务) |
| OpenEXR | BSD 3-clause | 低 |
| LibWebP | BSD-style | 低 |
| jxrlib | BSD-style | 低 |
最大的注意点是 LibRaw(RAW 相机文件解码器):以 LGPL 2.1 形式静态链接到 FreeImage 内可能让你的应用承担 LGPL 义务(提供 object 文件以便用户重链接,或动态链接)。如果你不需要 RAW 支持,可以从源码构建 FreeImage 时关掉 LibRaw 模块。
5.5 商用要求
实际商用项目使用 FreeImage 应满足的事项:
- 保留版权与许可文本:在 About 对话框、文档或 LICENSE 文件里附 FIPL 全文与 FreeImage 版权声明。例如:
This software uses the FreeImage open source image library. See http://freeimage.sourceforge.net for details. FreeImage is used under the FreeImage Public License (FIPL), version 1.0. - 不修改 FreeImage 源码:如果使用预编译 DLL,则不会触发"修改公开"义务。
- 如修改源码:把修改后的源码公开(典型做法是在公司公开仓库挂一份 patch)。
- 决定是否包含 LibRaw:商业项目如不需要 RAW 解码,建议从源码构建 FreeImage 并禁用 LibRaw 插件,规避 LGPL 链接问题。
- DLL 分发:把
FreeImage.dll(或自定义构建的 .dll)与应用一起分发;不要把 FreeImage 静态编入你的核心闭源模块以避免边界模糊。 - 更新追踪:FreeImage 自 2018 年起更新缓慢;如果合规非常严格,建议自己维护一个 fork 并跟踪 CVE。
5.6 关于"声称商用免费"的常见误区
- "FreeImage 是 free 的所以能商用" — 部分正确:FIPL 允许商用,但要满足声明义务。
- "我可以静态链接到我的 EXE" — 技术上可以,但因为静态链接会"模糊文件边界",FIPL 的 file-level copyleft 解释存在争议;本项目按"DLL 动态链接 + 声明"的最保守做法走。
- "GPL 路径更安全" — 对闭源商用反而不安全;只在你愿意整个产品 GPL 时才选。
6. 在本项目中的使用方式
本仓库的 pub/external/freeimage/ 已包含官方预编译 DLL,CMakeLists.txt 通过 find_library / target_link_libraries 链接。运行时把 FreeImage.dll 放在 .ds 同目录(详见 README.md 的 "Installed files")。
关键调用点:
- 加载源图:
FreeImage_GetFileTypeU+FreeImage_LoadU。 - 颜色 / 位深:
FreeImage_ConvertTo24Bits/FreeImage_ConvertToGreyscale/FreeImage_Threshold。 - 尺寸:
FreeImage_Rescale(..., FILTER_LANCZOS3)。 - DPI 元数据:
FreeImage_SetDotsPerMeterX / Y。 - 保存输出:
FreeImage_Save(FIF_PNG | FIF_JPEG | FIF_BMP | FIF_TIFF, ...)。 - DIB 输出(Native Transfer):
FreeImage_GetWidth / GetHeight / GetBPP / GetScanLine / GetPalette直接拼BITMAPINFOHEADER。
7. 代码示例
以下示例为最小可运行 demo;省略错误处理细节,实际产品代码请补充资源释放与失败分支。
7.1 初始化与清理
#include <FreeImage.h>
int main() {
// 静态库需手动初始化;DLL 版本可省略(启动时自动 Init)。
// FreeImage_Initialise();
// ... 业务代码 ...
// FreeImage_DeInitialise();
return 0;
}
7.2 加载图片
FIBITMAP* LoadImage(const wchar_t* path) {
FREE_IMAGE_FORMAT fif = FreeImage_GetFileTypeU(path, 0);
if (fif == FIF_UNKNOWN) {
// 退化到扩展名嗅探。
fif = FreeImage_GetFIFFromFilenameU(path);
}
if (fif == FIF_UNKNOWN || !FreeImage_FIFSupportsReading(fif)) {
return nullptr;
}
FIBITMAP* bmp = FreeImage_LoadU(fif, path, 0);
return bmp; // 调用方负责 FreeImage_Unload(bmp)
}
7.3 图片放大两倍(高质量 Lanczos3)
FIBITMAP* RescaleTwoTimes(FIBITMAP* src) {
if (!src) return nullptr;
int src_w = FreeImage_GetWidth(src);
int src_h = FreeImage_GetHeight(src);
int dst_w = src_w * 2;
int dst_h = src_h * 2;
FIBITMAP* dst = FreeImage_Rescale(src, dst_w, dst_h, FILTER_LANCZOS3);
return dst; // 调用方负责 Unload;原 src 也要单独 Unload
}
7.4 保存图片
bool SaveImage(FIBITMAP* bmp, const wchar_t* path, FREE_IMAGE_FORMAT fif) {
if (!bmp) return false;
int flags = 0;
if (fif == FIF_JPEG) flags = JPEG_QUALITYGOOD; // ≈85
if (fif == FIF_TIFF) flags = TIFF_LZW; // 8/24bit 用 LZW
if (fif == FIF_PNG) flags = PNG_DEFAULT; // 默认 zlib
return FreeImage_SaveU(fif, bmp, path, flags) == TRUE;
}
7.5 完整示例:BMP → PNG 转换 + 放大两倍 + 写 DPI
#include <FreeImage.h>
#include <cstdio>
int wmain(int argc, wchar_t** argv) {
if (argc < 3) {
std::wprintf(L"usage: bmp2png <in.bmp> <out.png>\n");
return 1;
}
const wchar_t* in_path = argv[1];
const wchar_t* out_path = argv[2];
// 1. 加载 BMP(FreeImage 按签名识别,扩展名只是辅助)。
FREE_IMAGE_FORMAT in_fif = FreeImage_GetFileTypeU(in_path, 0);
if (in_fif == FIF_UNKNOWN) {
in_fif = FreeImage_GetFIFFromFilenameU(in_path);
}
FIBITMAP* src = FreeImage_LoadU(in_fif, in_path, 0);
if (!src) {
std::wprintf(L"failed to load: %ls\n", in_path);
return 2;
}
// 2. 统一到 24-bit BGR,避免索引 / palette 在重采样后出现意外。
FIBITMAP* rgb24 = FreeImage_ConvertTo24Bits(src);
FreeImage_Unload(src);
if (!rgb24) {
std::wprintf(L"convert to 24-bit failed\n");
return 3;
}
// 3. 放大两倍(Lanczos3)。
int src_w = FreeImage_GetWidth(rgb24);
int src_h = FreeImage_GetHeight(rgb24);
FIBITMAP* scaled = FreeImage_Rescale(rgb24, src_w * 2, src_h * 2,
FILTER_LANCZOS3);
FreeImage_Unload(rgb24);
if (!scaled) {
std::wprintf(L"rescale failed\n");
return 4;
}
// 4. 写 DPI 元数据:300 DPI ≈ 300 * 39.37 pixels-per-meter。
unsigned ppm = static_cast<unsigned>(300.0 * 39.37);
FreeImage_SetDotsPerMeterX(scaled, ppm);
FreeImage_SetDotsPerMeterY(scaled, ppm);
// 5. 保存为 PNG。
BOOL ok = FreeImage_SaveU(FIF_PNG, scaled, out_path, PNG_DEFAULT);
FreeImage_Unload(scaled);
if (!ok) {
std::wprintf(L"failed to save: %ls\n", out_path);
return 5;
}
std::wprintf(L"ok: %ls -> %ls (2x, 300dpi)\n", in_path, out_path);
return 0;
}
编译(Windows + MSVC,假设 FreeImage 头文件与 import lib 已在 include / lib 路径):
cl /EHsc /W4 bmp2png.cpp /link FreeImage.lib
bmp2png in.bmp out.png
运行后 out.png 是输入 BMP 的 2 倍尺寸、300 DPI 的 PNG 文件。
7.6 内存 I/O 版本(不落盘)
FIMEMORY* SaveToMemory(FIBITMAP* bmp, FREE_IMAGE_FORMAT fif) {
FIMEMORY* mem = FreeImage_OpenMemory();
FreeImage_SaveToMemory(fif, bmp, mem, 0);
return mem; // 调用方负责 FreeImage_CloseMemory(mem)
}
void ReadFromMemory(BYTE* buffer, DWORD size) {
FIMEMORY* mem = FreeImage_OpenMemory(buffer, size);
FREE_IMAGE_FORMAT fif = FreeImage_GetFileTypeFromMemory(mem, 0);
FIBITMAP* bmp = FreeImage_LoadFromMemory(fif, mem, 0);
// ...
FreeImage_Unload(bmp);
FreeImage_CloseMemory(mem);
}
适合 HTTP 上传 / 数据库 BLOB 场景。
8. 小结
- 优点:API 简洁、格式多、二进制小、跨平台、扫描类项目几乎"开箱即用"。
- 缺点:维护节奏慢、滤波器非 SIMD、商用许可需要小心 LibRaw 等内部依赖。
- 在 BN Tech Virtual Scanner 中,FreeImage 是
VirtualScanner的底层支柱:从加载到 DPI 缩放、像素类型转换、最终 DIB / 文件输出全靠它,是 TWAIN 协议层之外几乎所有像素工作的唯一执行者。