I have two images with the same pixel dimensions: org.jpg and temple.jpg. I would like to accomplish the following:
- Compress org.jpg using the compression fingerprint of temple.jpg, and generate a new image called target.jpg.
- Ensure that the DQT (quantization table), SOF (start of frame), and DHT (Huffman table) information, including hex codes(from FFD8 to FFDA), in target.jpg are identical to those in temple.jpg.
I have already made sure that the pixel dimensions of org.jpg and temple.jpg are the same.
I have also conducted detailed tests, and the results are as follows:
1.Before saving the image, the Huffman and Quantization tables of "target.jpg" have been confirmed to be consistent with those of "temple.jpg".
2.If temple.jpg itself is saved in the IJG standard, I can directly copy the code between FFD8 and FFDA from "temple.jpg" to replace that of "target.jpg" without any errors. However, if the compression of "temple.jpg" itself is not IJG standard, "target.jpg" can be saved, but directly copying the code between FFD8 and FFDA from "temple.jpg" to replace that of "target.jpg" will cause the image to be corrupted.
3.When I analyze the generated "target.jpg" image with libjpeg, the Quantization tables and Huffman table are the same with "temple.jpg", but when I directly look at the hex codes of the two images using a hex editor, they are not consistent.
here is the code I tried :
#include <iostream>
#include <string>
#include <cstdio>
#include <jpeglib.h>
#include <jerror.h>
#include <algorithm>
#include <fstream>
#include <iterator>
#include <vector>
// Manually copy quantization table
void copy_quant_table(JQUANT_TBL* src, JQUANT_TBL* dst) {
for (int i = 0; i < DCTSIZE2; i++) {
dst->quantval[i] = src->quantval[i];
}
}
// Manually copy huffman table
void copy_huff_table(JHUFF_TBL* src, JHUFF_TBL* dst) {
for (int i = 0; i < 256; i++) {
dst->bits[i] = src->bits[i];
dst->huffval[i] = src->huffval[i];
}
}
void custom_copy_critical_parameters(j_decompress_ptr srcinfo, j_compress_ptr dstinfo) {
dstinfo->image_width = srcinfo->image_width;
dstinfo->image_height = srcinfo->image_height;
dstinfo->input_components = srcinfo->num_components;
dstinfo->in_color_space = srcinfo->jpeg_color_space;
jpeg_set_defaults(dstinfo);
jpeg_set_colorspace(dstinfo, srcinfo->jpeg_color_space);
dstinfo->data_precision = srcinfo->data_precision;
dstinfo->CCIR601_sampling = srcinfo->CCIR601_sampling;
for (int ci = 0; ci < srcinfo->num_components; ci++) {
jpeg_component_info* compptr = &srcinfo->comp_info[ci];
jpeg_component_info* dstcompptr = &dstinfo->comp_info[ci];
dstcompptr->h_samp_factor = compptr->h_samp_factor;
dstcompptr->v_samp_factor = compptr->v_samp_factor;
}
}
void replace_ffda_hex_code(const std::string& ref_filename, const std::string& output_filename) {
// 打开参考文件和输出文件
std::ifstream ref_file(ref_filename, std::ios::binary);
std::ifstream output_file(output_filename, std::ios::binary);
if (!ref_file || !output_file) {
std::cerr << "Error opening files for hex code replacement." << std::endl;
exit(1);
}
// 寻找FFDA标记
const uint8_t marker_ff = 0xFF;
const uint8_t marker_da = 0xDA;
auto find_ffda_marker = [](std::ifstream& file) -> std::streampos {
uint8_t byte1, byte2;
byte1 = file.get();
byte2 = file.get();
while (!file.eof()) {
if (byte1 == marker_ff && byte2 == marker_da) {
return file.tellg() - std::streamoff(2);
}
byte1 = byte2;
byte2 = file.get();
}
return std::streampos(-1);
};
// 在两个文件中找到FFDA标记的位置
std::streampos ref_ffda_pos = find_ffda_marker(ref_file);
std::streampos output_ffda_pos = find_ffda_marker(output_file);
if (ref_ffda_pos == std::streampos(-1) || output_ffda_pos == std::streampos(-1)) {
std::cerr << "FFDA marker not found in one of the files." << std::endl;
exit(1);
}
// 创建临时文件以保存修改后的输出文件内容
std::ofstream temp_file("temp_output.jpg", std::ios::binary);
if (!temp_file) {
std::cerr << "Error creating temporary file." << std::endl;
exit(1);
}
// 将参考文件的FFDA之前的内容复制到临时文件
ref_file.seekg(0, std::ios::beg);
std::copy_n(std::istreambuf_iterator<char>(ref_file), ref_ffda_pos, std::ostreambuf_iterator<char>(temp_file));
// 将输出文件的FFDA之后的内容复制到临时文件
output_file.seekg(output_ffda_pos, std::ios::beg);
temp_file << output_file.rdbuf();
// 关闭文件
ref_file.close();
output_file.close();
temp_file.close();
// 使用修改后的临时文件覆盖原始输出文件
std::remove(output_filename.c_str());
std::rename("temp_output.jpg", output_filename.c_str());
}
void Debug_output_decompress_Huffman_and_quantization(jpeg_decompress_struct& info, const std::string& name) {
std::cout << name << std::endl << " Quantization tables:" << std::endl;
for (int i = 0; i < NUM_QUANT_TBLS; i++) {
std::cout << "Table " << i << ": ";
if (info.quant_tbl_ptrs[i] != nullptr) {
for (int j = 0; j < DCTSIZE2; j++) {
std::cout << info.quant_tbl_ptrs[i]->quantval[j] << " ";
}
}
std::cout << std::endl;
}
std::cout << name << " Huffman tables:" << std::endl;
for (int i = 0; i < NUM_HUFF_TBLS; i++) {
std::cout << "Table " << i << ": ";
if (info.dc_huff_tbl_ptrs[i] != nullptr) {
std::cout << "DC Table " << i << " bits: ";
for (int j = 0; j < 17; j++) {
std::cout << (int)info.dc_huff_tbl_ptrs[i]->bits[j] << " ";
}
std::cout << " values: ";
for (int j = 0; j < 256; j++) {
std::cout << (int)info.dc_huff_tbl_ptrs[i]->huffval[j] << " ";
}
}
std::cout << std::endl;
if (info.ac_huff_tbl_ptrs[i] != nullptr) {
std::cout << "AC Table " << i << " bits: ";
for (int j = 0; j < 17; j++) {
std::cout << (int)info.ac_huff_tbl_ptrs[i]->bits[j] << " ";
}
std::cout << " values: ";
for (int j = 0; j < 256; j++) {
std::cout << (int)info.ac_huff_tbl_ptrs[i]->huffval[j] << " ";
}
}
std::cout << std::endl;
}
std::cout << "**---------------finish output---------------**" << std::endl;
std::cout << std::endl;
}
void Debug_output_compress_Huffman_and_quantization(jpeg_compress_struct& info, const std::string& name) {
std::cout << name << std::endl << " Quantization tables:" << std::endl;
for (int i = 0; i < NUM_QUANT_TBLS; i++) {
std::cout << "Table " << i << ": ";
if (info.quant_tbl_ptrs[i] != nullptr) {
for (int j = 0; j < DCTSIZE2; j++) {
std::cout << info.quant_tbl_ptrs[i]->quantval[j] << " ";
}
}
std::cout << std::endl;
}
std::cout << name << " Huffman tables:" << std::endl;
for (int i = 0; i < NUM_HUFF_TBLS; i++) {
std::cout << "Table " << i << ": ";
if (info.dc_huff_tbl_ptrs[i] != nullptr) {
std::cout << "DC Table " << i << " bits: ";
for (int j = 0; j < 17; j++) {
std::cout << (int)info.dc_huff_tbl_ptrs[i]->bits[j] << " ";
}
std::cout << " values: ";
for (int j = 0; j < 256; j++) {
std::cout << (int)info.dc_huff_tbl_ptrs[i]->huffval[j] << " ";
}
}
std::cout << std::endl;
if (info.ac_huff_tbl_ptrs[i] != nullptr) {
std::cout << "AC Table " << i << " bits: ";
for (int j = 0; j < 17; j++) {
std::cout << (int)info.ac_huff_tbl_ptrs[i]->bits[j] << " ";
}
std::cout << " values: ";
for (int j = 0; j < 256; j++) {
std::cout << (int)info.ac_huff_tbl_ptrs[i]->huffval[j] << " ";
}
}
std::cout << std::endl;
}
std::cout << "**---------------finish output---------------**" << std::endl;
std::cout << std::endl;
}
void copy_jpeg_file(const std::string& input_filename, const std::string& ref_filename, const std::string& output_filename) {
// Open input and reference files
FILE* input_file;
FILE* ref_file;
errno_t err;
err = fopen_s(&input_file, input_filename.c_str(), "rb");
err = fopen_s(&ref_file, ref_filename.c_str(), "rb");
if (!input_file || !ref_file) {
std::cerr << "Error opening files." << std::endl;
exit(1);
}
// Input image decompression
struct jpeg_decompress_struct srcinfo;
struct jpeg_error_mgr srcerr;
srcinfo.err = jpeg_std_error(&srcerr);
jpeg_create_decompress(&srcinfo);
jpeg_stdio_src(&srcinfo, input_file);
jpeg_read_header(&srcinfo, TRUE);
// Reference image decompression
struct jpeg_decompress_struct refinfo;
struct jpeg_error_mgr referr;
refinfo.err = jpeg_std_error(&referr);
jpeg_create_decompress(&refinfo);
jpeg_stdio_src(&refinfo, ref_file);
jpeg_read_header(&refinfo, TRUE);
// Output image compression
struct jpeg_compress_struct dstinfo;
struct jpeg_error_mgr dsterr;
dstinfo.err = jpeg_std_error(&dsterr);
jpeg_create_compress(&dstinfo);
FILE* output_file;
err = fopen_s(&output_file, output_filename.c_str(), "wb");
if (!output_file) {
std::cerr << "Error opening output file." << std::endl;
exit(1);
}
jpeg_stdio_dest(&dstinfo, output_file);
custom_copy_critical_parameters(&refinfo, &dstinfo);
// Set input color space for output image
dstinfo.in_color_space = srcinfo.out_color_space;
// Disable JFIF header
dstinfo.write_JFIF_header = FALSE;
dstinfo.optimize_coding = FALSE;
dstinfo.smoothing_factor = 0;
dstinfo.dct_method = JDCT_ISLOW;
dstinfo.comp_info[0].h_samp_factor = refinfo.comp_info[0].h_samp_factor;
dstinfo.comp_info[0].v_samp_factor = refinfo.comp_info[0].v_samp_factor;
dstinfo.comp_info[1].h_samp_factor = refinfo.comp_info[1].h_samp_factor;
dstinfo.comp_info[1].v_samp_factor = refinfo.comp_info[1].v_samp_factor;
dstinfo.comp_info[2].h_samp_factor = refinfo.comp_info[2].h_samp_factor;
dstinfo.comp_info[2].v_samp_factor = refinfo.comp_info[2].v_samp_factor;
Debug_output_compress_Huffman_and_quantization(dstinfo, "dstinfo huff_table and quant_table before copied");
// Copy DQT、SOF and DHT
for (int i = 0; i < NUM_QUANT_TBLS; i++) {
if (refinfo.quant_tbl_ptrs[i] != nullptr) {
if (dstinfo.quant_tbl_ptrs[i] == nullptr) {
dstinfo.quant_tbl_ptrs[i] = jpeg_alloc_quant_table((j_common_ptr)&dstinfo);
}
copy_quant_table(refinfo.quant_tbl_ptrs[i], dstinfo.quant_tbl_ptrs[i]);
}
}
for (int i = 0; i < NUM_HUFF_TBLS; i++) {
if (refinfo.dc_huff_tbl_ptrs[i] != nullptr) {
if (dstinfo.dc_huff_tbl_ptrs[i] == nullptr) {
dstinfo.dc_huff_tbl_ptrs[i] = jpeg_alloc_huff_table((j_common_ptr)&dstinfo);
}
copy_huff_table(refinfo.dc_huff_tbl_ptrs[i], dstinfo.dc_huff_tbl_ptrs[i]);
}
if (refinfo.ac_huff_tbl_ptrs[i] != nullptr) {
if (dstinfo.ac_huff_tbl_ptrs[i] == nullptr) {
dstinfo.ac_huff_tbl_ptrs[i] = jpeg_alloc_huff_table((j_common_ptr)&dstinfo);
}
copy_huff_table(refinfo.ac_huff_tbl_ptrs[i], dstinfo.ac_huff_tbl_ptrs[i]);
}
}
Debug_output_decompress_Huffman_and_quantization(srcinfo, "srcinfo huff_table and quant_table after copied");
Debug_output_compress_Huffman_and_quantization(dstinfo, "dstinfo huff_table and quant_table after copied");
Debug_output_decompress_Huffman_and_quantization(refinfo, "refinfo huff_table and quant_table after copied");
// Start decompression of input image and compression of output image
jpeg_start_decompress(&srcinfo);
jpeg_start_compress(&dstinfo, TRUE);
// Allocate memory for scanlines buffer
JSAMPARRAY buffer = (*srcinfo.mem->alloc_sarray)((j_common_ptr)&srcinfo, JPOOL_IMAGE, srcinfo.output_width * srcinfo.output_components, 1);
// Copy scanlines from input image to output image
while (srcinfo.output_scanline < srcinfo.output_height) {
jpeg_read_scanlines(&srcinfo, buffer, 1);
jpeg_write_scanlines(&dstinfo, buffer, 1);
}
// Finish decompression and compression
jpeg_finish_decompress(&srcinfo);
jpeg_finish_compress(&dstinfo);
// Destroy structures and close files
jpeg_destroy_decompress(&srcinfo);
jpeg_destroy_decompress(&refinfo);
jpeg_destroy_compress(&dstinfo);
fclose(input_file);
fclose(ref_file);
fclose(output_file);
}
int main() {
std::string input_filename = "org.JPG";
std::string ref_filename = "temple.jpg";
std::string output_filename = "target.jpg";
// Copy critical parameters from temple.jpg to target.jpg
copy_jpeg_file(input_filename, ref_filename, output_filename);
//replace_ffda_hex_code(ref_filename, output_filename);
return 0;
}
The code above has already generated the target.jpg, but in fact, after comparison, the HEX code between target.jpg and temple.jpg is still different. I am not sure what caused this difference. Did I do something wrong somewhere?
Two image from the same camare,there is a compare of two image from the same camera, they has the same compreess algorithm.and the same hex code (from FFD8 to FFDA)
output result,this is a compare of "target.jpg" and "temple.jpg" , the hex code with two image are not the same , that is whay i ask the question.