文章目录 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 && 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; } 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 ;
}