> 技术文档 > 金字塔降低采样

金字塔降低采样


文章目录

    • image_scale.hpp
    • image_scale.cpp
    • main

image_scale.hpp

#ifndef IMAGE_SCALE_HPP#define IMAGE_SCALE_HPP#include #include #include  // for std::pair#include  #include enum class ScaleMethod { Nearest, // 最近邻插值 Bilinear, // 双线性插值 Bicubic, // 双三次插值 Pyramid // 金字塔降采样};struct Image { std::vector<uint8_t> data; int width = 0; int height = 0; int channels = 0; float dpi = 0.0f; Image(int w, int h, int c, float d = 0.0f) : width(w), height(h), channels(c), dpi(d), data(w* h* c) { } // 安全获取像素(带边界检查) uint8_t get_pixel(int x, int y, int c) const { x = std::clamp(x, 0, width - 1); y = std::clamp(y, 0, height - 1); return data[(y * width + x) * channels + c]; }};Image scale_image(const Image& src, std::pair<int, int> dst_size, float target_dpi , ScaleMethod method );/** * 从 JPEG 文件读取图像数据(使用 TurboJPEG) * @param path JPEG 文件路径 * @param dst_dpi 目标DPI(若<=0 则使用文件默认DPI) * @return Image 结构(数据自动管理) * @throws std::runtime_error 读取失败时抛出 */Image read_jpeg(const std::string& path);/** * 将 Image 编码为 JPEG 字节流 * @param img 输入图像(支持 RGB/RGBA) * @param quality 压缩质量(1-100) * @return JPEG 二进制数据 */std::vector<uint8_t> encode_jpeg(const Image& img, int quality );/** * 将 JPEG 数据保存到文件 * @param path 输出路径 * @param img 输入图像 * @param quality 压缩质量(1-100) */void save_jpeg(const std::string& path, const Image& img, int quality );#endif // IMAGE_SCALE_HPP

image_scale.cpp

