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