Commit 40ecba4172722533916c359fcfe5a43dcd0801ea

Authored by Jay Berkenbilt
1 parent cbb26149

Pl_DCT: Use custom source and destination managers (fixes #153)

Avoid calling jpeg_mem_src and jpeg_mem_dest. The custom destination
manager writes to the pipeline in smaller chunks to avoid having the
whole image in memory at once. The source manager works directly with
the Buffer object. Using customer managers avoids use of memory source
and destination managers, which are not present in older versions of
libjpeg still in use by some Linux distributions.
libqpdf/Pl_DCT.cc
1 #include <qpdf/Pl_DCT.hh> 1 #include <qpdf/Pl_DCT.hh>
2 2
3 #include <qpdf/QUtil.hh> 3 #include <qpdf/QUtil.hh>
  4 +#include <qpdf/QTC.hh>
4 #include <setjmp.h> 5 #include <setjmp.h>
5 #include <string> 6 #include <string>
6 #include <stdexcept> 7 #include <stdexcept>
@@ -80,15 +81,28 @@ Pl_DCT::finish() @@ -80,15 +81,28 @@ Pl_DCT::finish()
80 // and decompress causes a memory leak with setjmp/longjmp. Just 81 // and decompress causes a memory leak with setjmp/longjmp. Just
81 // use a pointer and delete it. 82 // use a pointer and delete it.
82 Buffer* b = this->buf.getBuffer(); 83 Buffer* b = this->buf.getBuffer();
  84 + // The jpeg library is a "C" library, so we use setjmp and longjmp
  85 + // for exceptoin handling.
83 if (setjmp(jerr.jmpbuf) == 0) 86 if (setjmp(jerr.jmpbuf) == 0)
84 { 87 {
85 - if (this->action == a_compress) 88 + try
86 { 89 {
87 - compress(reinterpret_cast<void*>(&cinfo_compress), b); 90 + if (this->action == a_compress)
  91 + {
  92 + compress(reinterpret_cast<void*>(&cinfo_compress), b);
  93 + }
  94 + else
  95 + {
  96 + decompress(reinterpret_cast<void*>(&cinfo_decompress), b);
  97 + }
88 } 98 }
89 - else 99 + catch (std::exception& e)
90 { 100 {
91 - decompress(reinterpret_cast<void*>(&cinfo_decompress), b); 101 + // Convert an exception back to a longjmp so we can ensure
  102 + // that the right cleanup happens. This will get converted
  103 + // back to an exception.
  104 + jerr.msg = e.what();
  105 + longjmp(jerr.jmpbuf, 1);
92 } 106 }
93 } 107 }
94 else 108 else
@@ -111,24 +125,118 @@ Pl_DCT::finish() @@ -111,24 +125,118 @@ Pl_DCT::finish()
111 } 125 }
112 } 126 }
113 127
114 -class Freer 128 +struct dct_pipeline_dest
115 { 129 {
116 - public:  
117 - Freer(unsigned char** p) :  
118 - p(p) 130 + struct jpeg_destination_mgr pub; /* public fields */
  131 + unsigned char* buffer;
  132 + size_t size;
  133 + Pipeline* next;
  134 +};
  135 +
  136 +static void
  137 +init_pipeline_destination(j_compress_ptr)
  138 +{
  139 +}
  140 +
  141 +static int
  142 +empty_pipeline_output_buffer(j_compress_ptr cinfo)
  143 +{
  144 + QTC::TC("libtests", "Pl_DCT empty_pipeline_output_buffer");
  145 + dct_pipeline_dest* dest =
  146 + reinterpret_cast<dct_pipeline_dest*>(cinfo->dest);
  147 + dest->next->write(dest->buffer, dest->size);
  148 + dest->pub.next_output_byte = dest->buffer;
  149 + dest->pub.free_in_buffer = dest->size;
  150 + return TRUE;
  151 +}
  152 +
  153 +static void
  154 +term_pipeline_destination(j_compress_ptr cinfo)
  155 +{
  156 + QTC::TC("libtests", "Pl_DCT term_pipeline_destination");
  157 + dct_pipeline_dest* dest =
  158 + reinterpret_cast<dct_pipeline_dest*>(cinfo->dest);
  159 + dest->next->write(dest->buffer, dest->size - dest->pub.free_in_buffer);
  160 +}
  161 +
  162 +static void
  163 +jpeg_pipeline_dest(j_compress_ptr cinfo,
  164 + unsigned char* outbuffer, size_t size,
  165 + Pipeline* next)
  166 +{
  167 + cinfo->dest = static_cast<struct jpeg_destination_mgr *>(
  168 + (*cinfo->mem->alloc_small)(reinterpret_cast<j_common_ptr>(cinfo),
  169 + JPOOL_PERMANENT,
  170 + sizeof(dct_pipeline_dest)));
  171 + dct_pipeline_dest* dest =
  172 + reinterpret_cast<dct_pipeline_dest*>(cinfo->dest);
  173 + dest->pub.init_destination = init_pipeline_destination;
  174 + dest->pub.empty_output_buffer = empty_pipeline_output_buffer;
  175 + dest->pub.term_destination = term_pipeline_destination;
  176 + dest->pub.next_output_byte = dest->buffer = outbuffer;
  177 + dest->pub.free_in_buffer = dest->size = size;
  178 + dest->next = next;
  179 +}
  180 +
  181 +static void
  182 +init_buffer_source(j_decompress_ptr)
  183 +{
  184 +}
  185 +
  186 +static int
  187 +fill_buffer_input_buffer(j_decompress_ptr)
  188 +{
  189 + // The whole JPEG data is expected to reside in the supplied memory
  190 + // buffer, so any request for more data beyond the given buffer size
  191 + // is treated as an error.
  192 + throw std::runtime_error("invalid jpeg data reading from buffer");
  193 + return TRUE;
  194 +}
  195 +
  196 +static void
  197 +skip_buffer_input_data(j_decompress_ptr cinfo, long num_bytes)
  198 +{
  199 + if (num_bytes < 0)
119 { 200 {
  201 + throw std::runtime_error(
  202 + "reading jpeg: jpeg library requested"
  203 + " skipping a negative number of bytes");
120 } 204 }
121 - ~Freer() 205 + size_t to_skip = static_cast<size_t>(num_bytes);
  206 + if ((to_skip > 0) && (to_skip <= cinfo->src->bytes_in_buffer))
122 { 207 {
123 - if (*p)  
124 - {  
125 - free(*p);  
126 - } 208 + cinfo->src->next_input_byte += to_skip;
  209 + cinfo->src->bytes_in_buffer -= to_skip;
  210 + }
  211 + else if (to_skip != 0)
  212 + {
  213 + cinfo->src->next_input_byte += cinfo->src->bytes_in_buffer;
  214 + cinfo->src->bytes_in_buffer = 0;
127 } 215 }
  216 +}
128 217
129 - private:  
130 - unsigned char** p;  
131 -}; 218 +static void
  219 +term_buffer_source(j_decompress_ptr)
  220 +{
  221 +}
  222 +
  223 +static void
  224 +jpeg_buffer_src(j_decompress_ptr cinfo, Buffer* buffer)
  225 +{
  226 + cinfo->src = reinterpret_cast<jpeg_source_mgr *>(
  227 + (*cinfo->mem->alloc_small)(reinterpret_cast<j_common_ptr>(cinfo),
  228 + JPOOL_PERMANENT,
  229 + sizeof(jpeg_source_mgr)));
  230 +
  231 + jpeg_source_mgr* src = cinfo->src;
  232 + src->init_source = init_buffer_source;
  233 + src->fill_input_buffer = fill_buffer_input_buffer;
  234 + src->skip_input_data = skip_buffer_input_data;
  235 + src->resync_to_restart = jpeg_resync_to_restart; /* use default method */
  236 + src->term_source = term_buffer_source;
  237 + src->bytes_in_buffer = buffer->getSize();
  238 + src->next_input_byte = buffer->getBuffer();
  239 +}
