264 lines
8.6 KiB
C++
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);
|
|
}
|
|
|
|
}
|