Files
ar_basalt/thirdparty/Pangolin/src/image/image_io_jpg.cpp
2022-04-05 11:42:28 +03:00

264 lines
8.6 KiB
C++

#include <algorithm>
#include <fstream>
#include <pangolin/platform.h>
#include <pangolin/image/typed_image.h>
#ifdef HAVE_JPEG
# include <jpeglib.h>
# ifdef _WIN_
// Undef windows Macro polution from jpeglib.h
# undef LoadImage
# endif
#endif // HAVE_JPEG
// Inspired by https://cs.stanford.edu/~acoates/jpegAndIOS.txt
namespace pangolin {
#ifdef HAVE_JPEG
void error_handler(j_common_ptr cinfo) {
char msg[JMSG_LENGTH_MAX];
(*(cinfo->err->format_message)) (cinfo, msg);
throw std::runtime_error(msg);
}
const static size_t PANGO_JPEG_BUF_SIZE = 16384;
struct pango_jpeg_source_mgr {
struct jpeg_source_mgr pub;
std::istream* is;
JOCTET* buffer;
};
static void pango_jpeg_init_source(j_decompress_ptr /*cinfo*/) {
}
static boolean pango_jpeg_fill_input_buffer(j_decompress_ptr cinfo) {
pango_jpeg_source_mgr* src = (pango_jpeg_source_mgr*)cinfo->src;
src->is->read((char*)src->buffer, PANGO_JPEG_BUF_SIZE);
size_t bytes = src->is->gcount();
if (bytes == 0) {
/* Insert a fake EOI marker */
src->buffer[0] = (JOCTET) 0xFF;
src->buffer[1] = (JOCTET) JPEG_EOI;
bytes = 2;
}
src->pub.next_input_byte = src->buffer;
src->pub.bytes_in_buffer = bytes;
return TRUE;
}
static void pango_jpeg_skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
pango_jpeg_source_mgr* src = (pango_jpeg_source_mgr*)cinfo->src;
if (num_bytes > 0) {
while (num_bytes > (long)src->pub.bytes_in_buffer) {
num_bytes -= (long)src->pub.bytes_in_buffer;
pango_jpeg_fill_input_buffer(cinfo);
}
src->pub.next_input_byte += num_bytes;
src->pub.bytes_in_buffer -= num_bytes;
}
}
static void pango_jpeg_term_source(j_decompress_ptr cinfo) {
// must seek backward so that future reads will start at correct place.
pango_jpeg_source_mgr* src = (pango_jpeg_source_mgr*)cinfo->src;
src->is->clear();
src->is->seekg( src->is->tellg() - (std::streampos)src->pub.bytes_in_buffer );
}
static void pango_jpeg_set_source_mgr(j_decompress_ptr cinfo, std::istream& is) {
pango_jpeg_source_mgr* src = nullptr;
if (cinfo->src == 0)
{
cinfo->src = (struct jpeg_source_mgr *)(*cinfo->mem->alloc_small)
((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(pango_jpeg_source_mgr));
src = (pango_jpeg_source_mgr*) cinfo->src;
src->buffer = (JOCTET *)(*cinfo->mem->alloc_small)
((j_common_ptr) cinfo, JPOOL_PERMANENT, PANGO_JPEG_BUF_SIZE*sizeof(JOCTET));
}else{
src = (pango_jpeg_source_mgr*) cinfo->src;
}
src->is = &is;
src->pub.init_source = pango_jpeg_init_source;
src->pub.fill_input_buffer = pango_jpeg_fill_input_buffer;
src->pub.skip_input_data = pango_jpeg_skip_input_data;
src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */
src->pub.term_source = pango_jpeg_term_source;
src->pub.bytes_in_buffer = 0;
src->pub.next_input_byte = 0;
}
struct pango_jpeg_destination_mgr {
struct jpeg_destination_mgr pub; /* public fields */
std::ostream* os; /* target stream */
JOCTET * buffer; /* start of buffer */
};
void pango_jpeg_init_destination (j_compress_ptr cinfo) {
pango_jpeg_destination_mgr* dest = (pango_jpeg_destination_mgr*) cinfo->dest;
dest->buffer = (JOCTET *)(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
PANGO_JPEG_BUF_SIZE * sizeof(JOCTET));
dest->pub.next_output_byte = dest->buffer;
dest->pub.free_in_buffer = PANGO_JPEG_BUF_SIZE;
}
boolean pango_jpeg_empty_output_buffer(j_compress_ptr cinfo) {
pango_jpeg_destination_mgr* dest = (pango_jpeg_destination_mgr*)cinfo->dest;
dest->os->write((const char*)dest->buffer, PANGO_JPEG_BUF_SIZE);
if (dest->os->fail()) {
throw std::runtime_error("Couldn't write entire jpeg buffer to stream.");
}
dest->pub.next_output_byte = dest->buffer;
dest->pub.free_in_buffer = PANGO_JPEG_BUF_SIZE;
return TRUE;
}
void pango_jpeg_term_destination (j_compress_ptr cinfo) {
pango_jpeg_destination_mgr* dest = (pango_jpeg_destination_mgr*) cinfo->dest;
size_t datacount = PANGO_JPEG_BUF_SIZE - dest->pub.free_in_buffer;
/* Write any data remaining in the buffer */
if (datacount > 0) {
dest->os->write((const char*)dest->buffer, datacount);
if (dest->os->fail()) {
throw std::runtime_error("Couldn't write remaining jpeg data to stream.");
}
}
dest->os->flush();
}
void pango_jpeg_set_dest_mgr(j_compress_ptr cinfo, std::ostream& os) {
pango_jpeg_destination_mgr* dest;
if (cinfo->dest == NULL) { /* first time for this JPEG object? */
cinfo->dest = (struct jpeg_destination_mgr *)
(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
sizeof(pango_jpeg_destination_mgr));
}
dest = (pango_jpeg_destination_mgr*)cinfo->dest;
dest->pub.init_destination = pango_jpeg_init_destination;
dest->pub.empty_output_buffer = pango_jpeg_empty_output_buffer;
dest->pub.term_destination = pango_jpeg_term_destination;
dest->os = &os;
}
#endif // HAVE_JPEG
TypedImage LoadJpg(std::istream& is) {
#ifdef HAVE_JPEG
TypedImage image;
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
// Setup decompression structure
cinfo.err = jpeg_std_error(&jerr);
jerr.error_exit = error_handler;
jpeg_create_decompress(&cinfo);
pango_jpeg_set_source_mgr(&cinfo, is);
// read info from header.
int r = jpeg_read_header(&cinfo, TRUE);
if (r != JPEG_HEADER_OK) {
throw std::runtime_error("Failed to read JPEG header.");
} else if (cinfo.num_components != 3 && cinfo.num_components != 1) {
throw std::runtime_error("Unsupported number of color components");
} else {
jpeg_start_decompress(&cinfo);
// resize storage if necessary
PixelFormat fmt = PixelFormatFromString(cinfo.output_components == 3 ? "RGB24" : "GRAY8");
image.Reinitialise(cinfo.output_width, cinfo.output_height, fmt);
JSAMPARRAY imageBuffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE,
cinfo.output_width*cinfo.output_components, 1);
for (size_t y = 0; y < cinfo.output_height; y++) {
jpeg_read_scanlines(&cinfo, imageBuffer, 1);
uint8_t* dstRow = (uint8_t*)image.RowPtr(y);
memcpy(dstRow, imageBuffer[0], cinfo.output_width*cinfo.output_components);
}
jpeg_finish_decompress(&cinfo);
}
// clean up.
jpeg_destroy_decompress(&cinfo);
return image;
#else
PANGOLIN_UNUSED(is);
throw std::runtime_error("Rebuild Pangolin for JPEG support.");
#endif // HAVE_JPEG
}
TypedImage LoadJpg(const std::string& filename) {
std::ifstream f(filename);
return LoadJpg(f);
}
void SaveJpg(const Image<unsigned char>& img, const PixelFormat& fmt, std::ostream& os, float quality) {
#ifdef HAVE_JPEG
const int iquality = (int)std::max(std::min(quality, 100.0f),0.0f);
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
if (fmt.channels != 1 && fmt.channels != 3) {
throw std::runtime_error("Unsupported number of image channels.");
}
if (fmt.bpp != 8 && fmt.bpp != 24) {
throw std::runtime_error("Unsupported image depth.");
}
// set up compression structure
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
pango_jpeg_set_dest_mgr(&cinfo, os);
cinfo.image_width = img.w;
cinfo.image_height = img.h;
cinfo.input_components = fmt.channels;
if (fmt.channels == 3) {
cinfo.in_color_space = JCS_RGB;
} else {
cinfo.in_color_space = JCS_GRAYSCALE;
}
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, iquality, (boolean)true);
jpeg_start_compress(&cinfo, (boolean)true);
JSAMPROW row;
while (cinfo.next_scanline < cinfo.image_height) {
row = (JSAMPROW)((char*)img.RowPtr(cinfo.next_scanline));
jpeg_write_scanlines(&cinfo, &row, 1);
}
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
#else
PANGOLIN_UNUSED(img);
PANGOLIN_UNUSED(fmt);
PANGOLIN_UNUSED(os);
PANGOLIN_UNUSED(quality);
throw std::runtime_error("Rebuild Pangolin for JPEG support.");
#endif // HAVE_JPEG
}
void SaveJpg(const Image<unsigned char>& img, const PixelFormat& fmt, const std::string& filename, float quality) {
std::ofstream f(filename);
SaveJpg(img, fmt, f, quality);
}
}