132 240
133 void 241 void
134 Pl_DCT::compress(void* cinfo_p, Buffer* b) 242 Pl_DCT::compress(void* cinfo_p, Buffer* b)
@@ -146,10 +254,11 @@ Pl_DCT::compress(void* cinfo_p, Buffer* b) @@ -146,10 +254,11 @@ Pl_DCT::compress(void* cinfo_p, Buffer* b)
146 defined(__clang__)) 254 defined(__clang__))
147 # pragma GCC diagnostic pop 255 # pragma GCC diagnostic pop
148 #endif 256 #endif
149 - unsigned char* outbuffer = 0;  
150 - Freer freer(&outbuffer);  
151 - unsigned long outsize = 0;  
152 - jpeg_mem_dest(cinfo, &outbuffer, &outsize); 257 + static int const BUF_SIZE = 65536;
  258 + PointerHolder<unsigned char> outbuffer_ph(
  259 + true, new unsigned char[BUF_SIZE]);
  260 + unsigned char* outbuffer = outbuffer_ph.getPointer();
  261 + jpeg_pipeline_dest(cinfo, outbuffer, BUF_SIZE, this->getNext());
153 262
154 cinfo->image_width = this->image_width; 263 cinfo->image_width = this->image_width;
155 cinfo->image_height = this->image_height; 264 cinfo->image_height = this->image_height;
@@ -182,7 +291,6 @@ Pl_DCT::compress(void* cinfo_p, Buffer* b) @@ -182,7 +291,6 @@ Pl_DCT::compress(void* cinfo_p, Buffer* b)
182 (void) jpeg_write_scanlines(cinfo, row_pointer, 1); 291 (void) jpeg_write_scanlines(cinfo, row_pointer, 1);
183 } 292 }
184 jpeg_finish_compress(cinfo); 293 jpeg_finish_compress(cinfo);
185 - this->getNext()->write(outbuffer, outsize);  
186 this->getNext()->finish(); 294 this->getNext()->finish();
187 } 295 }
188 296
@@ -202,7 +310,7 @@ Pl_DCT::decompress(void* cinfo_p, Buffer* b) @@ -202,7 +310,7 @@ Pl_DCT::decompress(void* cinfo_p, Buffer* b)
202 defined(__clang__)) 310 defined(__clang__))
203 # pragma GCC diagnostic pop 311 # pragma GCC diagnostic pop
204 #endif 312 #endif
205 - jpeg_mem_src(cinfo, b->getBuffer(), b->getSize()); 313 + jpeg_buffer_src(cinfo, b);
206 314
207 (void) jpeg_read_header(cinfo, TRUE); 315 (void) jpeg_read_header(cinfo, TRUE);
208 (void) jpeg_calc_output_dimensions(cinfo); 316 (void) jpeg_calc_output_dimensions(cinfo);
libtests/libtests.testcov
@@ -27,3 +27,5 @@ InputSource found match at buf[0] 0 @@ -27,3 +27,5 @@ InputSource found match at buf[0] 0
27 Pl_RunLength: switch to run 1 27 Pl_RunLength: switch to run 1
28 Pl_RunLength flush full buffer 1 28 Pl_RunLength flush full buffer 1
29 Pl_RunLength flush empty buffer 0 29 Pl_RunLength flush empty buffer 0
  30 +Pl_DCT empty_pipeline_output_buffer 0
  31 +Pl_DCT term_pipeline_destination 0
