#include <turbojpeg.h>
#include <iostream>
#include <fstream>
#include <vector>
#include <cstring>
#include "TinyEXIF.h"
#include "libjpeg_wrapper.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
extern "C" {
bool is_jpeg(const unsigned char* data, size_t size) {
return size >= 2 && data[0] == 0xFF && data[1] == 0xD8;
}
bool is_png(const unsigned char* data, size_t size) {
return size >= 8 && data[0] == 0x89 && data[1] == 0x50 && data[2] == 0x4E && data[3] == 0x47;
}
void init_exif_data(ExifData& exif_data) {
memset(exif_data.camera_make, 0, 64);
memset(exif_data.camera_model, 0, 64);
exif_data.software = nullptr;
exif_data.iso_speed = 0;
exif_data.shutter = 0.0;
exif_data.aperture = 0.0;
exif_data.focal_length = 0.0;
exif_data.raw_width = 0;
exif_data.raw_height = 0;
exif_data.output_width = 0;
exif_data.output_height = 0;
exif_data.colors = 3; exif_data.color_filter = 0;
for (int i = 0; i < 4; i++) {
exif_data.cam_mul[i] = 0.0;
}
exif_data.date_taken = nullptr;
exif_data.lens = nullptr;
exif_data.max_aperture = 0.0;
exif_data.focal_length_35mm = 0;
exif_data.description = nullptr;
exif_data.artist = nullptr;
}
void populate_exif_from_tinyexif(const TinyEXIF::EXIFInfo& exif_info, ExifData& exif_data) {
strncpy(exif_data.camera_make, exif_info.Make.c_str(), 63);
exif_data.camera_make[63] = '\0';
strncpy(exif_data.camera_model, exif_info.Model.c_str(), 63);
exif_data.camera_model[63] = '\0';
exif_data.iso_speed = static_cast<int>(exif_info.ISOSpeedRatings);
exif_data.shutter = exif_info.ExposureTime;
exif_data.aperture = exif_info.FNumber;
exif_data.focal_length = exif_info.FocalLength;
exif_data.max_aperture = 0.0; exif_data.focal_length_35mm = 0;
exif_data.software = nullptr;
exif_data.date_taken = nullptr;
exif_data.lens = nullptr;
exif_data.description = nullptr;
exif_data.artist = nullptr;
}
void extract_jpeg_exif(const std::vector<unsigned char>& input_data, ExifData& exif_data) {
TinyEXIF::EXIFInfo original_exif_info;
bool has_original_exif = (original_exif_info.parseFrom(input_data.data(), input_data.size()) == TinyEXIF::PARSE_SUCCESS);
if (has_original_exif) {
std::cout << "EXIF found in JPEG file" << std::endl;
populate_exif_from_tinyexif(original_exif_info, exif_data);
} else {
std::cout << "No EXIF data available, using basic info" << std::endl;
strncpy(exif_data.camera_make, "Unknown", 63);
exif_data.camera_make[63] = '\0';
strncpy(exif_data.camera_model, "JPEG Image", 63);
exif_data.camera_model[63] = '\0';
}
}
void extract_non_jpeg_exif(const std::vector<unsigned char>& input_data, ExifData& exif_data) {
strncpy(exif_data.camera_make, "Unknown", 63);
exif_data.camera_make[63] = '\0';
if (is_png(input_data.data(), input_data.size())) {
strncpy(exif_data.camera_model, "PNG->JPEG Conversion", 63);
} else {
strncpy(exif_data.camera_model, "Image->JPEG Conversion", 63);
}
exif_data.camera_model[63] = '\0';
}
void finalize_exif_data(ExifData& exif_data, int width, int height) {
exif_data.output_width = width;
exif_data.output_height = height;
exif_data.colors = 3; exif_data.color_filter = 0;
if (exif_data.iso_speed == 0) exif_data.iso_speed = 0;
if (exif_data.shutter == 0.0) exif_data.shutter = 0.0;
if (exif_data.aperture == 0.0) exif_data.aperture = 0.0;
if (exif_data.focal_length == 0.0) exif_data.focal_length = 0.0;
if (exif_data.max_aperture == 0.0) exif_data.max_aperture = 0.0;
if (exif_data.focal_length_35mm == 0) exif_data.focal_length_35mm = 0;
for (int i = 0; i < 4; i++) {
if (exif_data.cam_mul[i] == 0.0) exif_data.cam_mul[i] = 1.0;
}
}
int save_rgb_as_jpeg(unsigned char* rgb_data, int width, int height, const char* output_path) {
tjhandle compress_handle = tjInitCompress();
if (!compress_handle) {
std::cerr << "Failed to initialize TurboJPEG compressor" << std::endl;
return -1;
}
unsigned char* jpeg_buffer = nullptr;
unsigned long jpeg_size = 0;
if (tjCompress2(compress_handle, rgb_data, width, 0, height, TJPF_RGB, &jpeg_buffer, &jpeg_size, TJSAMP_444, 75, TJFLAG_FASTDCT) != 0) {
std::cerr << "Failed to compress JPEG: " << tjGetErrorStr() << std::endl;
tjDestroy(compress_handle);
return -1;
}
std::ofstream output_file(output_path, std::ios::binary);
if (!output_file) {
std::cerr << "Failed to open output file: " << output_path << std::endl;
tjFree(jpeg_buffer);
tjDestroy(compress_handle);
return -1;
}
output_file.write(reinterpret_cast<char*>(jpeg_buffer), jpeg_size);
output_file.close();
tjFree(jpeg_buffer);
tjDestroy(compress_handle);
return 0;
}
int process_image_to_jpeg(const char* input_path, const char* output_path, ExifData& exif_data) {
init_exif_data(exif_data);
std::ifstream file(input_path, std::ios::binary);
if (!file) {
std::cerr << "Failed to open input file: " << input_path << std::endl;
return -1;
}
file.seekg(0, std::ios::end);
size_t size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<unsigned char> input_data(size);
file.read(reinterpret_cast<char*>(input_data.data()), size);
file.close();
if (size == 0) {
std::cerr << "Empty input file: " << input_path << std::endl;
return -1;
}
int width, height;
unsigned char* rgb_data = nullptr;
if (is_jpeg(input_data.data(), size)) {
extract_jpeg_exif(input_data, exif_data);
tjhandle decompress_handle = tjInitDecompress();
if (!decompress_handle) {
std::cerr << "Failed to initialize TurboJPEG decompressor" << std::endl;
return -1;
}
int subsampling, colorspace;
if (tjDecompressHeader3(decompress_handle, input_data.data(), size, &width, &height, &subsampling, &colorspace) != 0) {
std::cerr << "Failed to read JPEG header: " << tjGetErrorStr() << std::endl;
tjDestroy(decompress_handle);
return -1;
}
exif_data.raw_width = width;
exif_data.raw_height = height;
width /= 2; height /= 2; size_t rgb_buffer_size = width * height * tjPixelSize[TJPF_RGB];
rgb_data = new unsigned char[rgb_buffer_size];
if (tjDecompress2(decompress_handle, input_data.data(), size, rgb_data, width, 0, height, TJPF_RGB, TJFLAG_FASTDCT) != 0) {
std::cerr << "Failed to decompress JPEG: " << tjGetErrorStr() << std::endl;
delete[] rgb_data;
tjDestroy(decompress_handle);
return -1;
}
tjDestroy(decompress_handle);
TinyEXIF::EXIFInfo exif_info;
exif_info.parseFrom(input_data.data(), input_data.size());
int orientation = exif_info.Orientation;
if (orientation > 1) {
unsigned char* rotated_data = new unsigned char[rgb_buffer_size];
if (orientation == 3) {
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
int src_idx = (y * width + x) * 3;
int dst_idx = ((height - 1 - y) * width + (width - 1 - x)) * 3;
rotated_data[dst_idx] = rgb_data[src_idx];
rotated_data[dst_idx + 1] = rgb_data[src_idx + 1];
rotated_data[dst_idx + 2] = rgb_data[src_idx + 2];
}
}
} else if (orientation == 6) {
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
int src_idx = (y * width + x) * 3;
int dst_idx = (x * height + (height - 1 - y)) * 3;
rotated_data[dst_idx] = rgb_data[src_idx];
rotated_data[dst_idx + 1] = rgb_data[src_idx + 1];
rotated_data[dst_idx + 2] = rgb_data[src_idx + 2];
}
}
std::swap(width, height);
} else if (orientation == 8) {
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
int src_idx = (y * width + x) * 3;
int dst_idx = ((width - 1 - x) * height + y) * 3;
rotated_data[dst_idx] = rgb_data[src_idx];
rotated_data[dst_idx + 1] = rgb_data[src_idx + 1];
rotated_data[dst_idx + 2] = rgb_data[src_idx + 2];
}
}
std::swap(width, height);
} else {
delete[] rotated_data;
rotated_data = nullptr;
}
if (rotated_data) {
delete[] rgb_data;
rgb_data = rotated_data;
}
}
} else {
extract_non_jpeg_exif(input_data, exif_data);
int channels;
rgb_data = stbi_load(input_path, &width, &height, &channels, 3);
if (!rgb_data) {
std::cerr << "Failed to decode image with stb_image: " << stbi_failure_reason() << std::endl;
return -1;
}
exif_data.raw_width = width;
exif_data.raw_height = height;
int new_width = width / 2;
int new_height = height / 2;
unsigned char* downscaled_data = new unsigned char[new_width * new_height * 3];
for (int y = 0; y < new_height; ++y) {
for (int x = 0; x < new_width; ++x) {
int src_index = ((y * 2) * width + (x * 2)) * 3;
int dst_index = (y * new_width + x) * 3;
downscaled_data[dst_index] = rgb_data[src_index];
downscaled_data[dst_index + 1] = rgb_data[src_index + 1];
downscaled_data[dst_index + 2] = rgb_data[src_index + 2];
}
}
stbi_image_free(rgb_data);
rgb_data = downscaled_data;
width = new_width;
height = new_height;
}
finalize_exif_data(exif_data, width, height);
int result = save_rgb_as_jpeg(rgb_data, width, height, output_path);
if (is_jpeg(input_data.data(), size)) {
delete[] rgb_data;
} else {
stbi_image_free(rgb_data);
}
if (result == 0) {
std::cout << "Successfully converted to JPEG: " << width << "x" << height << std::endl;
}
return result;
}
int process_image_bytes(const unsigned char* data, size_t size, const char* output_path, ExifData& exif_data) {
init_exif_data(exif_data);
if (!data || size == 0) {
std::cerr << "Empty input buffer" << std::endl;
return -1;
}
int width, height;
unsigned char* rgb_data = nullptr;
if (is_jpeg(data, size)) {
std::vector<unsigned char> input_data(data, data + size);
extract_jpeg_exif(input_data, exif_data);
tjhandle decompress_handle = tjInitDecompress();
if (!decompress_handle) {
std::cerr << "Failed to initialize TurboJPEG decompressor" << std::endl;
return -1;
}
int subsampling, colorspace;
if (tjDecompressHeader3(decompress_handle, data, size, &width, &height, &subsampling, &colorspace) != 0) {
std::cerr << "Failed to read JPEG header: " << tjGetErrorStr() << std::endl;
tjDestroy(decompress_handle);
return -1;
}
exif_data.raw_width = width;
exif_data.raw_height = height;
width /= 2;
height /= 2;
size_t rgb_buffer_size = width * height * tjPixelSize[TJPF_RGB];
rgb_data = new unsigned char[rgb_buffer_size];
if (tjDecompress2(decompress_handle, data, size, rgb_data, width, 0, height, TJPF_RGB, TJFLAG_FASTDCT) != 0) {
std::cerr << "Failed to decompress JPEG: " << tjGetErrorStr() << std::endl;
delete[] rgb_data;
tjDestroy(decompress_handle);
return -1;
}
tjDestroy(decompress_handle);
TinyEXIF::EXIFInfo exif_info;
exif_info.parseFrom(data, size);
int orientation = exif_info.Orientation;
if (orientation > 1) {
unsigned char* rotated_data = new unsigned char[rgb_buffer_size];
if (orientation == 3) {
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
int src_idx = (y * width + x) * 3;
int dst_idx = ((height - 1 - y) * width + (width - 1 - x)) * 3;
rotated_data[dst_idx] = rgb_data[src_idx];
rotated_data[dst_idx + 1] = rgb_data[src_idx + 1];
rotated_data[dst_idx + 2] = rgb_data[src_idx + 2];
}
}
} else if (orientation == 6) {
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
int src_idx = (y * width + x) * 3;
int dst_idx = (x * height + (height - 1 - y)) * 3;
rotated_data[dst_idx] = rgb_data[src_idx];
rotated_data[dst_idx + 1] = rgb_data[src_idx + 1];
rotated_data[dst_idx + 2] = rgb_data[src_idx + 2];
}
}
std::swap(width, height);
} else if (orientation == 8) {
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
int src_idx = (y * width + x) * 3;
int dst_idx = ((width - 1 - x) * height + y) * 3;
rotated_data[dst_idx] = rgb_data[src_idx];
rotated_data[dst_idx + 1] = rgb_data[src_idx + 1];
rotated_data[dst_idx + 2] = rgb_data[src_idx + 2];
}
}
std::swap(width, height);
} else {
delete[] rotated_data;
rotated_data = nullptr;
}
if (rotated_data) {
delete[] rgb_data;
rgb_data = rotated_data;
}
}
} else {
extract_non_jpeg_exif(std::vector<unsigned char>(data, data + size), exif_data);
int channels;
rgb_data = stbi_load_from_memory(data, (int)size, &width, &height, &channels, 3);
if (!rgb_data) {
std::cerr << "Failed to decode image from memory with stb_image: " << stbi_failure_reason() << std::endl;
return -1;
}
exif_data.raw_width = width;
exif_data.raw_height = height;
int new_width = width / 2;
int new_height = height / 2;
unsigned char* downscaled_data = new unsigned char[new_width * new_height * 3];
for (int y = 0; y < new_height; ++y) {
for (int x = 0; x < new_width; ++x) {
int src_index = ((y * 2) * width + (x * 2)) * 3;
int dst_index = (y * new_width + x) * 3;
downscaled_data[dst_index] = rgb_data[src_index];
downscaled_data[dst_index + 1] = rgb_data[src_index + 1];
downscaled_data[dst_index + 2] = rgb_data[src_index + 2];
}
}
stbi_image_free(rgb_data);
rgb_data = downscaled_data;
width = new_width;
height = new_height;
}
finalize_exif_data(exif_data, width, height);
int result = save_rgb_as_jpeg(rgb_data, width, height, output_path);
if (is_jpeg(data, size)) {
delete[] rgb_data;
} else {
stbi_image_free(rgb_data);
}
if (result == 0) {
std::cout << "Successfully converted in-memory to JPEG: " << width << "x" << height << std::endl;
}
return result;
}
void free_buffer(unsigned char* buffer) {
delete[] buffer;
}
int process_image_bytes_to_buffer(const unsigned char* data, size_t size, unsigned char** out_buf, size_t* out_size, ExifData& exif_data) {
if (!out_buf || !out_size) return -1;
*out_buf = nullptr;
*out_size = 0;
init_exif_data(exif_data);
if (!data || size == 0) {
std::cerr << "Empty input buffer" << std::endl;
return -1;
}
int width, height;
unsigned char* rgb_data = nullptr;
size_t rgb_buffer_size = 0;
if (is_jpeg(data, size)) {
std::vector<unsigned char> input_data(data, data + size);
extract_jpeg_exif(input_data, exif_data);
tjhandle decompress_handle = tjInitDecompress();
if (!decompress_handle) return -1;
int subsampling, colorspace;
if (tjDecompressHeader3(decompress_handle, data, size, &width, &height, &subsampling, &colorspace) != 0) {
tjDestroy(decompress_handle);
return -1;
}
exif_data.raw_width = width;
exif_data.raw_height = height;
width /= 2; height /= 2;
rgb_buffer_size = width * height * tjPixelSize[TJPF_RGB];
rgb_data = new unsigned char[rgb_buffer_size];
if (tjDecompress2(decompress_handle, data, size, rgb_data, width, 0, height, TJPF_RGB, TJFLAG_FASTDCT) != 0) {
delete[] rgb_data;
tjDestroy(decompress_handle);
return -1;
}
tjDestroy(decompress_handle);
TinyEXIF::EXIFInfo exif_info;
exif_info.parseFrom(data, size);
int orientation = exif_info.Orientation;
if (orientation > 1) {
unsigned char* rotated_data = new unsigned char[rgb_buffer_size];
delete[] rgb_data; rgb_data = rotated_data;
}
} else {
extract_non_jpeg_exif(std::vector<unsigned char>(data, data + size), exif_data);
int channels;
unsigned char* decoded = stbi_load_from_memory(data, (int)size, &width, &height, &channels, 3);
if (!decoded) return -1;
exif_data.raw_width = width;
exif_data.raw_height = height;
int new_width = width / 2;
int new_height = height / 2;
rgb_buffer_size = new_width * new_height * 3;
rgb_data = new unsigned char[rgb_buffer_size];
for (int y = 0; y < new_height; ++y) {
for (int x = 0; x < new_width; ++x) {
int src_index = ((y * 2) * width + (x * 2)) * 3;
int dst_index = (y * new_width + x) * 3;
rgb_data[dst_index] = decoded[src_index];
rgb_data[dst_index + 1] = decoded[src_index + 1];
rgb_data[dst_index + 2] = decoded[src_index + 2];
}
}
stbi_image_free(decoded);
width = new_width; height = new_height;
}
finalize_exif_data(exif_data, width, height);
tjhandle compress_handle = tjInitCompress();
if (!compress_handle) {
if (is_jpeg(data, size)) delete[] rgb_data; else stbi_image_free(rgb_data);
return -1;
}
unsigned char* jpeg_buffer = nullptr;
unsigned long jpeg_size = 0;
if (tjCompress2(compress_handle, rgb_data, width, 0, height, TJPF_RGB, &jpeg_buffer, &jpeg_size, TJSAMP_444, 75, TJFLAG_FASTDCT) != 0) {
tjDestroy(compress_handle);
if (is_jpeg(data, size)) delete[] rgb_data; else stbi_image_free(rgb_data);
return -1;
}
unsigned char* out = new unsigned char[jpeg_size];
memcpy(out, jpeg_buffer, jpeg_size);
*out_buf = out;
*out_size = jpeg_size;
tjFree(jpeg_buffer);
tjDestroy(compress_handle);
if (is_jpeg(data, size)) delete[] rgb_data; else stbi_image_free(rgb_data);
return 0;
}
}