当前位置: 首页 > news >正文

PNG图像压缩优化工具

PNG图像压缩优化工具

标题:PNG图像三重压缩优化系统

介绍大纲

1. 工具概述

  • 基于libimagequant和libpng的高效PNG压缩工具
  • 提供三种不同级别的压缩算法
  • 支持保留透明度和色彩质量优化

2. 核心功能

  • ​基础压缩​​ (compress_png):

    • 标准量化处理
    • 中等压缩率和处理速度
    • 适合大多数常规用途
  • ​优化压缩​​ (compress_png_optimized):

    • 增强的量化参数设置
    • 更低的抖动级别
    • 更高的压缩级别(9)
    • 适合需要更好压缩率的场景
  • ​极限压缩​​ (compress_png_max_compression):

    • 激进的颜色减少预处理
    • 相似颜色合并算法
    • 最低质量范围设置(0-30)
    • 适合对文件大小极度敏感的场景

3. 技术特点

  • 现代C++实现(使用C++17特性)

  • RAII资源管理(智能指针管理资源)

  • 多阶段压缩管道:

    1. 图像读取与解码
    2. 颜色预处理(极限压缩模式)
    3. 量化与调色板优化
    4. PNG编码与压缩
  • 支持的功能:

    • 透明度保留
    • 颜色相似度合并
    • 自适应抖动控制
    • 多种PNG过滤策略

4. 性能指标

  • 压缩率比较:

    • 基础压缩:中等压缩率
    • 优化压缩:比基础高10-20%
    • 极限压缩:比基础高20-40%
  • 处理速度:

    • 基础压缩:最快
    • 优化压缩:中等
    • 极限压缩:最慢(质量优先)

5. 使用场景

  • 网页图像优化
  • 移动应用资源压缩
  • 游戏素材处理
  • 批量图像处理流水线

6. 扩展性

  • 可轻松集成到现有系统
  • 模块化设计便于添加新算法
  • 支持自定义压缩参数

7. 示例输出

  • 提供三种压缩级别的结果比较
  • 输出详细的压缩统计信息:
    • 原始大小
    • 压缩后大小
    • 压缩百分比
    • 处理时间(可选)
#include <iostream>
#include <fstream>
#include <vector>
#include <memory>
#include <filesystem>
#include <cstring>
#include <cmath>
#include <png.h>
#include <string>
#include <iomanip>
#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 * heightconst 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_attrUniqueLiqAttr 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 <algorithm>
#include <vector>
#include <cmath>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;
}
http://www.dtcms.com/a/266609.html

相关文章:

  • 钉钉小程序开发技巧:getSystemInfo 系统信息获取全解析
  • IRIV算法详解 | 变量选择的迭代保留法
  • 全星稽核管理软件系统——企业智能化稽核管理的最佳解决方案
  • zxing去白边
  • 督皇口粮酱酒 平价不平质
  • 第十五节:第三部分:特殊文件:XML概述、解析
  • C语言中的输入输出函数:构建程序交互的基石
  • Linux的压缩与解压缩
  • WPF 右键菜单 MenuItem 绑定图片时只显示最后一个 Icon
  • OpenCV 相机标定中的畸变系数及调试硬件
  • 前端渲染大量图片的首屏加载优化方案
  • 刷题笔记--串联所有单词的子串
  • [附源码+数据库+毕业论文]基于Spring+MyBatis+MySQL+Maven+jsp实现的个人财务管理系统,推荐!
  • [附源码+数据库+毕业论文]基于Spring+MyBatis+MySQL+Maven+jsp实现的电影小说网站管理系统,推荐!
  • 儿童益智玩具+AI大模型能不能原地起飞?
  • Unity URP法线贴图实现教程
  • 三、jenkins使用tomcat部署项目
  • RK-Android11-性能优化-限制App内存上限默认512m
  • 利用TCP协议,创建一个多人聊天室
  • 使用reactor-rabbitmq库监听Rabbitmq
  • Go中使用Google Authenticator
  • 东软8位MCU低功耗调试总结
  • 如何使用python识别出文件夹中全是图片合成的的PDF,并将其移动到指定文件夹
  • 【ASP.NET Core】REST与RESTful详解,从理论到实现
  • 当前主流AI智能代理框架对比分析报告
  • 分布式光伏监控系统防孤岛保护装置光功率预测
  • 【论文阅读】VARGPT-v1.1
  • Webpack构建工具
  • node.js下载教程
  • 机器学习数学基础与Python实现