libtests/qtest/dct.test
@@ -16,38 +16,45 @@ my $td = new TestDriver(&#39;dct&#39;); @@ -16,38 +16,45 @@ my $td = new TestDriver(&#39;dct&#39;);
16 16
17 cleanup(); 17 cleanup();
18 18
19 -$td->runtest("compress",  
20 - {$td->COMMAND => "dct_compress rawdata a.jpg 400 256 gray"},  
21 - {$td->STRING => "", $td->EXIT_STATUS => 0});  
22 -$td->runtest("decompress",  
23 - {$td->COMMAND => "dct_uncompress a.jpg out"},  
24 - {$td->STRING => "", $td->EXIT_STATUS => 0});  
25 -# Compare  
26 -my @raw = get_data('rawdata');  
27 -my @processed = get_data('out');  
28 my $checked_data = 0; 19 my $checked_data = 0;
29 -if ($td->runtest("bytes in data",  
30 - {$td->STRING => scalar(@processed)},  
31 - {$td->STRING => scalar(@raw)})) 20 +foreach my $d (['rawdata', '400 256 gray', 0],
  21 + ['big-rawdata', '1024 576 rgb', 0.2])
32 { 22 {
33 - my $mismatch = 0;  
34 - for (my $i = 0; $i < scalar(@raw); ++$i) 23 + my ($in, $args, $mismatch_fraction) = @$d;
  24 + $td->runtest("compress",
  25 + {$td->COMMAND => "dct_compress $in a.jpg $args"},
  26 + {$td->STRING => "", $td->EXIT_STATUS => 0});
  27 + $td->runtest("decompress",
  28 + {$td->COMMAND => "dct_uncompress a.jpg out"},
  29 + {$td->STRING => "", $td->EXIT_STATUS => 0});
  30 + # Compare
  31 + my @raw = get_data($in);
  32 + my @processed = get_data('out');
  33 + my $bytes = scalar(@raw);
  34 + if ($td->runtest("bytes in data",
  35 + {$td->STRING => scalar(@processed)},
  36 + {$td->STRING => $bytes}))
35 { 37 {
36 - $checked_data = 1;  
37 - my $delta = abs(ord($raw[$i]) - ord($processed[$i]));  
38 - if ($delta > 10) 38 + ++$checked_data;
  39 + my $mismatch = 0;
  40 + for (my $i = 0; $i < scalar(@raw); ++$i)
39 { 41 {
40 - ++$mismatch; 42 + my $delta = abs(ord($raw[$i]) - ord($processed[$i]));
  43 + if ($delta > 10)
  44 + {
  45 + ++$mismatch;
  46 + }
41 } 47 }
  48 + my $threshold = int($mismatch_fraction * $bytes);
  49 + $td->runtest("data is close enough",
  50 + {$td->STRING => $mismatch <= $threshold ? 'pass' : 'fail'},
  51 + {$td->STRING => 'pass'});
42 } 52 }
43 - $td->runtest("data is close enough",  
44 - {$td->STRING => $mismatch},  
45 - {$td->STRING => '0'});  
46 } 53 }
47 54
48 cleanup(); 55 cleanup();
49 56
50 -$td->report(3 + $checked_data); 57 +$td->report(6 + $checked_data);
51 58
52 sub cleanup 59 sub cleanup
53 { 60 {
libtests/qtest/dct/big-rawdata 0 → 100644
No preview for this file type