压缩项目是仅用于Windows平台的,主要使用gdi+方案,如果需要跨平台建议用opencv。本项目目前处理图片的类型只有jpg和png。

首先是jpg方面,jpg图片没有透明通道

png图片可能有透明通道,有函数可以检查这方面的信息,本项目简化了这一部分操作,统一用24rgb参数压缩(8r8g8b),jpg的缩放质量参数是0-100,计算好后新宽高注意要/100。png没有缩放质量参数。压缩使用的是高质量双三次插值模式(InterpolationModeHighQualityBicubic),每个像素点根据周围4*4共计16个像素点进行插值计算。

图片压缩函数

static bool compressImage(std::string_view input_path, const std::string_view output_path, int quality,
                              IMG_TYPE type, int scale) {
        using namespace Gdiplus;
        spdlog::debug("Compress image: {}--{}", input_path, output_path);

        // 初始化GDI+
        GdiplusStartupInput const gdiplus_startup_input{};
        ULONG_PTR gdiplus_token = 0;
        GdiplusStartup(&gdiplus_token, &gdiplus_startup_input, nullptr);

        Image* image = Image::FromFile(CommonTool::charToWchar(input_path).c_str());
        if (image == nullptr || image->GetLastStatus() != Ok) {
            GdiplusShutdown(gdiplus_token);
            return false;
        }

        UINT const original_width = image->GetWidth();
        UINT const original_height = image->GetHeight();
        INT const new_width = static_cast<INT>(original_width * scale) / 100;
        INT const new_height = static_cast<INT>(original_height * scale) / 100;

        auto* scaled_bitmap = new Bitmap(new_width, new_height, PixelFormat24bppRGB);
        Graphics* graphics = Graphics::FromImage(scaled_bitmap);
        graphics->SetInterpolationMode(InterpolationModeHighQualityBicubic);
        graphics->DrawImage(image, 0, 0, new_width, new_height);

        CLSID encoder_clsid;
        if (type == IT_PNG) {
            getEncoderClsid(L"image/png", &encoder_clsid);
        } else {
            getEncoderClsid(L"image/jpeg", &encoder_clsid);
        }

        // 设置编码参数
        EncoderParameters encoder_parameters{};
        encoder_parameters.Count = 1;
        encoder_parameters.Parameter[0].Guid = EncoderQuality;
        encoder_parameters.Parameter[0].Type = EncoderParameterValueTypeLong;
        encoder_parameters.Parameter[0].NumberOfValues = 1;

        // JPEG质量范围是0-100,PNG不支持质量设置
        ULONG quality_value = (type == IT_PNG) ? 100 : quality;
        encoder_parameters.Parameter[0].Value = &quality_value;

        auto result =
            scaled_bitmap->Save(CommonTool::charToWchar(output_path).c_str(), &encoder_clsid, &encoder_parameters);

        delete image;
        delete scaled_bitmap;
        delete graphics;
        GdiplusShutdown(gdiplus_token);
        return result;
    }

日志记录 → 初始化GDI+ → 加载图片 → 计算缩放 → 创建Bitmap → 绘制缩放 → 获取编码器 → 设置参数 → 保存图片 → 释放资源 → 返回结果

生成指定文件大小的图片函数

有些时候压缩需要考虑到对压缩后文件大小有要求的情况,这个函数的功能就是尝试修改质量参数来使最终的文件大小尽可能接近目标大小。

主要功能和流程如下:

  1. 初始化 GDI+
    启动 GDI+ 环境,准备图片处理。

  2. 加载图片
    把输入路径的图片加载为 GDI+ 的 Image 对象。

  3. 选择编码器
    根据目标类型(PNG/JPG)选择合适的图片编码器。

  4. 目标大小转换
    把目标大小(KB)转换为字节(乘以 1024)。

  5. 初始质量设置
    根据目标大小,初步设定压缩质量(小于100KB用30,500KB用50,1MB用70,否则用90)。

  6. 二分查找压缩质量
    最多尝试10次,通过调整压缩质量,反复保存临时图片并检测文件大小,直到接近目标大小。

  7. 保存最终图片
    找到合适质量后,把临时文件移动为最终输出文件。

  8. 清理资源
    释放图片对象和 GDI+ 资源,删除临时文件。