#include \"image_scale.hpp\"#include #include #include namespace { // 双三次插值核 float bicubic_kernel(float x, float B = 0.0f, float C = 0.5f) { x = std::abs(x); if (x < 1.0f) { return ((12 - 9 * B - 6 * C) * x * x * x + (-18 + 12 * B + 6 * C) * x * x + (6 - 2 * B)) / 6.0f; } else if (x < 2.0f) { return ((-B - 6 * C) * x * x * x + (6 * B + 30 * C) * x * x + (-12 * B - 48 * C) * x + (8 * B + 24 * C)) / 6.0f; } return 0.0f; } // 单次降采样(双线性) Image downscale_half(const Image& src) { if (src.width <= 1 || src.height <= 1) throw std::invalid_argument(\"Image too small for downscaling\"); Image dst(src.width / 2, src.height / 2, src.channels, src.dpi / 2.0f); for (int y = 0; y < dst.height; ++y) { for (int x = 0; x < dst.width; ++x) { for (int c = 0; c < src.channels; ++c) {  // 2x2 区域均值  float p = ( src.get_pixel(x * 2, y * 2, c) + src.get_pixel(x * 2 + 1, y * 2, c) + src.get_pixel(x * 2, y * 2 + 1, c) + src.get_pixel(x * 2 + 1, y * 2 + 1, c) ) / 4.0f;  dst.data[(y * dst.width + x) * src.channels + c] = static_cast<uint8_t>(p); } } } return dst; } // 计算基于 DPI 的目标尺寸 std::pair<int, int> calculate_target_size(const Image& src, float target_dpi) { if (target_dpi <= 0 || src.dpi <= 0) return { src.width, src.height }; // 忽略 DPI 计算 float scale = target_dpi / src.dpi; return { static_cast<int>(std::round(src.width * scale)), static_cast<int>(std::round(src.height * scale)) }; }}Image scale_image(const Image& src, std::pair<int, int> dst_size, float target_dpi, ScaleMethod method){ auto [dst_width, dst_height] = dst_size; // 1. 根据 DPI 调整目标尺寸 if (target_dpi > 0) { auto dpi_size = calculate_target_size(src, target_dpi); dst_width = dpi_size.first; dst_height = dpi_size.second; } // 2. 金字塔降采样 if (method == ScaleMethod::Pyramid && (dst_width < src.width || dst_height < src.height)) { Image current = src; // 逐级减半直到接近目标尺寸 while (current.width / 2 >= dst_width && current.height / 2 >= dst_height) { current = downscale_half(current); } // 最终精确缩放 if (current.width != dst_width || current.height != dst_height) { return scale_image(current, { dst_width, dst_height }, -1.0f, ScaleMethod::Bilinear); } return current; } // 3. 常规缩放 Image dst(dst_width, dst_height, src.channels, (target_dpi > 0) ? target_dpi : src.dpi * (static_cast<float>(dst_width) / src.width)); const float x_ratio = static_cast<float>(src.width - 1) / dst_width; const float y_ratio = static_cast<float>(src.height - 1) / dst_height; for (int y = 0; y < dst_height; ++y) { for (int x = 0; x < dst_width; ++x) { const float src_x = x * x_ratio; const float src_y = y * y_ratio; for (int c = 0; c < src.channels; ++c) { float pixel = 0.0f; switch (method) { case ScaleMethod::Nearest: {  int nx = static_cast<int>(src_x + 0.5f);  int ny = static_cast<int>(src_y + 0.5f);  pixel = src.get_pixel(nx, ny, c);  break; } case ScaleMethod::Bilinear: {  int x0 = static_cast<int>(src_x);  int y0 = static_cast<int>(src_y);  float dx = src_x - x0;  float dy = src_y - y0;  pixel = src.get_pixel(x0, y0, c) * (1 - dx) * (1 - dy) + src.get_pixel(x0 + 1, y0, c) * dx * (1 - dy) + src.get_pixel(x0, y0 + 1, c) * (1 - dx) * dy + src.get_pixel(x0 + 1, y0 + 1, c) * dx * dy;  break; } case ScaleMethod::Bicubic: {  int x0 = static_cast<int>(src_x) - 1;  int y0 = static_cast<int>(src_y) - 1;  float sum = 0.0f, weight_sum = 0.0f;  for (int i = 0; i < 4; ++i) { for (int j = 0; j < 4; ++j) { float wx = bicubic_kernel(src_x - (x0 + i)); float wy = bicubic_kernel(src_y - (y0 + j)); float w = wx * wy; sum += src.get_pixel(x0 + i, y0 + j, c) * w; weight_sum += w; }  }  pixel = sum / (weight_sum + 1e-8f);  break; } default:  throw std::invalid_argument(\"Unsupported scale method\"); } dst.data[(y * dst.width + x) * src.channels + c] =  static_cast<uint8_t>(std::clamp(pixel, 0.0f, 255.0f)); } } } return dst;}//#include \"jpeg_reader.hpp\"#include #include #include #include  // std::unique_ptr// 自动释放 TurboJPEG 实例的 RAII 包装器struct TJDeleter { void operator()(tjhandle h) const { if (h) tjDestroy(h); }};using TJHandle = std::unique_ptr<void, TJDeleter>;Image read_jpeg(const std::string& path) { // 1. 读取文件到内存 std::ifstream file(path, std::ios::binary | std::ios::ate); if (!file) throw std::runtime_error(\"Cannot open file: \" + path); const size_t file_size = file.tellg(); file.seekg(0); std::vector<uint8_t> jpeg_data(file_size); if (!file.read(reinterpret_cast<char*>(jpeg_data.data()), file_size)) { throw std::runtime_error(\"Failed to read file: \" + path); } // 2. 初始化 TurboJPEG TJHandle jpeg(tjInitDecompress()); if (!jpeg) throw std::runtime_error(\"TurboJPEG init failed: \" + std::string(tjGetErrorStr())); // 3. 获取图像信息(使用 tjDecompressHeader3) int width, height, subsamp, colorspace; if (tjDecompressHeader3( jpeg.get(), jpeg_data.data(), jpeg_data.size(), &width, &height, &subsamp, &colorspace) != 0 ) { throw std::runtime_error(\"JPEG header error: \" + std::string(tjGetErrorStr())); } // 5. 分配输出缓冲区(RGB 格式) const int pixel_format = TJPF_RGB; // 输出格式 const int pixel_size = tjPixelSize[pixel_format]; Image img(width, height, pixel_size); // 6. 解压图像(使用 tjDecompress3) if (tjDecompress2( jpeg.get(), jpeg_data.data(), jpeg_data.size(), img.data.data(), width, 0, height, pixel_format, TJFLAG_FASTDCT | TJFLAG_NOREALLOC // 禁止内部重分配 ) != 0 // 忽略 ROI 和元数据 ) { throw std::runtime_error(\"JPEG decompress failed: \" + std::string(tjGetErrorStr())); } return img;}// 编码实现std::vector<uint8_t> encode_jpeg(const Image& img, int quality ) { // 参数校验 if (img.data.empty() || img.width <= 0 || img.height <= 0) { throw std::runtime_error(\"Invalid image data\"); } if (quality < 1 || quality > 100) { throw std::runtime_error(\"Quality must be between 1-100\"); } // 初始化 TurboJPEG 压缩器 TJHandle jpeg(tjInitCompress()); if (!jpeg) { throw std::runtime_error(\"TurboJPEG init failed: \" + std::string(tjGetErrorStr())); } // 设置像素格式 int pixel_format; switch (img.channels) { case 1: pixel_format = TJPF_GRAY; break; case 3: pixel_format = TJPF_RGB; break; case 4: pixel_format = TJPF_RGBA; break; default: throw std::runtime_error(\"Unsupported image channels\"); } // 压缩 JPEG uint8_t* jpeg_buf = nullptr; unsigned long jpeg_size = 0; if (tjCompress2( jpeg.get(), img.data.data(), img.width, 0, img.height, pixel_format, &jpeg_buf, &jpeg_size, TJSAMP_444, // 4:4:4 色度采样(最高质量) quality, TJFLAG_ACCURATEDCT // 高精度 DCT ) != 0) { throw std::runtime_error(\"JPEG compression failed: \" + std::string(tjGetErrorStr())); } // 复制数据到 vector(TurboJPEG 需要手动释放内存) std::vector<uint8_t> result(jpeg_buf, jpeg_buf + jpeg_size); tjFree(jpeg_buf); return result;}// 保存到文件void save_jpeg(const std::string& path, const Image& img, int quality) { auto jpeg_data = encode_jpeg(img, quality); std::ofstream file(path, std::ios::binary); if (!file) throw std::runtime_error(\"Cannot open output file\"); file.write(reinterpret_cast<const char*>(jpeg_data.data()), jpeg_data.size());}

