- 固件
- 操作系统
【免费下载链接】CloverBootloader
Bootloader for macOS, Windows and Linux in UEFI and in legacy mode
libimagequant 是 pngquant 背后的核心量化引擎,一个体积小巧、无外部依赖的 C 库,专门用于把 RGBA 真彩图像高质量转换为 8 位索引色(调色板)图像。本文以仓库中 CloverApp/libimagequant-2.12.6/README.md 为骨架,结合源码与示例,完整讲解其编译链接方式、五大核心使用流程、全部公开 API 的参数语义,并深入剖析中位切分(median cut)、K-Means 迭代、vantage point tree 等底层实现,以及它在 Clover Bootloader 项目(主题图标量化)中的实际用法。读完本文,你将能独立用 C/C++/Rust/Java 集成 libimagequant,把任意 RGBA 像素流转换为带调色板的 8 位图像,并理解每个配置项对质量与速度的真实影响。
一、库的定位:只做像素,不管文件格式
libimagequant 只处理内存中的原始未压缩像素数组,与任何图像文件格式完全无关。它不做 PNG 编码、不做解码——如果你手头是一个压缩过的 PNG 文件,必须先用 libpng、lodepng 等库解码成 RGBA 像素,再交给 libimagequant 量化;量化完成后输出的 8 位索引像素同样需要交给其他库去做 PNG 压缩。这一点在 example.c 中体现得非常清楚:示例用 lodepng 负责lodepng_decode32_file解码与lodepng_encode编码,libimagequant 只负责中间的调色板生成与像素重映射。
库的头文件 libimagequant.h 中定义了版本宏:
#define LIQ_VERSION 21206 #define LIQ_VERSION_STRING "2.12.6"liq_version()函数返回与LIQ_VERSION相同的整数,便于运行时判断库版本。
二、编译与链接:五种接入方式
2.1 Unix/Linux:静态库
在 Unix-like 系统上构建:
make static生成libimagequant.a,随后直接链接进你的程序:
gcc yourprogram.c /path/to/libimagequant.a在 BSD 上请用gmake(GNU make)代替原生make。
也可以不建库,直接把所有.c文件一起编译进你的程序(定义NDEBUG可得到快速版本):
gcc -std=c99 -O3 -DNDEBUG libimagequant/*.c yourprogram.c源码对 C99 有硬性要求。libimagequant.c 开头就有编译期检查:不满足 C99 且 MSVC 版本低于 18.0 会直接#error报错。
2.2 共享库与安装
./configure --prefix=/usr make libimagequant.so make installconfigure 脚本提供了丰富的选项:--prefix、--libdir、--includedir、--pkgconfigdir、--extra-cflags、--extra-ldflags、--enable-debug、--enable-sse/--disable-sse(SSE 指令开关)、--with-openmp=static(多核支持),以及通过CC=、CFLAGS=、LDFLAGS=覆盖编译器和参数。Makefile 中OBJS = pam.o mediancut.o blur.o mempool.o kmeans.o nearest.o libimagequant.o,可以看到库由六个内部模块构成(见第八节源码解析)。make install会把静态库、共享库、pkg-config 文件(imagequant.pc)和头文件安装到对应目录。
2.3 Rust 程序
在 Cargo 依赖中加入imagequantcrate 即可;也可以用cargo build在imagequant-sys中构建出可供任何 C 兼容语言使用的libimagequant.a。仓库内 rust-api 是官方 Rust 封装(imagequant),rust-sys 是 FFI 层(imagequant_sys)。典型的 Rust 用法见 rust-api/examples/basic.rs:
let mut liq = imagequant::new(); liq.set_speed(5); liq.set_quality(70, 99); let ref mut img = liq.new_image(&fakebitmap[..], width, height, 0.0).unwrap(); let mut res = match liq.quantize(img) { Ok(res) => res, Err(err) => panic!("Quantization failed, because: {:?}", err), }; res.set_dithering_level(1.0); let (palette, pixels) = res.remapped(img).unwrap();2.4 Java JNI
确保JAVA_HOME指向 JDK 目录后:
# export JAVA_HOME=$(locate include/jni.h) # 必要时先设置 JAVA_HOME make java生成libimagequant.jnilib和org/pngquant/下的类;Windows 上运行make java-dll生成libimagequant.dll。仓库内的 org/pngquant/PngQuant.java 演示了 Java 封装:PngQuant对应liq_attr*,通过native方法暴露setMaxColors、setQuality(min,max)、setSpeed、setMinPosterization等配置,getRemapped(BufferedImage)一步完成量化与重映射。
2.5 Windows / Visual Studio
任何基本支持 C99 的编译器(GCC、clang、ICC、C++ Builder、Tiny C Compiler)都能编译本库,但 Visual Studio 2012 及更早版本对 C99 支持不完整。两个推荐方案:
- 使用 Visual Studio2015与库的 MSVC 兼容分支;
- 使用 MinGW 或 MSYS2 的 GCC 构建
libimagequant.a,连同编译器自带的libgcc.a一起加入 VC 项目。
三、核心使用流程:五步完成量化
基本流程固定为五步:
- 创建 attributes 对象并配置库;
- 从 RGBA 像素或数据源创建 image 对象;
- 执行量化(生成调色板);
- 保存重映射后的图像与最终调色板;
- 释放内存。
README.md 给出的最小骨架(完整代码见 example.c):
#include "libimagequant.h" liq_attr *attr = liq_attr_create(); liq_image *image = liq_image_create_rgba(attr, example_bitmap_rgba, width, height, 0); liq_result *res; liq_image_quantize(image, attr, &res); liq_write_remapped_image(res, image, example_bitmap_8bpp, example_bitmap_size); const liq_palette *pal = liq_get_palette(res); // 现在保存图像与调色板 for(int i=0; i < pal->count; i++) { example_copy_palette_entry(pal->entries[i]); } // 需要一个 PNG 库来写文件 example_write_image(example_bitmap_8bpp); liq_result_destroy(res); liq_image_destroy(image); liq_attr_destroy(attr);要点:
- 返回
liq_error的函数在成功时返回LIQ_OK(即0),非零即错误; - 任何接受
liq_attr、liq_image、liq_result的函数都可以安全地传入NULL,此时返回LIQ_INVALID_POINTER; - 这些对象都可以被多次复用(如一个
liq_attr配多个图像)。
创建 image 对象有三种方式:
| 函数 | 适用场景 |
|---|---|
liq_image_create_rgba() | 简单、连续存放的 RGBA 像素数组(宽×高×4 字节) |
liq_image_create_rgba_rows() | 非连续 RGBA 数组(行之间有 padding 或行序颠倒,如 BMP) |
liq_image_create_custom() | RGB、ABGR、YUV 等一切可即时转换为 RGBA 的格式(需自备转换回调) |
四、核心 API 详解
4.1 配置对象(liq_attr)
liq_attr* liq_attr_create(void);
返回持有初始设置的对象,用完后以liq_attr_destroy()释放。极少数情况下(例如为 SSE 版 x86 CPU 编译的库跑在 VIA C3 上)会返回NULL。
liq_error liq_set_max_colors(liq_attr* attr, int colors);
指定使用的最大颜色数,默认 256,取值范围 2–256,越界返回LIQ_VALUE_OUT_OF_RANGE。与其设固定上限,更推荐用liq_set_quality()让库自动决定颜色数。liq_get_max_colors()返回当前设定值。
liq_error liq_set_quality(liq_attr* attr, int minimum, int maximum);
质量取值 0(最差)到 100(最好),语义与 JPEG 质量类似(80 通常就足够好)。量化会尝试用尽可能少的颜色达成maximum质量;maximum默认 100(尽可能好)。如果连 256 色都无法达到minimum质量,liq_image_quantize()将失败返回LIQ_QUALITY_TOO_LOW;minimum默认 0(不管质量如何都继续)。
质量衡量的是"生成的调色板对liq_image_quantize()输入图像的拟合度",若用同一结果去重映射另一张图,实际质量可能不同。无论质量如何设置,颜色数都不会超过liq_set_max_colors()的上限。target 低于 minimum 或任一值超出 0–100 范围返回LIQ_VALUE_OUT_OF_RANGE,attr 无效返回LIQ_INVALID_POINTER。
liq_attr *attr = liq_attr_create(); liq_set_quality(attr, 50, 80); // 尽量达到 80 的质量,低于 50 则放弃对应 getter:liq_get_min_quality()、liq_get_max_quality()。
4.2 创建图像对象(liq_image)
liq_image *liq_image_create_rgba(liq_attr *attr, void* pixels, int width, int height, double gamma);
pixels必须是连续的 RGBA 像素(alpha 为最后一个分量,0 透明,255 不透明),宽×高×4 字节(10×10 的图需要 400 字节数组);- 在对象被
liq_image_destroy()释放前,pixels不得被修改或释放(可用liq_image_set_memory_ownership()改变); - 同一
attr对象应贯穿整个流程(建图到量化); gamma传 0 时使用默认 1/2.2(对多数 sRGB 图像合适);否则必须 >0 且 <1,如0.45455(1/2.2)或0.55555(1/1.8)。除非使用liq_set_output_gamma(),否则生成的调色板沿用同一 gamma;此时 gamma 只影响图像亮部/暗部谁分到更多调色板颜色;pixels为 NULL 或宽/高 ≤0 时返回 NULL。
liq_image *liq_image_create_rgba_rows(liq_attr *attr, void* rows[], int width, int height, double gamma);
接受指向每行像素的指针数组,可表达倒序行(BMP)、stride 大于宽度的行、大位图的局部片段等。rows数组至少要有height个元素,每行至少width个 RGBA 像素:
unsigned char *bitmap = …; void *rows = malloc(height * sizeof(void*)); int bytes_per_row = width * 4 + padding; // stride for(int i=0; i < height; i++) { rows[i] = bitmap + i * bytes_per_row; } liq_image *img = liq_image_create_rgba_rows(attr, rows, width, height, 0); // … liq_image_destroy(img); free(rows);4.3 量化与重映射
liq_error liq_image_quantize(liq_image *const input_image, liq_attr *const attr, liq_result **out_result);
基于attr中的设置与图像像素执行量化(生成调色板)。成功返回LIQ_OK并把liq_result指针写入out_result(注意取址):
liq_result *result; if (LIQ_OK == liq_image_quantize(img, attr, &result)) { // 注意 &result // 此处 result 指针有效 }因liq_set_quality()的限制失败时返回LIQ_QUALITY_TOO_LOW。若想为多张图生成同一个调色板,改用直方图流程(见第六节)。
liq_error liq_set_dithering_level(liq_result *res, float dither_level);
开关liq_write_remapped_image()中的抖动(dithering)。取值必须在 0–1(含)之间:0 启用快速非抖动重映射,否则使用 Floyd-Steinberg 误差扩散的变体。抖动算法的精度取决于liq_set_speed()的速度档位。越界返回LIQ_VALUE_OUT_OF_RANGE。
liq_error liq_write_remapped_image(liq_result *result, liq_image *input_image, void *buffer, size_t buffer_size);
把图像按调色板重映射,以每像素 1 字节写入缓冲区。缓冲区需至少容纳整个图像(宽×高字节),应把缓冲区大小作为buffer_size传入做安全检查;不足时返回LIQ_BUFFER_TOO_SMALL。为了最佳性能,应在此函数之后再调用liq_get_palette(),因为重映射期间调色板会被进一步优化(使用liq_histogram_quantize()时除外)。
int buffer_size = width*height; char *buffer = malloc(buffer_size); if (LIQ_OK == liq_write_remapped_image(result, input_image, buffer, buffer_size)) { liq_palette *pal = liq_get_palette(result); // 保存图像 }缓冲区假定为连续内存、行从上到下、行间无空隙。需要带 padding 或倒序行时改用liq_write_remapped_image_rows()。
const liq_palette *liq_get_palette(liq_result *result);
返回针对已量化/已重映射图像优化过的调色板指针。若计划不重映射或让多图共用调色板,可在重映射前调用。liq_palette->count是颜色数(最多 256),liq_palette->entries[n]是第 n 种颜色的 RGBA 值。调色板是临时只读的——必须在liq_result_destroy()之前复制到别处。出错返回 NULL。
4.4 内存释放
void liq_attr_destroy(liq_attr *); void liq_image_destroy(liq_image *); void liq_result_destroy(liq_result *); void liq_histogram_destroy(liq_histogram *);释放对应对象持有的内存,释放后不得再使用。释放liq_result会连带释放从中取得的liq_palette。
五、高级 API 详解
5.1 速度与输出精度
liq_error liq_set_speed(liq_attr* attr, int speed);
速度越高,禁用的昂贵算法越多、量化精度越低。默认速度 4;速度 1 质量略好但 CPU 开销显著;速度 10 质量通常低约 5%,但比默认快 8 倍。高速档配合liq_set_quality()时会用比必要更多的颜色,且更可能达不到最低质量要求。取值范围 1–10,越界返回LIQ_VALUE_OUT_OF_RANGE。不同档位启用的特性见下表:
| 依赖速度的特性 | 启用档位 |
|---|---|
| 噪声敏感抖动(Noise-sensitive dithering) | speed 1–5 |
| 强制色调分离(Forced posterization) | 8–10,或图像颜色超过一百万种时 |
| 已知量化误差(Quantization error known) | 1–7,或设置了最低质量时 |
| 额外量化技术(Additional quantization techniques) | 1–6 |
liq_get_speed()返回当前设定。
liq_error liq_set_min_opacity(liq_attr* attr, int min);
把 alpha 高于该值的像素四舍五入为不透明。这是针对 IE6 的旧工作区,浏览器早已淘汰,因此已弃用并计划移除。默认 255(不做改动),越界(非 0–255)返回LIQ_VALUE_OUT_OF_RANGE。liq_get_min_opacity()读取当前值。
liq_set_min_posterization(liq_attr* attr, int bits);
忽略所有通道的指定数量最低有效位,把图像色调分离为2^bits级。0 为全质量;2 适用于 VGA 或 16 位 RGB565 显示;4 适用于 RGB444/RGBA4444 显示(如 Android 上的低质量纹理)。越界(非 0–4)返回LIQ_VALUE_OUT_OF_RANGE。liq_get_min_posterization()读取当前值。
liq_set_last_index_transparent(liq_attr* attr, int is_last);
默认 0:alpha 颜色排在不透明颜色之前。非 0 时混合所有颜色,仅把完全透明的颜色移到调色板末尾——这是为"盲目假定最后一个调色板条目是透明色"的程序准备的兼容开关。
5.2 自定义格式输入
liq_image *liq_image_create_custom(liq_attr *attr, liq_image_get_rgba_row_callback *row_callback, void *user_info, int width, int height, double gamma);
用回调按需读取图像数据,支持非 RGBA 色彩空间的即时转换:
void image_get_rgba_row_callback(liq_color row_out[], int row_index, int width, void *user_info) { for(int column_index=0; column_index < width; column_index++) { row_out[column_index] = /* 生成 (row_index, column_index) 处的像素 */; } }user_info会原样传给回调,可用于保存程序内部的图像表示指针。回调必须读取/生成第row_index行并把 RGBA 像素写入row_out(width恒等于图像宽度)。注意:量化与重映射至少需要对图像做两次完整遍历,回调会被每行多次调用,所以缓存回调结果没有意义——这种情况不如整图转换后用liq_image_create_rgba()。
RGB 图像的转换回调示例:
void rgb_to_rgba_callback(liq_color row_out[], int row_index, int width, void *user_info) { unsigned char *rgb_row = ((unsigned char *)user_info) + 3*width*row_index; for(int i=0; i < width; i++) { row_out[i].r = rgb_row[i*3]; row_out[i].g = rgb_row[i*3+1]; row_out[i].b = rgb_row[i*3+2]; row_out[i].a = 255; } } liq_image *img = liq_image_create_custom(attr, rgb_to_rgba_callback, rgb_bitmap, width, height, 0);库不"原生"支持 RGB 位图,是因为只支持单一格式能让编译器内联更多代码、4 字节像素对齐更快,且 SSE 指令一次处理 4 个值,alpha 支持几乎零成本。
5.3 内存所有权
liq_error liq_image_set_memory_ownership(liq_image *image, int ownership_flags);
把图像像素数据和/或行数组的所有权移交给liq_image对象,无需自己释放。对象持有的内存会用liq_attr_create_with_allocator()指定的free释放(默认是 stdlib 的free())。
LIQ_OWN_PIXELS:像素数组归对象所有,不再需要时自动释放;设置此标志后绝不可自行释放像素数组。若图像由liq_image_create_rgba_rows()创建,像素数组的起始地址被假定为任意行的最低地址。LIQ_OWN_ROWS:行指针数组(不含行指向的像素)归对象所有,对象释放时自动释放;设置后绝不可自行释放 rows 数组。此标志仅对liq_image_create_rgba_rows()创建的对象有效。
两标志可用二进制或组合:LIQ_OWN_PIXELS | LIQ_OWN_ROWS。此函数不能用于liq_image_create_custom()创建的对象;标志非法或对象只从回调取像素时返回LIQ_VALUE_OUT_OF_RANGE。
5.4 背景与重要性地图
liq_error liq_image_set_background(liq_image *image, liq_image *background_image);
按"此图像永远正好叠放在该背景之上"的假设来分析与重映射。当图像被重映射到含完全透明色的调色板(用liq_image_add_fixed_color()确保)时,用背景比用调色板表示更优的像素会被置为透明。典型场景:把上一动画帧设为背景,提高 GIF 动画质量。此函数完全接管背景对象所有权,不要自行释放,它会随前景图一起自动释放。背景尺寸与前景不同时返回LIQ_BUFFER_TOO_SMALL。
liq_error liq_image_set_importance_map(liq_image *image, unsigned char map[], size_t buffer_size, liq_ownership ownership);
重要性地图控制图像哪些区域获得更多调色板颜色:值为 0 的像素被完全忽略;值越高该像素权重越大,越可能影响最终调色板。地图为每像素 1 字节,尺寸必须等于图像尺寸(宽×高字节),buffer_size用于校验。ownership为LIQ_COPY_PIXELS时立即拷贝 map 内容(内存自行释放);为LIQ_OWN_PIXELS时 map 内存归图像所有、随图像自动释放(若设置了自定义分配器,map 必须用同一分配器分配)。指针为 NULL 返回LIQ_INVALID_POINTER,尺寸不符返回LIQ_BUFFER_TOO_SMALL,ownership 非法返回LIQ_UNSUPPORTED。
5.5 逐行输出
liq_error liq_write_remapped_image_rows(liq_result *result, liq_image *input_image, unsigned char **row_pointers);
类似liq_write_remapped_image(),把重映射结果(每像素 1 字节)写入row_pointers数组指向的每一行。数组至少要有图像高度个元素,每行至少宽度个字节,行之间不得重叠。最佳性能下同样在调用后取调色板(liq_histogram_quantize()场景除外)。result 或 input_image 为 NULL 时返回LIQ_INVALID_POINTER。
5.6 误差与质量查询
double liq_get_quantization_error(liq_result *result);
返回量化均方误差(源图像像素与重映射版本之差的平方)。已计入 alpha 通道、gamma 校正与像素近似重要性,因此并非各通道均方误差的严格值。经验参考:多数图像 MSE 1–5 为优秀,7–10 可接受,20–30 有明显可见误差,100 很糟糕。高速档、尚未重映射且未设质量下限时可能返回-1(见liq_set_speed()/liq_set_quality());该值不随多图重映射更新,只适用于liq_image_quantize()输入的那张图或第一张被重映射的图。
double liq_get_remapping_error(liq_result *result);
返回最后一次重映射的均方误差(重映射图像与重映射版本之差的平方,同样计入 alpha 与 gamma)。高速档或尚未重映射时可能返回-1。
double liq_get_quantization_quality(liq_result *result);
与liq_get_quantization_error()类似,但把误差换算成liq_set_quality()所用的 0–100 质量值。可能返回-1。典型用途是给用户界面的质量选项设置上限:
liq_attr *attr = liq_attr_create(); liq_image *img = liq_image_create_rgba(…); liq_result *res; liq_image_quantize(img, attr, &res); int max_attainable_quality = liq_get_quantization_quality(res); printf("Please select quality between 0 and %d: ", max_attainable_quality); int user_selected_quality = prompt(); if (user_selected_quality < max_attainable_quality) { liq_set_quality(user_selected_quality, 0); liq_result_destroy(res); liq_image_quantize(img, attr, &res); } liq_write_remapped_image(…);double liq_get_remapping_quality(liq_result *result);
与liq_get_remapping_error()类似,但把误差换算为 0–100 的质量值。
5.7 日志与进度回调
void liq_set_log_callback(liq_attr*, liq_log_callback_function*, void *user_info);
void log_callback_function(const liq_attr*, const char *message, void *user_info) {}void liq_set_log_flush_callback(liq_attr*, liq_log_flush_callback_function*, void *user_info);
void log_flush_callback_function(const liq_attr*, void *user_info) {}设置库报告状态/错误时调用的回调。回调不得调用任何库函数;user_info原样透传(可为 NULL);传 NULL 回调清除当前回调。日志回调中的message是以零结尾的字符串,仅在回调返回前有效,需要的话必须复制。liq_set_log_flush_callback()的回调在最后一次日志回调之后触发,用于冲刷缓冲、释放日志回调占用的资源。内部实现在 libimagequant.c:日志经由liq_verbose_printf/liq_verbose_printf_flush分发。
进度回调(README 中写作liq_set_progress_callback,头文件中正式名称为liq_attr_set_progress_callback):
void liq_attr_set_progress_callback(liq_attr*, liq_progress_callback_function*, void *user_info); void liq_result_set_progress_callback(liq_result*, liq_progress_callback_function*, void *user_info); int progress_callback_function(const liq_attr*, float progress_percent, void *user_info) {}处理图像期间回调被调用,回调返回0可中止处理。liq_attr_set_progress_callback用于量化进度,liq_result_set_progress_callback用于重映射进度(当前只有抖动重映射报告进度)。progress_percent为 0–100 的完成度估算;回调返回1继续、0中止;同样不得调用库函数、user_info可为 NULL、传 NULL 清除回调。
5.8 分配器与复制
liq_attr* liq_attr_create_with_allocator(void* (*malloc)(size_t), void (*free)(void*));
与liq_attr_create相同,但用给定的malloc/free替代品分配库的所有内存。malloc在 x86 上必须返回 16 字节对齐的内存(其他架构上需对齐到double与指针);若 stdlib 的malloc对齐不满足要求,应通过此函数提供对齐版本。
liq_attr* liq_attr_copy(liq_attr *orig);
创建liq_attr的独立副本,副本同样需用liq_attr_destroy()释放。
5.9 输出 gamma 与尺寸查询
liq_error liq_set_output_gamma(liq_result* res, double gamma);
设置生成调色板与重映射图像的 gamma 校正。必须 >0 且 <1,如 PNG 中 gamma 1/2.2 即0.45455。默认输出 gamma 与输入图像一致。
int liq_image_get_width(const liq_image *img); int liq_image_get_height(const liq_image *img); double liq_get_output_gamma(const liq_result *result);三个 getter 分别返回输入图像宽、高与输出 gamma;输入无效时均返回 -1。
5.10 固定颜色
liq_error liq_image_add_fixed_color(liq_image* img, liq_color color);
liq_error liq_histogram_add_fixed_color(liq_histogram* hist, liq_color color, double gamma);
在输出调色板中预留一种颜色,效果等同于该颜色在图中出现且非常重要。liq_color的 RGB 值假定与图像同 gamma;histogram 版本的gamma可为 0(语义见liq_image_create_rgba())。必须在量化前调用。添加超过 256 种颜色时返回错误;若量化出的颜色数少于固定颜色数,多余的固定颜色会被忽略。直方图场景还有更灵活的liq_histogram_add_colors()。
六、多图像共享同一调色板(liq_histogram)
需要为多张图(如 APNG 动画的每一帧)生成一个共同最优调色板时,用liq_histogram对象汇总各图的颜色统计:
liq_attr *attr = liq_attr_create(); liq_histogram *hist = liq_histogram_create(attr); liq_image *image1 = liq_image_create_rgba(attr, example_bitmap_rgba1, width, height, 0); liq_histogram_add_image(hist, attr, image1); liq_image *image2 = liq_image_create_rgba(attr, example_bitmap_rgba2, width, height, 0); liq_histogram_add_image(hist, attr, image2); liq_result *result; liq_error err = liq_histogram_quantize(attr, hist, &result); if (LIQ_OK == err) { // result 中包含对 image1 与 image2 都最优的共享调色板 }liq_histogram *liq_histogram_create(liq_attr *attr);
创建用于汇总多图颜色统计的直方图对象,必须用liq_histogram_destroy()释放。所有选项应在创建直方图之前在attr上设置,之后修改可能不生效。
liq_error liq_histogram_add_image(liq_histogram *hist, liq_attr *attr, liq_image* image);
"学习"图像中的颜色,供后续生成调色板。图像加入直方图后即可释放以省内存(但若还要用于重映射,保留 image 对象更高效)。加入图像时其固定颜色也会加入直方图;固定颜色总数超过 256 时此函数失败返回LIQ_BUFFER_TOO_SMALL。
liq_error liq_histogram_add_colors(liq_histogram *hist, liq_attr *attr, liq_histogram_entry entries[], int num_entries, double gamma);
liq_histogram_add_image()的替代:直接传入颜色及其计数数组(结构见libimagequant.h中的liq_histogram_entry),适合你已经从其他来源拿到了图像直方图的情况。gamma语义见liq_image_create_rgba()。
liq_error liq_histogram_quantize(liq_histogram *const hist, liq_attr *const attr, liq_result **out_result);
从直方图生成调色板:
liq_result *result; liq_error err = liq_histogram_quantize(attr, hist, &result); if (LIQ_OK == err) { // 用 result 重映射并取调色板 }调色板质量低于liq_set_quality()的下限时返回LIQ_QUALITY_TOO_LOW。一个直方图对象只能量化一次。注意:此函数生成的调色板在重映射时不会被改进,如果只为单张图生成调色板,优先用liq_image_quantize()。
七、GIF 工作流
库可以为 GIF 图像生成调色板。为确保透明正确,需要自行预处理:把 0 和 255 之外的 alpha 值替换为二者之一。对动画 GIF,用liq_image_set_background()可针对 GIF 的 "keep" 帧处置方式重映射各帧(见 CHANGELOG 中 2.11 版引入的说明)。
八、多线程与 OpenMP
库是无状态的:不使用任何全局或线程局部存储,不加任何锁。
- 不同线程可以同时执行互不相关的量化/重映射(如每线程处理一张图);
- 同一个
liq_attr、liq_result等对象可从不同线程访问,但不能同时访问(如一个线程创建liq_attr、另一个线程释放)。
库需要排序图中出现的独特颜色,虽然排序算法在典型情况下尽量少用栈,但对极端退化情况无保证,因此线程应使用可自动增长的栈。
用 OpenMP 编译时库会并行化部分操作。不得在liq_image创建后增加最大线程数,因为对象会按线程数预分配每线程缓冲区。liq_image_create_custom()的回调可能被不同线程同时调用。kmeans.c 展示了这种线程化设计:kmeans_update_color按线程号偏移写入average_color数组(用KMEANS_CACHE_LINE_GAP拉开缓存行避免伪共享),kmeans_finalize再把各线程结果聚合。
九、源码级原理:量化管线如何工作
仓库的源码目录(CloverApp/libimagequant-2.12.6)由六个内部模块构成(见 Makefile 的OBJS):
- pam.c:颜色直方图。
pam_computeacolorhash用哈希表统计全图颜色,可选的ignorebits会先把各通道的低位截断(对应liq_set_min_posterization),完全透明的像素统一哈希为 0 并给予 2000 的额外权重,从而让"脏 alpha"(不同 RGBA 值但都表现为全透明)合并为一个颜色; - mediancut.c:中位切分。把颜色空间递归切分为
box,box_variance计算各通道的加权方差决定沿哪个通道切分,并记录每个 box 的均方误差以驱动质量约束; - blur.c:抖动所需的模糊/噪声处理;
- kmeans.c:K-Means 迭代精化调色板,用颜色到各调色板条目的加权平均逐步收敛(支持 OpenMP 多线程聚合);
- nearest.c:vantage point tree 最近邻搜索。
nearest_init/nearest_search用透视点树加速"找调色板中最接近像素的颜色"这一重映射核心操作——这正是 CHANGELOG 中 2.5 版提到的"更快更可靠"的颜色搜索算法; - libimagequant.c:公开 API 实现,负责参数校验、gamma 处理、SSE 检测(
is_sse_available)、日志/进度回调分发与内存所有权管理。
struct liq_attr(libimagequant.c)集中保存了全部配置:target_mse/max_mse(质量上下限换算出的目标/最大误差)、max_colors、min_posterization_*、kmeans_iterations、last_index_transparent、use_contrast_maps、speed以及各回调与 user_info。可见质量、速度、色调分离等选项最终都换算为内部算法参数驱动整条量化管线。
十、Clover 项目中的真实集成案例
在 Clover Bootloader 仓库中,libimagequant 被 CloverApp/Clover/ThemeImage.m 用于主题图像(如开机图标、背景图)的量化压缩:ThemeImage.h 引入libimagequant.h,实现里用lodepng_decode32解码 PNG,再按标准的五步流程调用liq_attr_create→liq_image_create_rgba→liq_image_quantize→liq_set_dithering_level(quantization_result, 1.0)→liq_write_remapped_image→liq_get_palette,最后把 8 位像素与调色板交给 lodepng 编码成索引色 PNG。这段代码与仓库内 example.c 的结构几乎一致,是官方文档流程在真实产品中的直接印证。
十一、许可与版本
libimagequant 采用双重许可:
- 自由/开源软件:GPL v3 或更高版本,附带对早期代码部分额外的版权声明(见仓库 COPYRIGHT,代码版权归属于 Kornel Lesiński、Jef Poskanzer、Greg Roelofs 等人);
- 闭源软件、AppStore 分发及其他非 GPL 用途:需获取商业许可。
版本演进可参考 CHANGELOG:2.12 引入liq_histogram_add_fixed_color()并加速大图处理;2.11 引入liq_image_set_background()与liq_image_set_importance_map();2.8 起成为独立版本并加入 Java JNI 接口与多图共享调色板 API;2.5 起使用 vantage point tree 颜色搜索;2.2 起支持 OpenMP 加速。
十二、快速上手指南总结
- 构建:Unix 下
make static(BSD 用gmake),或./configure --prefix=/usr && make libimagequant.so && make install; - 最小集成:
liq_attr_create()建配置 →liq_image_create_rgba()(或_rgba_rows/_custom)建图 → 可选liq_set_quality()/liq_set_speed()等调参 →liq_image_quantize()生成liq_result→liq_write_remapped_image()输出 8 位像素 →liq_get_palette()取调色板(复制后再销毁 result)→ 依次liq_result_destroy/liq_image_destroy/liq_attr_destroy; - 多图共用调色板:
liq_histogram_create→ 逐图liq_histogram_add_image→liq_histogram_quantize; - 动画/GIF:预处理 alpha 为 0 或 255,并用
liq_image_set_background()设置上一帧为背景。
记住两条铁律:先复制调色板再销毁 result;像素数据在liq_image_destroy()前不得改动。基于这些 API 与源码细节,你可以放心地把 libimagequant 集成进自己的工具链,复现 pngquant 级别的量化质量。
- 固件
- 操作系统
【免费下载链接】CloverBootloader
Bootloader for macOS, Windows and Linux in UEFI and in legacy mode
相关推荐
CloverBootloader 中的 libimagequant 图像量化库:RGBA 转 8 位索引色 PNG 的 C 库实践指南
CloverBootloader 中的 libimagequant 图像量化库:RGBA 转 8 位索引色 PNG 的 C 库实践指南 libimagequan
固件操作系统Clover 主题管理器中的 libimagequant Rust 绑定:RGBA 图像到 8 位调色板量化指南
Clover 主题管理器中的 libimagequant Rust 绑定:RGBA 图像到 8 位调色板量化指南 libimagequant 是一个小巧、可移植
固件操作系统图像量化终极指南:用libimagequant实现高质量图片压缩的完整解析
图像量化终极指南:用libimagequant实现高质量图片压缩的完整解析 你是否曾为网页加载速度慢而烦恼?是否希望让应用中的图片体积更小却不损失质量?这就是图
音视频
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考