1 // Copyright 2014 PDFium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6
7 #include <setjmp.h>
8
9 #include <memory>
10 #include <utility>
11
12 #include "core/fxcodec/codec/codec_int.h"
13 #include "core/fxcodec/fx_codec.h"
14 #include "core/fxcrt/fx_safe_types.h"
15 #include "core/fxge/fx_dib.h"
16 #include "third_party/base/ptr_util.h"
17
18 extern "C" {
19 #undef FAR
20 #if defined(USE_SYSTEM_LIBJPEG)
21 #include <jpeglib.h>
22 #elif defined(USE_LIBJPEG_TURBO)
23 #include "third_party/libjpeg_turbo/jpeglib.h"
24 #else
25 #include "third_party/libjpeg/jpeglib.h"
26 #endif
27 }
28
29 extern "C" {
30
JpegScanSOI(const uint8_t ** src_buf,uint32_t * src_size)31 static void JpegScanSOI(const uint8_t** src_buf, uint32_t* src_size) {
32 if (*src_size == 0)
33 return;
34
35 uint32_t offset = 0;
36 while (offset < *src_size - 1) {
37 if ((*src_buf)[offset] == 0xff && (*src_buf)[offset + 1] == 0xd8) {
38 *src_buf += offset;
39 *src_size -= offset;
40 return;
41 }
42 offset++;
43 }
44 }
45
_src_do_nothing(struct jpeg_decompress_struct * cinfo)46 static void _src_do_nothing(struct jpeg_decompress_struct* cinfo) {}
47
_error_fatal(j_common_ptr cinfo)48 static void _error_fatal(j_common_ptr cinfo) {
49 longjmp(*(jmp_buf*)cinfo->client_data, -1);
50 }
51
_src_skip_data(struct jpeg_decompress_struct * cinfo,long num)52 static void _src_skip_data(struct jpeg_decompress_struct* cinfo, long num) {
53 if (num > (long)cinfo->src->bytes_in_buffer) {
54 _error_fatal((j_common_ptr)cinfo);
55 }
56 cinfo->src->next_input_byte += num;
57 cinfo->src->bytes_in_buffer -= num;
58 }
59
_src_fill_buffer(j_decompress_ptr cinfo)60 static boolean _src_fill_buffer(j_decompress_ptr cinfo) {
61 return 0;
62 }
63
_src_resync(j_decompress_ptr cinfo,int desired)64 static boolean _src_resync(j_decompress_ptr cinfo, int desired) {
65 return 0;
66 }
67
_error_do_nothing(j_common_ptr cinfo)68 static void _error_do_nothing(j_common_ptr cinfo) {}
69
_error_do_nothing1(j_common_ptr cinfo,int)70 static void _error_do_nothing1(j_common_ptr cinfo, int) {}
71
_error_do_nothing2(j_common_ptr cinfo,char *)72 static void _error_do_nothing2(j_common_ptr cinfo, char*) {}
73
74 #if _FX_OS_ == _FX_WIN32_DESKTOP_ || _FX_OS_ == _FX_WIN64_DESKTOP_
_dest_do_nothing(j_compress_ptr cinfo)75 static void _dest_do_nothing(j_compress_ptr cinfo) {}
76
_dest_empty(j_compress_ptr cinfo)77 static boolean _dest_empty(j_compress_ptr cinfo) {
78 return false;
79 }
80 #endif
81 };
82
83 #define JPEG_MARKER_ICC (JPEG_APP0 + 2)
84 #define JPEG_MARKER_MAXSIZE 0xFFFF
85
86 #ifdef PDF_ENABLE_XFA
JpegLoadAttribute(struct jpeg_decompress_struct * pInfo,CFX_DIBAttribute * pAttribute)87 static void JpegLoadAttribute(struct jpeg_decompress_struct* pInfo,
88 CFX_DIBAttribute* pAttribute) {
89 if (!pAttribute)
90 return;
91
92 pAttribute->m_nXDPI = pInfo->X_density;
93 pAttribute->m_nYDPI = pInfo->Y_density;
94 pAttribute->m_wDPIUnit = pInfo->density_unit;
95 }
96 #endif // PDF_ENABLE_XFA
97
JpegLoadInfo(const uint8_t * src_buf,uint32_t src_size,int * width,int * height,int * num_components,int * bits_per_components,bool * color_transform)98 static bool JpegLoadInfo(const uint8_t* src_buf,
99 uint32_t src_size,
100 int* width,
101 int* height,
102 int* num_components,
103 int* bits_per_components,
104 bool* color_transform) {
105 JpegScanSOI(&src_buf, &src_size);
106 struct jpeg_decompress_struct cinfo;
107 struct jpeg_error_mgr jerr;
108 jerr.error_exit = _error_fatal;
109 jerr.emit_message = _error_do_nothing1;
110 jerr.output_message = _error_do_nothing;
111 jerr.format_message = _error_do_nothing2;
112 jerr.reset_error_mgr = _error_do_nothing;
113 jerr.trace_level = 0;
114 cinfo.err = &jerr;
115 jmp_buf mark;
116 cinfo.client_data = &mark;
117 if (setjmp(mark) == -1)
118 return false;
119
120 jpeg_create_decompress(&cinfo);
121 struct jpeg_source_mgr src;
122 src.init_source = _src_do_nothing;
123 src.term_source = _src_do_nothing;
124 src.skip_input_data = _src_skip_data;
125 src.fill_input_buffer = _src_fill_buffer;
126 src.resync_to_restart = _src_resync;
127 src.bytes_in_buffer = src_size;
128 src.next_input_byte = src_buf;
129 cinfo.src = &src;
130 if (setjmp(mark) == -1) {
131 jpeg_destroy_decompress(&cinfo);
132 return false;
133 }
134 int ret = jpeg_read_header(&cinfo, true);
135 if (ret != JPEG_HEADER_OK) {
136 jpeg_destroy_decompress(&cinfo);
137 return false;
138 }
139 *width = cinfo.image_width;
140 *height = cinfo.image_height;
141 *num_components = cinfo.num_components;
142 *color_transform =
143 cinfo.jpeg_color_space == JCS_YCbCr || cinfo.jpeg_color_space == JCS_YCCK;
144 *bits_per_components = cinfo.data_precision;
145 jpeg_destroy_decompress(&cinfo);
146 return true;
147 }
148
149 class CCodec_JpegDecoder : public CCodec_ScanlineDecoder {
150 public:
151 CCodec_JpegDecoder();
152 ~CCodec_JpegDecoder() override;
153
154 bool Create(const uint8_t* src_buf,
155 uint32_t src_size,
156 int width,
157 int height,
158 int nComps,
159 bool ColorTransform);
160
161 // CCodec_ScanlineDecoder
162 bool v_Rewind() override;
163 uint8_t* v_GetNextLine() override;
164 uint32_t GetSrcOffset() override;
165
166 bool InitDecode();
167
168 jmp_buf m_JmpBuf;
169 struct jpeg_decompress_struct cinfo;
170 struct jpeg_error_mgr jerr;
171 struct jpeg_source_mgr src;
172 const uint8_t* m_SrcBuf;
173 uint32_t m_SrcSize;
174 uint8_t* m_pScanlineBuf;
175
176 bool m_bInited;
177 bool m_bStarted;
178 bool m_bJpegTransform;
179
180 protected:
181 uint32_t m_nDefaultScaleDenom;
182 };
183
CCodec_JpegDecoder()184 CCodec_JpegDecoder::CCodec_JpegDecoder() {
185 m_pScanlineBuf = nullptr;
186 m_bStarted = false;
187 m_bInited = false;
188 FXSYS_memset(&cinfo, 0, sizeof(cinfo));
189 FXSYS_memset(&jerr, 0, sizeof(jerr));
190 FXSYS_memset(&src, 0, sizeof(src));
191 m_nDefaultScaleDenom = 1;
192 }
193
~CCodec_JpegDecoder()194 CCodec_JpegDecoder::~CCodec_JpegDecoder() {
195 FX_Free(m_pScanlineBuf);
196 if (m_bInited)
197 jpeg_destroy_decompress(&cinfo);
198 }
199
InitDecode()200 bool CCodec_JpegDecoder::InitDecode() {
201 cinfo.err = &jerr;
202 cinfo.client_data = &m_JmpBuf;
203 if (setjmp(m_JmpBuf) == -1)
204 return false;
205
206 jpeg_create_decompress(&cinfo);
207 m_bInited = true;
208 cinfo.src = &src;
209 src.bytes_in_buffer = m_SrcSize;
210 src.next_input_byte = m_SrcBuf;
211 if (setjmp(m_JmpBuf) == -1) {
212 jpeg_destroy_decompress(&cinfo);
213 m_bInited = false;
214 return false;
215 }
216 cinfo.image_width = m_OrigWidth;
217 cinfo.image_height = m_OrigHeight;
218 int ret = jpeg_read_header(&cinfo, true);
219 if (ret != JPEG_HEADER_OK)
220 return false;
221
222 if (cinfo.saw_Adobe_marker)
223 m_bJpegTransform = true;
224
225 if (cinfo.num_components == 3 && !m_bJpegTransform)
226 cinfo.out_color_space = cinfo.jpeg_color_space;
227
228 m_OrigWidth = cinfo.image_width;
229 m_OrigHeight = cinfo.image_height;
230 m_OutputWidth = m_OrigWidth;
231 m_OutputHeight = m_OrigHeight;
232 m_nDefaultScaleDenom = cinfo.scale_denom;
233 return true;
234 }
235
Create(const uint8_t * src_buf,uint32_t src_size,int width,int height,int nComps,bool ColorTransform)236 bool CCodec_JpegDecoder::Create(const uint8_t* src_buf,
237 uint32_t src_size,
238 int width,
239 int height,
240 int nComps,
241 bool ColorTransform) {
242 JpegScanSOI(&src_buf, &src_size);
243 m_SrcBuf = src_buf;
244 m_SrcSize = src_size;
245 jerr.error_exit = _error_fatal;
246 jerr.emit_message = _error_do_nothing1;
247 jerr.output_message = _error_do_nothing;
248 jerr.format_message = _error_do_nothing2;
249 jerr.reset_error_mgr = _error_do_nothing;
250 src.init_source = _src_do_nothing;
251 src.term_source = _src_do_nothing;
252 src.skip_input_data = _src_skip_data;
253 src.fill_input_buffer = _src_fill_buffer;
254 src.resync_to_restart = _src_resync;
255 m_bJpegTransform = ColorTransform;
256 if (src_size > 1 &&
257 FXSYS_memcmp(src_buf + src_size - 2, "\xFF\xD9", 2) != 0) {
258 ((uint8_t*)src_buf)[src_size - 2] = 0xFF;
259 ((uint8_t*)src_buf)[src_size - 1] = 0xD9;
260 }
261 m_OutputWidth = m_OrigWidth = width;
262 m_OutputHeight = m_OrigHeight = height;
263 if (!InitDecode())
264 return false;
265
266 if (cinfo.num_components < nComps)
267 return false;
268
269 if ((int)cinfo.image_width < width)
270 return false;
271
272 m_Pitch =
273 (static_cast<uint32_t>(cinfo.image_width) * cinfo.num_components + 3) /
274 4 * 4;
275 m_pScanlineBuf = FX_Alloc(uint8_t, m_Pitch);
276 m_nComps = cinfo.num_components;
277 m_bpc = 8;
278 m_bStarted = false;
279 return true;
280 }
281
v_Rewind()282 bool CCodec_JpegDecoder::v_Rewind() {
283 if (m_bStarted) {
284 jpeg_destroy_decompress(&cinfo);
285 if (!InitDecode()) {
286 return false;
287 }
288 }
289 if (setjmp(m_JmpBuf) == -1) {
290 return false;
291 }
292 cinfo.scale_denom = m_nDefaultScaleDenom;
293 m_OutputWidth = m_OrigWidth;
294 m_OutputHeight = m_OrigHeight;
295 if (!jpeg_start_decompress(&cinfo)) {
296 jpeg_destroy_decompress(&cinfo);
297 return false;
298 }
299 if ((int)cinfo.output_width > m_OrigWidth) {
300 ASSERT(false);
301 return false;
302 }
303 m_bStarted = true;
304 return true;
305 }
306
v_GetNextLine()307 uint8_t* CCodec_JpegDecoder::v_GetNextLine() {
308 if (setjmp(m_JmpBuf) == -1)
309 return nullptr;
310
311 int nlines = jpeg_read_scanlines(&cinfo, &m_pScanlineBuf, 1);
312 return nlines > 0 ? m_pScanlineBuf : nullptr;
313 }
314
GetSrcOffset()315 uint32_t CCodec_JpegDecoder::GetSrcOffset() {
316 return (uint32_t)(m_SrcSize - src.bytes_in_buffer);
317 }
318
CreateDecoder(const uint8_t * src_buf,uint32_t src_size,int width,int height,int nComps,bool ColorTransform)319 std::unique_ptr<CCodec_ScanlineDecoder> CCodec_JpegModule::CreateDecoder(
320 const uint8_t* src_buf,
321 uint32_t src_size,
322 int width,
323 int height,
324 int nComps,
325 bool ColorTransform) {
326 if (!src_buf || src_size == 0)
327 return nullptr;
328
329 auto pDecoder = pdfium::MakeUnique<CCodec_JpegDecoder>();
330 if (!pDecoder->Create(src_buf, src_size, width, height, nComps,
331 ColorTransform)) {
332 return nullptr;
333 }
334 return std::move(pDecoder);
335 }
336
LoadInfo(const uint8_t * src_buf,uint32_t src_size,int * width,int * height,int * num_components,int * bits_per_components,bool * color_transform)337 bool CCodec_JpegModule::LoadInfo(const uint8_t* src_buf,
338 uint32_t src_size,
339 int* width,
340 int* height,
341 int* num_components,
342 int* bits_per_components,
343 bool* color_transform) {
344 return JpegLoadInfo(src_buf, src_size, width, height, num_components,
345 bits_per_components, color_transform);
346 }
347
348 struct FXJPEG_Context {
349 jmp_buf m_JumpMark;
350 jpeg_decompress_struct m_Info;
351 jpeg_error_mgr m_ErrMgr;
352 jpeg_source_mgr m_SrcMgr;
353 unsigned int m_SkipSize;
354 void* (*m_AllocFunc)(unsigned int);
355 void (*m_FreeFunc)(void*);
356 };
357 extern "C" {
_error_fatal1(j_common_ptr cinfo)358 static void _error_fatal1(j_common_ptr cinfo) {
359 longjmp(((FXJPEG_Context*)cinfo->client_data)->m_JumpMark, -1);
360 }
361 };
362 extern "C" {
_src_skip_data1(struct jpeg_decompress_struct * cinfo,long num)363 static void _src_skip_data1(struct jpeg_decompress_struct* cinfo, long num) {
364 if (cinfo->src->bytes_in_buffer < (size_t)num) {
365 ((FXJPEG_Context*)cinfo->client_data)->m_SkipSize =
366 (unsigned int)(num - cinfo->src->bytes_in_buffer);
367 cinfo->src->bytes_in_buffer = 0;
368 } else {
369 cinfo->src->next_input_byte += num;
370 cinfo->src->bytes_in_buffer -= num;
371 }
372 }
373 };
jpeg_alloc_func(unsigned int size)374 static void* jpeg_alloc_func(unsigned int size) {
375 return FX_Alloc(char, size);
376 }
jpeg_free_func(void * p)377 static void jpeg_free_func(void* p) {
378 FX_Free(p);
379 }
Start()380 FXJPEG_Context* CCodec_JpegModule::Start() {
381 FXJPEG_Context* p = FX_Alloc(FXJPEG_Context, 1);
382 p->m_AllocFunc = jpeg_alloc_func;
383 p->m_FreeFunc = jpeg_free_func;
384 p->m_ErrMgr.error_exit = _error_fatal1;
385 p->m_ErrMgr.emit_message = _error_do_nothing1;
386 p->m_ErrMgr.output_message = _error_do_nothing;
387 p->m_ErrMgr.format_message = _error_do_nothing2;
388 p->m_ErrMgr.reset_error_mgr = _error_do_nothing;
389 p->m_SrcMgr.init_source = _src_do_nothing;
390 p->m_SrcMgr.term_source = _src_do_nothing;
391 p->m_SrcMgr.skip_input_data = _src_skip_data1;
392 p->m_SrcMgr.fill_input_buffer = _src_fill_buffer;
393 p->m_SrcMgr.resync_to_restart = _src_resync;
394 p->m_Info.client_data = p;
395 p->m_Info.err = &p->m_ErrMgr;
396 if (setjmp(p->m_JumpMark) == -1) {
397 return 0;
398 }
399 jpeg_create_decompress(&p->m_Info);
400 p->m_Info.src = &p->m_SrcMgr;
401 p->m_SkipSize = 0;
402 return p;
403 }
404
Finish(FXJPEG_Context * ctx)405 void CCodec_JpegModule::Finish(FXJPEG_Context* ctx) {
406 jpeg_destroy_decompress(&ctx->m_Info);
407 ctx->m_FreeFunc(ctx);
408 }
409
Input(FXJPEG_Context * ctx,const unsigned char * src_buf,uint32_t src_size)410 void CCodec_JpegModule::Input(FXJPEG_Context* ctx,
411 const unsigned char* src_buf,
412 uint32_t src_size) {
413 if (ctx->m_SkipSize) {
414 if (ctx->m_SkipSize > src_size) {
415 ctx->m_SrcMgr.bytes_in_buffer = 0;
416 ctx->m_SkipSize -= src_size;
417 return;
418 }
419 src_size -= ctx->m_SkipSize;
420 src_buf += ctx->m_SkipSize;
421 ctx->m_SkipSize = 0;
422 }
423 ctx->m_SrcMgr.next_input_byte = src_buf;
424 ctx->m_SrcMgr.bytes_in_buffer = src_size;
425 }
426
427 #ifdef PDF_ENABLE_XFA
ReadHeader(FXJPEG_Context * ctx,int * width,int * height,int * nComps,CFX_DIBAttribute * pAttribute)428 int CCodec_JpegModule::ReadHeader(FXJPEG_Context* ctx,
429 int* width,
430 int* height,
431 int* nComps,
432 CFX_DIBAttribute* pAttribute) {
433 #else // PDF_ENABLE_XFA
434 int CCodec_JpegModule::ReadHeader(FXJPEG_Context* ctx,
435 int* width,
436 int* height,
437 int* nComps) {
438 #endif // PDF_ENABLE_XFA
439 if (setjmp(ctx->m_JumpMark) == -1)
440 return 1;
441
442 int ret = jpeg_read_header(&ctx->m_Info, true);
443 if (ret == JPEG_SUSPENDED)
444 return 2;
445 if (ret != JPEG_HEADER_OK)
446 return 1;
447
448 *width = ctx->m_Info.image_width;
449 *height = ctx->m_Info.image_height;
450 *nComps = ctx->m_Info.num_components;
451 #ifdef PDF_ENABLE_XFA
452 JpegLoadAttribute(&ctx->m_Info, pAttribute);
453 #endif
454 return 0;
455 }
456
457 bool CCodec_JpegModule::StartScanline(FXJPEG_Context* ctx, int down_scale) {
458 if (setjmp(ctx->m_JumpMark) == -1)
459 return false;
460
461 ctx->m_Info.scale_denom = down_scale;
462 return !!jpeg_start_decompress(&ctx->m_Info);
463 }
464
465 bool CCodec_JpegModule::ReadScanline(FXJPEG_Context* ctx,
466 unsigned char* dest_buf) {
467 if (setjmp(ctx->m_JumpMark) == -1)
468 return false;
469
470 int nlines = jpeg_read_scanlines(&ctx->m_Info, &dest_buf, 1);
471 return nlines == 1;
472 }
473
474 uint32_t CCodec_JpegModule::GetAvailInput(FXJPEG_Context* ctx,
475 uint8_t** avail_buf_ptr) {
476 if (avail_buf_ptr) {
477 *avail_buf_ptr = nullptr;
478 if (ctx->m_SrcMgr.bytes_in_buffer > 0) {
479 *avail_buf_ptr = (uint8_t*)ctx->m_SrcMgr.next_input_byte;
480 }
481 }
482 return (uint32_t)ctx->m_SrcMgr.bytes_in_buffer;
483 }
484
485 #if _FX_OS_ == _FX_WIN32_DESKTOP_ || _FX_OS_ == _FX_WIN64_DESKTOP_
486 #define JPEG_BLOCK_SIZE 1048576
487 bool CCodec_JpegModule::JpegEncode(const CFX_DIBSource* pSource,
488 uint8_t** dest_buf,
489 FX_STRSIZE* dest_size) {
490 struct jpeg_error_mgr jerr;
491 jerr.error_exit = _error_do_nothing;
492 jerr.emit_message = _error_do_nothing1;
493 jerr.output_message = _error_do_nothing;
494 jerr.format_message = _error_do_nothing2;
495 jerr.reset_error_mgr = _error_do_nothing;
496
497 struct jpeg_compress_struct cinfo;
498 memset(&cinfo, 0, sizeof(cinfo));
499 cinfo.err = &jerr;
500 jpeg_create_compress(&cinfo);
501 int Bpp = pSource->GetBPP() / 8;
502 uint32_t nComponents = Bpp >= 3 ? (pSource->IsCmykImage() ? 4 : 3) : 1;
503 uint32_t pitch = pSource->GetPitch();
504 uint32_t width = pdfium::base::checked_cast<uint32_t>(pSource->GetWidth());
505 uint32_t height = pdfium::base::checked_cast<uint32_t>(pSource->GetHeight());
506 FX_SAFE_UINT32 safe_buf_len = width;
507 safe_buf_len *= height;
508 safe_buf_len *= nComponents;
509 safe_buf_len += 1024;
510 if (!safe_buf_len.IsValid())
511 return false;
512
513 uint32_t dest_buf_length = safe_buf_len.ValueOrDie();
514 *dest_buf = FX_TryAlloc(uint8_t, dest_buf_length);
515 const int MIN_TRY_BUF_LEN = 1024;
516 while (!(*dest_buf) && dest_buf_length > MIN_TRY_BUF_LEN) {
517 dest_buf_length >>= 1;
518 *dest_buf = FX_TryAlloc(uint8_t, dest_buf_length);
519 }
520 if (!(*dest_buf))
521 return false;
522
523 struct jpeg_destination_mgr dest;
524 dest.init_destination = _dest_do_nothing;
525 dest.term_destination = _dest_do_nothing;
526 dest.empty_output_buffer = _dest_empty;
527 dest.next_output_byte = *dest_buf;
528 dest.free_in_buffer = dest_buf_length;
529 cinfo.dest = &dest;
530 cinfo.image_width = width;
531 cinfo.image_height = height;
532 cinfo.input_components = nComponents;
533 if (nComponents == 1) {
534 cinfo.in_color_space = JCS_GRAYSCALE;
535 } else if (nComponents == 3) {
536 cinfo.in_color_space = JCS_RGB;
537 } else {
538 cinfo.in_color_space = JCS_CMYK;
539 }
540 uint8_t* line_buf = nullptr;
541 if (nComponents > 1)
542 line_buf = FX_Alloc2D(uint8_t, width, nComponents);
543
544 jpeg_set_defaults(&cinfo);
545 jpeg_start_compress(&cinfo, TRUE);
546 JSAMPROW row_pointer[1];
547 JDIMENSION row;
548 while (cinfo.next_scanline < cinfo.image_height) {
549 const uint8_t* src_scan = pSource->GetScanline(cinfo.next_scanline);
550 if (nComponents > 1) {
551 uint8_t* dest_scan = line_buf;
552 if (nComponents == 3) {
553 for (uint32_t i = 0; i < width; i++) {
554 dest_scan[0] = src_scan[2];
555 dest_scan[1] = src_scan[1];
556 dest_scan[2] = src_scan[0];
557 dest_scan += 3;
558 src_scan += Bpp;
559 }
560 } else {
561 for (uint32_t i = 0; i < pitch; i++) {
562 *dest_scan++ = ~*src_scan++;
563 }
564 }
565 row_pointer[0] = line_buf;
566 } else {
567 row_pointer[0] = (uint8_t*)src_scan;
568 }
569 row = cinfo.next_scanline;
570 jpeg_write_scanlines(&cinfo, row_pointer, 1);
571 if (cinfo.next_scanline == row) {
572 *dest_buf =
573 FX_Realloc(uint8_t, *dest_buf, dest_buf_length + JPEG_BLOCK_SIZE);
574 dest.next_output_byte = *dest_buf + dest_buf_length - dest.free_in_buffer;
575 dest_buf_length += JPEG_BLOCK_SIZE;
576 dest.free_in_buffer += JPEG_BLOCK_SIZE;
577 }
578 }
579 jpeg_finish_compress(&cinfo);
580 jpeg_destroy_compress(&cinfo);
581 FX_Free(line_buf);
582 *dest_size = dest_buf_length - (FX_STRSIZE)dest.free_in_buffer;
583
584 return true;
585 }
586 #endif
587