基础压缩 (compress_png
):
优化压缩 (compress_png_optimized
):
极限压缩 (compress_png_max_compression
):
现代C++实现(使用C++17特性)
RAII资源管理(智能指针管理资源)
多阶段压缩管道:
支持的功能:
压缩率比较:
处理速度:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "libimagequant.h"
#include "png_manager.hpp"
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "advapi32.lib")
#pragma comment(lib, "userenv.lib")
#pragma comment(lib, "ntdll.lib")
// 自定义删除器用于 liq_attr 指针
struct LiqAttrDeleter {
void operator()(liq_attr* attr) const {
if (attr) liq_attr_destroy(attr);
}
};
// 自定义删除器用于 liq_image 指针
struct LiqImageDeleter {
void operator()(liq_image* image) const {
if (image) liq_image_destroy(image);
}
};
// 自定义删除器用于 liq_result 指针
struct LiqResultDeleter {
void operator()(liq_result* res) const {
if (res) liq_result_destroy(res);
}
};
// 使用现代 C++ 类型别名
using UniqueLiqAttr = std::unique_ptr<liq_attr, LiqAttrDeleter>;
using UniqueLiqImage = std::unique_ptr<liq_image, LiqImageDeleter>;
using UniqueLiqResult = std::unique_ptr<liq_result, LiqResultDeleter>;
// PNG 内存读取结构
struct MemoryReaderState {
const unsigned char* data;
size_t size;
size_t offset;
};
// PNG 内存写入结构
struct MemoryWriterState {
std::vector<unsigned char>* buffer;
};
// 扩展 PngManager 类以支持索引图像
class ExtendedPngManager : public PngManager {
public:
// 写入索引图像到内存
PngManager::ErrorCode WriteIndexedToMemory(
uint32_t width, uint32_t height,
const std::vector<uint8_t>& indexes, // 索引数据,大小为 width * height
const std::vector<png_color>& palette, // 调色板
const std::vector<uint8_t>& trans, // 调色板的透明度(可选)
std::vector<uint8_t>& outBuffer,
int compressionLevel = 6)
{
if (indexes.size() != width * height) {
return PngManager::ErrorCode::InvalidParameters;
}
if (palette.empty() || palette.size() > 256) {
return PngManager::ErrorCode::InvalidParameters;
}
// 初始化写入结构
png_structp pngPtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (!pngPtr) {
return PngManager::ErrorCode::CreateWriteStructFailed;
}
png_infop infoPtr = png_create_info_struct(pngPtr);
if (!infoPtr) {
png_destroy_write_struct(&pngPtr, nullptr);
return PngManager::ErrorCode::CreateInfoStructFailed;
}
// 错误处理设置
if (setjmp(png_jmpbuf(pngPtr))) {
png_destroy_write_struct(&pngPtr, &infoPtr);
return PngManager::ErrorCode::PngProcessingError;
}
// 设置内存写入回调
struct WriteContext {
std::vector<uint8_t>* buffer;
} ctx{ &outBuffer };
png_set_write_fn(pngPtr, &ctx, [](png_structp pngPtr, png_bytep data, png_size_t length) {
auto* ctx = static_cast<WriteContext*>(png_get_io_ptr(pngPtr));
size_t oldSize = ctx->buffer->size();
ctx->buffer->resize(oldSize + length);
memcpy(ctx->buffer->data() + oldSize, data, length);
}, nullptr);
// 设置压缩级别
png_set_compression_level(pngPtr, compressionLevel);
// 设置IHDR:索引模式
png_set_IHDR(pngPtr, infoPtr, width, height, 8,
PNG_COLOR_TYPE_PALETTE, // 索引颜色
PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT);
// 设置调色板
png_color paletteArray[256];
int paletteSize = static_cast<int>(palette.size());
if (paletteSize > 256) paletteSize = 256;
for (int i = 0; i < paletteSize; i++) {
paletteArray[i] = palette[i];
}
png_set_PLTE(pngPtr, infoPtr, paletteArray, paletteSize);
// 设置透明度(tRNS)块
if (!trans.empty()) {
// 如果trans的大小小于调色板大小,则只设置前面的部分
int transSize = static_cast<int>(trans.size()) < paletteSize ? static_cast<int>(trans.size()) : paletteSize;
png_byte transArray[256];
for (int i = 0; i < transSize; i++) {
transArray[i] = trans[i];
}
png_set_tRNS(pngPtr, infoPtr, transArray, transSize, nullptr);
}
// 写入信息头
png_write_info(pngPtr, infoPtr);
// 准备行指针
std::vector<png_bytep> rowPointers(height);
for (uint32_t y = 0; y < height; ++y) {
// 注意:indexes是一维数组,按行存储
// 由于indexes是const,但libpng要求非const指针,所以需要const_cast。但注意,libpng不会修改数据。
rowPointers[y] = const_cast<png_bytep>(&indexes[y * width]);
}
// 写入图像数据
png_write_image(pngPtr, rowPointers.data());
png_write_end(pngPtr, nullptr);
// 清理
png_destroy_write_struct(&pngPtr, &infoPtr);
return PngManager::ErrorCode::Success;
}
// 写入索引图像到文件
PngManager::ErrorCode WriteIndexedToFile(
uint32_t width, uint32_t height,
const std::vector<uint8_t>& indexes,
const std::vector<png_color>& palette,
const std::vector<uint8_t>& trans,
const std::filesystem::path& path,
int compressionLevel = 6)
{
std::vector<uint8_t> outBuffer;
ErrorCode err = WriteIndexedToMemory(width, height, indexes, palette, trans, outBuffer, compressionLevel);
if (err != ErrorCode::Success) {
return err;
}
// 将内存数据写入文件
std::ofstream file(path, std::ios::binary);
if (!file) {
return ErrorCode::PngProcessingError;
}
file.write(reinterpret_cast<const char*>(outBuffer.data()), outBuffer.size());
return ErrorCode::Success;
}
};
// 从文件读取数据到内存
std::vector<uint8_t> read_file_to_memory(const std::filesystem::path& path) {
std::ifstream file(path, std::ios::binary | std::ios::ate);
if (!file) {
throw std::runtime_error("无法打开文件: " + path.string());
}
// 获取文件大小
const size_t file_size = file.tellg();
file.seekg(0);
// 读取整个文件到内存
std::vector<uint8_t> buffer(file_size);
if (!file.read(reinterpret_cast<char*>(buffer.data()), file_size)) {
throw std::runtime_error("读取文件失败: " + path.string());
}
return buffer;
}
// 压缩 PNG 图像的函数
bool compress_png(const std::filesystem::path& input_path,
const std::filesystem::path& output_path,
int max_colors = 256,
float dither_level = 1.0f) {
try {
ExtendedPngManager pngManager;
// 1. 读取输入文件到内存
std::vector<uint8_t> fileContent = read_file_to_memory(input_path);
// 2. 使用PngManager读取PNG数据
PngManager::ImageData imageData;
auto error = pngManager.ReadFromMemory(fileContent.data(), fileContent.size(), imageData);
if (error != PngManager::ErrorCode::Success) {
throw std::runtime_error("Failed to read PNG file: " + std::to_string(static_cast<int>(error)));
}
int width = static_cast<int>(imageData.width);
int height = static_cast<int>(imageData.height);
int channels = (imageData.format == PngManager::PixelFormat::RGBA) ? 4 :
(imageData.format == PngManager::PixelFormat::RGB) ? 3 :
(imageData.format == PngManager::PixelFormat::GrayscaleAlpha) ? 2 : 1;
std::cout << "Processing image: " << width << " x " << height << " pixels"
<< ", Channels: " << channels << "\n";
// 3. 创建 liq_attr 对象
UniqueLiqAttr attr(liq_attr_create());
if (!attr) {
throw std::runtime_error("Failed to create liq_attr object");
}
// 设置量化参数
liq_set_max_colors(attr.get(), max_colors);
liq_set_speed(attr.get(), 5); // 中等速度预设
// 对于RGBA图像(带透明度),设置最小不透明度
if (channels == 4) {
liq_set_min_opacity(attr.get(), 0); // 保留全透明像素
}
// 4. 创建图像对象
UniqueLiqImage image(liq_image_create_rgba(attr.get(), imageData.pixels.data(), width, height, 0));
if (!image) {
throw std::runtime_error("Failed to create image object");
}
// 5. 执行量化
liq_result* raw_result = liq_quantize_image(attr.get(), image.get());
if (!raw_result) {
throw std::runtime_error("Quantization failed: liq_quantize_image returned null");
}
UniqueLiqResult result(raw_result);
// 6. 设置抖动水平
liq_set_dithering_level(result.get(), dither_level);
// 7. 准备索引图像
std::vector<uint8_t> indexed_data(width * height);
liq_error remap_err = liq_write_remapped_image(result.get(), image.get(), indexed_data.data(), indexed_data.size());
if (remap_err != LIQ_OK) {
throw std::runtime_error("Remapping image failed: " + std::to_string(remap_err));
}
// 8. 获取调色板
const liq_palette* palette_ptr = liq_get_palette(result.get());
if (!palette_ptr) {
throw std::runtime_error("Failed to get palette");
}
// 9. 检查调色板透明度
bool has_transparency = false;
if (channels == 4) { // 仅当原始图像有透明度时才检查
for (int i = 0; i < palette_ptr->count; i++) {
if (palette_ptr->entries[i].a < 255) {
has_transparency = true;
break;
}
}
if (has_transparency) {
std::cout << "Preserved transparency in palette\n";
}
}
// 10. 准备调色板和透明度数据
std::vector<png_color> pngPalette;
std::vector<uint8_t> trans;
for (int i = 0; i < palette_ptr->count; i++) {
png_color c;
c.red = palette_ptr->entries[i].r;
c.green = palette_ptr->entries[i].g;
c.blue = palette_ptr->entries[i].b;
pngPalette.push_back(c);
// 如果图像有透明度,才收集trans数据
if (has_transparency) {
trans.push_back(palette_ptr->entries[i].a);
}
}
if (!has_transparency) {
trans.clear();
}
// 11. 保存索引图像
error = pngManager.WriteIndexedToFile(width, height, indexed_data, pngPalette, trans, output_path);
if (error != PngManager::ErrorCode::Success) {
throw std::runtime_error("Failed to write indexed PNG: " + std::to_string(static_cast<int>(error)));
}
// 12. 计算压缩率
size_t original_size = fileContent.size();
size_t compressed_size = std::filesystem::file_size(output_path);
double reduction = (original_size > 0) ? 100.0 * (1.0 - static_cast<double>(compressed_size) / original_size) : 0.0;
std::cout << "Successfully compressed image ("
<< original_size << " bytes -> " << compressed_size << " bytes, "
<< std::fixed << std::setprecision(1) << reduction << "% reduction)\n"
<< "Saved as: " << output_path << std::endl;
return true;
}
catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
return false;
}
}
bool compress_png_optimized(const std::filesystem::path& input_path,
const std::filesystem::path& output_path,
int max_colors = 128,
float dither_level = 0.7f) {
try {
ExtendedPngManager pngManager;
std::vector<uint8_t> fileContent = read_file_to_memory(input_path);
PngManager::ImageData imageData;
auto error = pngManager.ReadFromMemory(fileContent.data(), fileContent.size(), imageData);
if (error != PngManager::ErrorCode::Success) {
throw std::runtime_error("Failed to read PNG file");
}
int width = static_cast<int>(imageData.width);
int height = static_cast<int>(imageData.height);
int channels = (imageData.format == PngManager::PixelFormat::RGBA) ? 4 : 3;
// 1. 创建并配置liq_attr
UniqueLiqAttr attr(liq_attr_create());
liq_set_max_colors(attr.get(), max_colors);
liq_set_speed(attr.get(), 1); // 最慢但质量最好
liq_set_quality(attr.get(), 0, 30); // 质量范围
if (channels == 4) {
liq_set_min_opacity(attr.get(), 0);
}
// 2. 创建图像并量化
UniqueLiqImage image(liq_image_create_rgba(attr.get(), imageData.pixels.data(), width, height, 0));
UniqueLiqResult result(liq_quantize_image(attr.get(), image.get()));
liq_set_dithering_level(result.get(), dither_level);
// 3. 准备输出数据
std::vector<uint8_t> indexed_data(width * height);
liq_write_remapped_image(result.get(), image.get(), indexed_data.data(), indexed_data.size());
// 4. 获取并优化调色板
const liq_palette* palette_ptr = liq_get_palette(result.get());
std::vector<png_color> pngPalette;
std::vector<uint8_t> trans;
for (int i = 0; i < palette_ptr->count; i++) {
png_color c = { palette_ptr->entries[i].r, palette_ptr->entries[i].g, palette_ptr->entries[i].b };
pngPalette.push_back(c);
if (channels == 4) trans.push_back(palette_ptr->entries[i].a);
}
// 5. 使用最高压缩级别写入
error = pngManager.WriteIndexedToFile(width, height, indexed_data, pngPalette, trans, output_path, 9);
// 报告压缩结果
size_t original_size = fileContent.size();
size_t compressed_size = std::filesystem::file_size(output_path);
double reduction = 100.0 * (1.0 - static_cast<double>(compressed_size) / original_size);
std::cout << "Optimized compression: " << reduction << "% reduction\n";
return true;
}
catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
return false;
}
}
#include
#include
#include
bool compress_png_max_compression(const std::filesystem::path& input_path,
const std::filesystem::path& output_path,
int max_colors = 64, // 更少的颜色数量
float dither_level = 0.5f) // 更低的抖动级别
{
try {
ExtendedPngManager pngManager;
// 1. 读取输入文件
std::vector<uint8_t> fileContent = read_file_to_memory(input_path);
PngManager::ImageData imageData;
auto error = pngManager.ReadFromMemory(fileContent.data(), fileContent.size(), imageData);
if (error != PngManager::ErrorCode::Success) {
throw std::runtime_error("Failed to read PNG file");
}
const int width = static_cast<int>(imageData.width);
const int height = static_cast<int>(imageData.height);
const int channels = (imageData.format == PngManager::PixelFormat::RGBA) ? 4 : 3;
// 2. 预处理图像 - 减少颜色变化
if (channels >= 3) {
auto& pixels = imageData.pixels;
const float color_reduction = 0.9f; // 颜色减少强度 (0.0-1.0)
for (size_t i = 0; i < pixels.size(); i += channels) {
// 对RGB通道进行轻微的颜色量化
for (int c = 0; c < 3; c++) {
float val = pixels[i + c];
val = std::round(val / (color_reduction * 10 + 5)) * (color_reduction * 10 + 5);
pixels[i + c] = static_cast<uint8_t>(std::clamp(val, 0.0f, 255.0f));
}
}
}
// 3. 配置量化参数 - 优化压缩
UniqueLiqAttr attr(liq_attr_create());
liq_set_max_colors(attr.get(), max_colors); // 更少的颜色
liq_set_speed(attr.get(), 1); // 最慢但质量最好
liq_set_quality(attr.get(), 0, 30); // 更低的质量范围
liq_set_min_posterization(attr.get(), 1); // 减少色带
if (channels == 4) {
liq_set_min_opacity(attr.get(), 15); // 合并接近透明的像素
}
// 4. 创建图像并量化
UniqueLiqImage image(liq_image_create_rgba(attr.get(), imageData.pixels.data(), width, height, 0));
UniqueLiqResult result(liq_quantize_image(attr.get(), image.get()));
liq_set_dithering_level(result.get(), dither_level); // 更少的抖动
// 5. 准备输出数据
std::vector<uint8_t> indexed_data(width * height);
liq_write_remapped_image(result.get(), image.get(), indexed_data.data(), indexed_data.size());
// 6. 获取并优化调色板
const liq_palette* palette_ptr = liq_get_palette(result.get());
std::vector<png_color> pngPalette;
std::vector<uint8_t> trans;
// 合并相似颜色
const int color_tolerance = 8; // 颜色相似度容差
for (int i = 0; i < palette_ptr->count; ) {
png_color current = {
palette_ptr->entries[i].r,
palette_ptr->entries[i].g,
palette_ptr->entries[i].b
};
// 尝试合并相似颜色
bool merged = false;
for (auto& existing : pngPalette) {
if (abs(existing.red - current.red) < color_tolerance &&
abs(existing.green - current.green) < color_tolerance &&
abs(existing.blue - current.blue) < color_tolerance) {
merged = true;
break;
}
}
if (!merged) {
pngPalette.push_back(current);
if (channels == 4) {
trans.push_back(palette_ptr->entries[i].a);
}
}
i++;
}
// 7. 使用最高压缩参数写入
error = pngManager.WriteIndexedToFile(
width, height,
indexed_data,
pngPalette,
trans,
output_path,
9 // 最高压缩级别
);
if (error != PngManager::ErrorCode::Success) {
throw std::runtime_error("Failed to write compressed PNG");
}
// 报告压缩结果
size_t original_size = fileContent.size();
size_t compressed_size = std::filesystem::file_size(output_path);
double reduction = 100.0 * (1.0 - static_cast<double>(compressed_size) / original_size);
std::cout << "Max compression: " << reduction << "% reduction ("
<< original_size << " bytes -> " << compressed_size << " bytes)\n";
return true;
}
catch (const std::runtime_error& e) {
std::cerr << "Compression error: " << e.what() << std::endl;
return false;
}
catch (...) {
std::cerr << "Unknown error during compression" << std::endl;
return false;
}
}
int main() {
const std::filesystem::path input_path = "D:\\png_compress\\test.png";
const std::filesystem::path output_path = "D:\\png_compress\\test_compresseded.png";
const std::filesystem::path output_optimized_path = "D:\\png_compress\\test_compresseded_optimized.png";
const std::filesystem::path output_max_optimized_path = "D:\\png_compress\\test_compresseded_max_optimized.png";
// 检查输入文件是否存在
if (!std::filesystem::exists(input_path)) {
std::cerr << "Input file does not exist: " << input_path << std::endl;
return 1;
}
// 执行压缩(启用透明度保护和中等抖动)
if (!compress_png(input_path, output_path, 256, 0.8f)) {
std::cerr << "Image compression failed" << std::endl;
return 1;
}
// 执行压缩(启用透明度保护和中等抖动)
if (!compress_png_optimized(input_path, output_optimized_path, 256, 0.8f)) {
std::cerr << "Image compression failed" << std::endl;
return 1;
}
// 执行压缩(启用透明度保护和中等抖动)
if (!compress_png_max_compression(input_path, output_max_optimized_path, 64, 0.8f)) {
std::cerr << "Image compression failed" << std::endl;
return 1;
}
return 0;
}