Skip to content

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_BITMAPFIT_UINT16FIT_INT16FIT_UINT32FIT_INT32FIT_FLOATFIT_DOUBLEFIT_COMPLEXFIT_RGB16FIT_RGBA16FIT_RGBFFIT_RGBAF

2.3 图像处理

不是大而全的图像处理库,但内置一组实用功能:

  • 几何变换:FreeImage_Rescale(多种滤波器)、FreeImage_RotateFreeImage_FlipHorizontal/VerticalFreeImage_CopyFreeImage_Paste
  • 色彩转换:FreeImage_ConvertTo24BitsFreeImage_ConvertTo32BitsFreeImage_ConvertToGreyscaleFreeImage_ConvertToRGBF
  • 二值化与抖动:FreeImage_ThresholdFreeImage_Dither(多种算法)。
  • 颜色调整:FreeImage_AdjustGammaFreeImage_AdjustBrightnessFreeImage_AdjustContrastFreeImage_Invert
  • 通道操作:FreeImage_GetChannelFreeImage_SetChannel
  • 调色板:FreeImage_GetPaletteFreeImage_SetPalette
  • DPI 元数据:FreeImage_GetDotsPerMeterX/YFreeImage_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 应满足的事项:

  1. 保留版权与许可文本:在 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.
    
  2. 不修改 FreeImage 源码:如果使用预编译 DLL,则不会触发"修改公开"义务。
  3. 如修改源码:把修改后的源码公开(典型做法是在公司公开仓库挂一份 patch)。
  4. 决定是否包含 LibRaw:商业项目如不需要 RAW 解码,建议从源码构建 FreeImage 并禁用 LibRaw 插件,规避 LGPL 链接问题。
  5. DLL 分发:把 FreeImage.dll(或自定义构建的 .dll)与应用一起分发;不要把 FreeImage 静态编入你的核心闭源模块以避免边界模糊。
  6. 更新追踪: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 协议层之外几乎所有像素工作的唯一执行者。