文章目录
- image_scale.hpp
- image_scale.cpp
- main
image_scale.hpp
#ifndef IMAGE_SCALE_HPP
#define IMAGE_SCALE_HPP#include <vector>
#include <cstdint>
#include <utility>
#include <algorithm>
#include <string>
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 );
Image read_jpeg(const std::string& path);
std::vector<uint8_t> encode_jpeg(const Image& img, int quality );
void save_jpeg(const std::string& path, const Image& img, int quality );#endif
image_scale.cpp
#include "image_scale.hpp"
#include <cmath>
#include <algorithm>
#include <stdexcept>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) {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;}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 }; 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;if (target_dpi > 0) {auto dpi_size = calculate_target_size(src, target_dpi);dst_width = dpi_size.first;dst_height = dpi_size.second;}if (method == ScaleMethod::Pyramid &&(dst_width < src.width || dst_height < src.height)){Image current = src;while (current.width / 2 >= dst_width &¤t.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;}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 <turbojpeg.h>
#include <fstream>
#include <vector>
#include <memory>
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) {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);}TJHandle jpeg(tjInitDecompress());if (!jpeg) throw std::runtime_error("TurboJPEG init failed: " + std::string(tjGetErrorStr()));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()));}const int pixel_format = TJPF_RGB; const int pixel_size = tjPixelSize[pixel_format];Image img(width, height, pixel_size);if (tjDecompress2(jpeg.get(),jpeg_data.data(), jpeg_data.size(),img.data.data(), width, 0, height,pixel_format,TJFLAG_FASTDCT | TJFLAG_NOREALLOC ) != 0 ) {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");}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");}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, quality,TJFLAG_ACCURATEDCT ) != 0) {throw std::runtime_error("JPEG compression failed: " + std::string(tjGetErrorStr()));}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 <iostream>
#include <string>int main() {try {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";Image nearest = scale_image(original,{ original.width / 2, original.height / 2 },-1.0f, ScaleMethod::Nearest);Image bilinear_400x300 = scale_image(original,{ 400, 300 },-1.0f,ScaleMethod::Bilinear);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";Image pyramid_quarter = scale_image(original,{ original.width / 4, original.height / 4 },-1.0f,ScaleMethod::Pyramid);save_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;
}