Home | History | Annotate | Download | only in codec
      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 
     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 
     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 
     65 static void _src_do_nothing(struct jpeg_decompress_struct* cinfo) {}
     66 
     67 static void _error_fatal(j_common_ptr cinfo) {
     68   longjmp(*(jmp_buf*)cinfo->client_data, -1);
     69 }
     70 
     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 
     79 static boolean _src_fill_buffer(j_decompress_ptr cinfo) {
     80   return 0;
     81 }
     82 
     83 static boolean _src_resync(j_decompress_ptr cinfo, int desired) {
     84   return 0;
     85 }
     86 
     87 static void _error_do_nothing(j_common_ptr cinfo) {}
     88 
     89 static void _error_do_nothing1(j_common_ptr cinfo, int) {}
     90 
     91 static void _error_do_nothing2(j_common_ptr cinfo, char*) {}
     92 
     93 #if _FX_PLATFORM_ == _FX_PLATFORM_WINDOWS_
     94 static void _dest_do_nothing(j_compress_ptr cinfo) {}
     95 
     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
    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 
    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 
    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 
    213 CCodec_JpegDecoder::~CCodec_JpegDecoder() {
    214   FX_Free(m_pScanlineBuf);
    215   if (m_bInited)
    216     jpeg_destroy_decompress(&cinfo);
    217 }
    218 
    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 
    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 
    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 
    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 
    333 uint32_t CCodec_JpegDecoder::GetSrcOffset() {
    334   return (uint32_t)(m_SrcSize - src.bytes_in_buffer);
    335 }
    336 
    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 
    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 
    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 
    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 
    384 static void* jpeg_alloc_func(unsigned int size) {
    385   return FX_Alloc(char, size);
    386 }
    387 
    388 static void jpeg_free_func(void* p) {
    389   FX_Free(p);
    390 }
    391 
    392 }  // extern "C"
    393 
    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 
    415 CJpegContext::~CJpegContext() {
    416   jpeg_destroy_decompress(&m_Info);
    417 }
    418 
    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 
    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
    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