main

#include \"image_scale.hpp\"#include #include int main() {try {// ============================================// 1. 读取 JPEG 文件(不缩放)// ============================================const std::string input_path = \"C:\\\\image\\\\jpeg_image.jpg\";Image original = read_jpeg(input_path);std::cout << \"Original image: \" << original.width << \"x\" << original.height<< \" (DPI: \" << original.dpi << \")\\n\";// ============================================// 2. 缩放操作(四种方法演示)// ============================================// 2.1 最近邻缩小 2 倍Image nearest = scale_image(original,{ original.width / 2, original.height / 2 },-1.0f, // 保持原DPIScaleMethod::Nearest);// 2.2 双线性缩小到 400x300Image bilinear_400x300 = scale_image(original,{ 400, 300 },-1.0f,ScaleMethod::Bilinear);// 2.3 双三次缩放到 150 DPI(自动计算尺寸)float target_dpi = 150.0f;Image bicubic_150dpi = scale_image(original,{ 0, 0 }, // 自动计算尺寸target_dpi,ScaleMethod::Bicubic);std::cout << \"Bicubic scaled to DPI \" << target_dpi << \": \"<< bicubic_150dpi.width << \"x\" << bicubic_150dpi.height << \"\\n\";// 2.4 金字塔降采样缩小到 1/4 尺寸Image pyramid_quarter = scale_image(original,{ original.width / 4, original.height / 4 },-1.0f,ScaleMethod::Pyramid);// ============================================// 4. 编码与保存// ============================================// 4.1 保存为不同质量的 JPEGsave_jpeg(\"C:\\\\image\\\\nearest.jpg\", nearest, 95); // 高质量save_jpeg(\"C:\\\\image\\\\bilinear.jpg\", bilinear_400x300, 95);save_jpeg(\"C:\\\\image\\\\bicubi.jpg\", bicubic_150dpi, 95); // 低质量save_jpeg(\"C:\\\\image\\\\pyramid.jpg\", pyramid_quarter, 95); // 低质量std::cout << \"All operations completed successfully!\\n\";}catch (const std::exception& e) {std::cerr << \"Fatal Error: \" << e.what() << \"\\n\";return 1;}return 0;}