static bool compressImageBySize(const std::string& input_path, const std::string& output_path, int target_size,
                                    IMG_TYPE type) {
        using namespace Gdiplus;

        // 初始化GDI+
        GdiplusStartupInput gdiplusStartupInput;
        ULONG_PTR gdiplusToken;
        GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr);

        // 加载图像
        auto* wideInputPath = new wchar_t[input_path.length() + 1];
        size_t convertedChars = 0;
        mbstowcs_s(&convertedChars, wideInputPath, input_path.length() + 1, input_path.c_str(), _TRUNCATE);

        Image* image = Image::FromFile(wideInputPath);
        if (image == nullptr || image->GetLastStatus() != Ok) {
            delete[] wideInputPath;
            GdiplusShutdown(gdiplusToken);
            return false;
        }

        delete[] wideInputPath;

        // 获取编码器CLSID
        CLSID encoderClsid;
        if (type == IT_JPG) {
            getEncoderClsid(L"image/jpeg", &encoderClsid);
        } else if (type == IT_PNG) {
            getEncoderClsid(L"image/png", &encoderClsid);
        } else {
            // 默认使用JPEG格式
            getEncoderClsid(L"image/jpeg", &encoderClsid);
            type = IT_JPG;
        }

        target_size *= 1024;
        uint32_t quality = 90;
        if (target_size < static_cast<uint64_t>(100 * 1024)) {  // 小于100KB
            quality = 30;
        } else if (target_size < static_cast<uint64_t>(500 * 1024)) {  // 小于500KB
            quality = 50;
        } else if (target_size < static_cast<long long>(1024) * 1024) {  // 小于1MB
            quality = 70;
        }

        // 通过二分查找找到合适的质量值
        uint32_t min_quality = 1;
        uint32_t max_quality = 100;

        // 创建临时文件路径用于测试
        std::string const temp_path = output_path + ".tmp";

        for (int i = 0; i < 10; i++) {  // 最多尝试10次
            // 设置编码参数
            EncoderParameters encoderParameters{};
            encoderParameters.Count = 1;
            encoderParameters.Parameter[0].Guid = EncoderQuality;
            encoderParameters.Parameter[0].Type = EncoderParameterValueTypeLong;
            encoderParameters.Parameter[0].NumberOfValues = 1;

            ULONG qualityValue = (type == IT_PNG) ? 100 : quality;
            encoderParameters.Parameter[0].Value = &qualityValue;

            // 保存图像到临时文件
            Status status = image->Save(CommonTool::charToWchar(temp_path).c_str(), &encoderClsid, &encoderParameters);

            if (status != Ok) {
                // 保存失败,降低质量再试
                max_quality = quality - 1;
                quality = (min_quality + max_quality) / 2;
                continue;
            }

            // 检查文件大小
            WIN32_FILE_ATTRIBUTE_DATA file_data;
            if (GetFileAttributesExW(CommonTool::charToWchar(temp_path).c_str(), GetFileExInfoStandard, &file_data)) {
                ULARGE_INTEGER file_size;
                file_size.LowPart = file_data.nFileSizeLow;
                file_size.HighPart = file_data.nFileSizeHigh;

                if (file_size.QuadPart <= target_size) {
                    std::string path = "\"";
                    path += output_path;
                    path += "\"";

                    MoveFileW(CommonTool::charToWchar(temp_path).c_str(), CommonTool::charToWchar(path).c_str());

                } else {
                    // 文件太大,降低质量
                    max_quality = quality - 1;
                }
            } else {
                max_quality = quality - 1;
            }

            // 调整质量值
            if (max_quality < min_quality) {
                // 已经是最低质量,保存并退出
                auto* wide_temp_path_for_final = new wchar_t[temp_path.length() + 1];
                mbstowcs_s(&convertedChars, wide_temp_path_for_final, temp_path.length() + 1, temp_path.c_str(),
                           _TRUNCATE);

                auto* wideOutputPathForFinal = new wchar_t[output_path.length() + 1];
                mbstowcs_s(&convertedChars, wideOutputPathForFinal, output_path.length() + 1, output_path.c_str(),
                           _TRUNCATE);

                MoveFileW(wide_temp_path_for_final, wideOutputPathForFinal);
                delete[] wide_temp_path_for_final;
                delete[] wideOutputPathForFinal;
                break;
            }

            quality = (min_quality + max_quality) / 2;
        }

        // 清理资源
        delete image;
        GdiplusShutdown(gdiplusToken);

        // 删除临时文件(如果存在)
        DeleteFileA(temp_path.c_str());
        return true;
    }

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