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 1 #include <qpdf/Pl_DCT.hh>
2 2  
3 3 #include <qpdf/QUtil.hh>
  4 +#include <qpdf/QTC.hh>
4 5 #include <setjmp.h>
5 6 #include <string>
6 7 #include <stdexcept>
... ... @@ -80,15 +81,28 @@ Pl_DCT::finish()
80 81 // and decompress causes a memory leak with setjmp/longjmp. Just
81 82 // use a pointer and delete it.
82 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 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 108 else
... ... @@ -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 241 void
134 242 Pl_DCT::compress(void* cinfo_p, Buffer* b)
... ... @@ -146,10 +254,11 @@ Pl_DCT::compress(void* cinfo_p, Buffer* b)
146 254 defined(__clang__))
147 255 # pragma GCC diagnostic pop
148 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 263 cinfo->image_width = this->image_width;
155 264 cinfo->image_height = this->image_height;
... ... @@ -182,7 +291,6 @@ Pl_DCT::compress(void* cinfo_p, Buffer* b)
182 291 (void) jpeg_write_scanlines(cinfo, row_pointer, 1);
183 292 }
184 293 jpeg_finish_compress(cinfo);
185   - this->getNext()->write(outbuffer, outsize);
186 294 this->getNext()->finish();
187 295 }
188 296  
... ... @@ -202,7 +310,7 @@ Pl_DCT::decompress(void* cinfo_p, Buffer* b)
202 310 defined(__clang__))
203 311 # pragma GCC diagnostic pop
204 312 #endif
205   - jpeg_mem_src(cinfo, b->getBuffer(), b->getSize());
  313 + jpeg_buffer_src(cinfo, b);
206 314  
207 315 (void) jpeg_read_header(cinfo, TRUE);
208 316 (void) jpeg_calc_output_dimensions(cinfo);
... ...
libtests/libtests.testcov
... ... @@ -27,3 +27,5 @@ InputSource found match at buf[0] 0
27 27 Pl_RunLength: switch to run 1
28 28 Pl_RunLength flush full buffer 1
29 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 16  
17 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 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 55 cleanup();
49 56  
50   -$td->report(3 + $checked_data);
  57 +$td->report(6 + $checked_data);
51 58  
52 59 sub cleanup
53 60 {
... ...
libtests/qtest/dct/big-rawdata 0 → 100644
No preview for this file type