2
1
mirror of https://github.com/qpdf/qpdf.git synced 2024-12-23 03:18:59 +00:00
qpdf/libqpdf/Pl_DCT.cc
Jay Berkenbilt 79f6b4823b Convert remaining public classes to use Members pattern
Have classes contain only a single private member of type
PointerHolder<Members>. This makes it safe to change the structure of
the Members class without breaking binary compatibility. Many of the
classes already follow this pattern quite successfully. This brings in
the rest of the class that are part of the public API.
2019-06-22 10:13:27 -04:00

362 lines
10 KiB
C++

#include <qpdf/Pl_DCT.hh>
#include <qpdf/QUtil.hh>
#include <qpdf/QTC.hh>
#include <qpdf/QIntC.hh>
#include <setjmp.h>
#include <stdexcept>
#include <stdlib.h>
#include <string>
#if BITS_IN_JSAMPLE != 8
# error "qpdf does not support libjpeg built with BITS_IN_JSAMPLE != 8"
#endif
struct qpdf_jpeg_error_mgr
{
struct jpeg_error_mgr pub;
jmp_buf jmpbuf;
std::string msg;
};
static void
error_handler(j_common_ptr cinfo)
{
qpdf_jpeg_error_mgr* jerr =
reinterpret_cast<qpdf_jpeg_error_mgr*>(cinfo->err);
char buf[JMSG_LENGTH_MAX];
(*cinfo->err->format_message)(cinfo, buf);
jerr->msg = buf;
longjmp(jerr->jmpbuf, 1);
}
Pl_DCT::Members::Members(action_e action,
char const* buf_description,
JDIMENSION image_width,
JDIMENSION image_height,
int components,
J_COLOR_SPACE color_space,
CompressConfig* config_callback) :
action(action),
buf(buf_description),
image_width(image_width),
image_height(image_height),
components(components),
color_space(color_space),
config_callback(config_callback)
{
}
Pl_DCT::Members::~Members()
{
}
Pl_DCT::Pl_DCT(char const* identifier, Pipeline* next) :
Pipeline(identifier, next),
m(new Members(a_decompress, "DCT compressed image"))
{
}
Pl_DCT::Pl_DCT(char const* identifier, Pipeline* next,
JDIMENSION image_width,
JDIMENSION image_height,
int components,
J_COLOR_SPACE color_space,
CompressConfig* config_callback) :
Pipeline(identifier, next),
m(new Members(a_compress, "DCT uncompressed image",
image_width, image_height, components, color_space, config_callback))
{
}
Pl_DCT::~Pl_DCT()
{
}
void
Pl_DCT::write(unsigned char* data, size_t len)
{
this->m->buf.write(data, len);
}
void
Pl_DCT::finish()
{
this->m->buf.finish();
// Using a PointerHolder<Buffer> here and passing it into compress
// and decompress causes a memory leak with setjmp/longjmp. Just
// use a pointer and delete it.
Buffer* b = this->m->buf.getBuffer();
if (b->getSize() == 0)
{
// Special case: empty data will never succeed and probably
// means we're calling finish a second time from an exception
// handler.
delete b;
this->getNext()->finish();
return;
}
struct jpeg_compress_struct cinfo_compress;
struct jpeg_decompress_struct cinfo_decompress;
struct qpdf_jpeg_error_mgr jerr;
cinfo_compress.err = jpeg_std_error(&(jerr.pub));
cinfo_decompress.err = jpeg_std_error(&(jerr.pub));
jerr.pub.error_exit = error_handler;
bool error = false;
// The jpeg library is a "C" library, so we use setjmp and longjmp
// for exception handling.
if (setjmp(jerr.jmpbuf) == 0)
{
try
{
if (this->m->action == a_compress)
{
compress(reinterpret_cast<void*>(&cinfo_compress), b);
}
else
{
decompress(reinterpret_cast<void*>(&cinfo_decompress), b);
}
}
catch (std::exception& e)
{
// Convert an exception back to a longjmp so we can ensure
// that the right cleanup happens. This will get converted
// back to an exception.
jerr.msg = e.what();
longjmp(jerr.jmpbuf, 1);
}
}
else
{
error = true;
}
delete b;
if (this->m->action == a_compress)
{
jpeg_destroy_compress(&cinfo_compress);
}
if (this->m->action == a_decompress)
{
jpeg_destroy_decompress(&cinfo_decompress);
}
if (error)
{
throw std::runtime_error(jerr.msg);
}
}
struct dct_pipeline_dest
{
struct jpeg_destination_mgr pub; /* public fields */
unsigned char* buffer;
size_t size;
Pipeline* next;
};
static void
init_pipeline_destination(j_compress_ptr)
{
}
static boolean
empty_pipeline_output_buffer(j_compress_ptr cinfo)
{
QTC::TC("libtests", "Pl_DCT empty_pipeline_output_buffer");
dct_pipeline_dest* dest =
reinterpret_cast<dct_pipeline_dest*>(cinfo->dest);
dest->next->write(dest->buffer, dest->size);
dest->pub.next_output_byte = dest->buffer;
dest->pub.free_in_buffer = dest->size;
return TRUE;
}
static void
term_pipeline_destination(j_compress_ptr cinfo)
{
QTC::TC("libtests", "Pl_DCT term_pipeline_destination");
dct_pipeline_dest* dest =
reinterpret_cast<dct_pipeline_dest*>(cinfo->dest);
dest->next->write(dest->buffer, dest->size - dest->pub.free_in_buffer);
}
static void
jpeg_pipeline_dest(j_compress_ptr cinfo,
unsigned char* outbuffer, size_t size,
Pipeline* next)
{
cinfo->dest = static_cast<struct jpeg_destination_mgr *>(
(*cinfo->mem->alloc_small)(reinterpret_cast<j_common_ptr>(cinfo),
JPOOL_PERMANENT,
sizeof(dct_pipeline_dest)));
dct_pipeline_dest* dest =
reinterpret_cast<dct_pipeline_dest*>(cinfo->dest);
dest->pub.init_destination = init_pipeline_destination;
dest->pub.empty_output_buffer = empty_pipeline_output_buffer;
dest->pub.term_destination = term_pipeline_destination;
dest->pub.next_output_byte = dest->buffer = outbuffer;
dest->pub.free_in_buffer = dest->size = size;
dest->next = next;
}
static void
init_buffer_source(j_decompress_ptr)
{
}
static boolean
fill_buffer_input_buffer(j_decompress_ptr)
{
// The whole JPEG data is expected to reside in the supplied memory
// buffer, so any request for more data beyond the given buffer size
// is treated as an error.
throw std::runtime_error("invalid jpeg data reading from buffer");
return TRUE;
}
static void
skip_buffer_input_data(j_decompress_ptr cinfo, long num_bytes)
{
if (num_bytes < 0)
{
throw std::runtime_error(
"reading jpeg: jpeg library requested"
" skipping a negative number of bytes");
}
size_t to_skip = QIntC::to_size(num_bytes);
if ((to_skip > 0) && (to_skip <= cinfo->src->bytes_in_buffer))
{
cinfo->src->next_input_byte += to_skip;
cinfo->src->bytes_in_buffer -= to_skip;
}
else if (to_skip != 0)
{
cinfo->src->next_input_byte += cinfo->src->bytes_in_buffer;
cinfo->src->bytes_in_buffer = 0;
}
}
static void
term_buffer_source(j_decompress_ptr)
{
}
static void
jpeg_buffer_src(j_decompress_ptr cinfo, Buffer* buffer)
{
cinfo->src = reinterpret_cast<jpeg_source_mgr *>(
(*cinfo->mem->alloc_small)(reinterpret_cast<j_common_ptr>(cinfo),
JPOOL_PERMANENT,
sizeof(jpeg_source_mgr)));
jpeg_source_mgr* src = cinfo->src;
src->init_source = init_buffer_source;
src->fill_input_buffer = fill_buffer_input_buffer;
src->skip_input_data = skip_buffer_input_data;
src->resync_to_restart = jpeg_resync_to_restart; /* use default method */
src->term_source = term_buffer_source;
src->bytes_in_buffer = buffer->getSize();
src->next_input_byte = buffer->getBuffer();
}
void
Pl_DCT::compress(void* cinfo_p, Buffer* b)
{
struct jpeg_compress_struct* cinfo =
reinterpret_cast<jpeg_compress_struct*>(cinfo_p);
#if ((defined(__GNUC__) && ((__GNUC__ * 100) + __GNUC_MINOR__) >= 406) || \
defined(__clang__))
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wold-style-cast"
#endif
jpeg_create_compress(cinfo);
#if ((defined(__GNUC__) && ((__GNUC__ * 100) + __GNUC_MINOR__) >= 406) || \
defined(__clang__))
# pragma GCC diagnostic pop
#endif
static int const BUF_SIZE = 65536;
PointerHolder<unsigned char> outbuffer_ph(
true, new unsigned char[BUF_SIZE]);
unsigned char* outbuffer = outbuffer_ph.getPointer();
jpeg_pipeline_dest(cinfo, outbuffer, BUF_SIZE, this->getNext());
cinfo->image_width = this->m->image_width;
cinfo->image_height = this->m->image_height;
cinfo->input_components = this->m->components;
cinfo->in_color_space = this->m->color_space;
jpeg_set_defaults(cinfo);
if (this->m->config_callback)
{
this->m->config_callback->apply(cinfo);
}
jpeg_start_compress(cinfo, TRUE);
unsigned int width = cinfo->image_width *
QIntC::to_uint(cinfo->input_components);
size_t expected_size =
cinfo->image_height * cinfo->image_width *
QIntC::to_uint(cinfo->input_components);
if (b->getSize() != expected_size)
{
throw std::runtime_error(
"Pl_DCT: image buffer size = " +
QUtil::uint_to_string(b->getSize()) + "; expected size = " +
QUtil::uint_to_string(expected_size));
}
JSAMPROW row_pointer[1];
unsigned char* buffer = b->getBuffer();
while (cinfo->next_scanline < cinfo->image_height)
{
// We already verified that the buffer is big enough.
row_pointer[0] = &buffer[cinfo->next_scanline * width];
(void) jpeg_write_scanlines(cinfo, row_pointer, 1);
}
jpeg_finish_compress(cinfo);
this->getNext()->finish();
}
void
Pl_DCT::decompress(void* cinfo_p, Buffer* b)
{
struct jpeg_decompress_struct* cinfo =
reinterpret_cast<jpeg_decompress_struct*>(cinfo_p);
#if ((defined(__GNUC__) && ((__GNUC__ * 100) + __GNUC_MINOR__) >= 406) || \
defined(__clang__))
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wold-style-cast"
#endif
jpeg_create_decompress(cinfo);
#if ((defined(__GNUC__) && ((__GNUC__ * 100) + __GNUC_MINOR__) >= 406) || \
defined(__clang__))
# pragma GCC diagnostic pop
#endif
jpeg_buffer_src(cinfo, b);
(void) jpeg_read_header(cinfo, TRUE);
(void) jpeg_calc_output_dimensions(cinfo);
unsigned int width = cinfo->output_width *
QIntC::to_uint(cinfo->output_components);
JSAMPARRAY buffer = (*cinfo->mem->alloc_sarray)
(reinterpret_cast<j_common_ptr>(cinfo), JPOOL_IMAGE, width, 1);
(void) jpeg_start_decompress(cinfo);
while (cinfo->output_scanline < cinfo->output_height)
{
(void) jpeg_read_scanlines(cinfo, buffer, 1);
this->getNext()->write(reinterpret_cast<unsigned char*>(buffer[0]),
width * sizeof(buffer[0][0]));
}
(void) jpeg_finish_decompress(cinfo);
this->getNext()->finish();
}