text: /*
   *  Copyright 2012 The LibYuv Project Authors. All rights reserved.
   *
   *  Use of this source code is governed by a BSD-style license
   *  that can be found in the LICENSE file in the root of the source
   *  tree. An additional intellectual property rights grant can be found
   *  in the file PATENTS. All contributing project authors may
   *  be found in the AUTHORS file in the root of the source tree.
   */
  
  #include "libyuv/mjpeg_decoder.h"
  
  #ifdef HAVE_JPEG
  #include <assert.h>
  
  #if !defined(__pnacl__) && !defined(__CLR_VER) && \
      !defined(COVERAGE_ENABLED) && !defined(TARGET_IPHONE_SIMULATOR)
  // Must be included before jpeglib.
  #include <setjmp.h>
  #define HAVE_SETJMP
  
  #if defined(_MSC_VER)
  // disable warning 4324: structure was padded due to __declspec(align())
  #pragma warning(disable : 4324)
  #endif
  
  #endif
  struct FILE;  // For jpeglib.h.
  
  // C++ build requires extern C for jpeg internals.
  #ifdef __cplusplus
  extern "C" {
  #endif
  
  #include <jpeglib.h>
  
  #ifdef __cplusplus
  }  // extern "C"
  #endif
  
  #include "libyuv/planar_functions.h"  // For CopyPlane().
  
  namespace libyuv {
  
  #ifdef HAVE_SETJMP
  struct SetJmpErrorMgr {
    jpeg_error_mgr base;  // Must be at the top
    jmp_buf setjmp_buffer;
  };
  #endif
  
  const int MJpegDecoder::kColorSpaceUnknown = JCS_UNKNOWN;
  const int MJpegDecoder::kColorSpaceGrayscale = JCS_GRAYSCALE;
  const int MJpegDecoder::kColorSpaceRgb = JCS_RGB;
  const int MJpegDecoder::kColorSpaceYCbCr = JCS_YCbCr;
  const int MJpegDecoder::kColorSpaceCMYK = JCS_CMYK;
  const int MJpegDecoder::kColorSpaceYCCK = JCS_YCCK;
  
  // Methods that are passed to jpeglib.
  boolean fill_input_buffer(jpeg_decompress_struct* cinfo);
  void init_source(jpeg_decompress_struct* cinfo);
  void skip_input_data(jpeg_decompress_struct* cinfo, long num_bytes);  // NOLINT
  void term_source(jpeg_decompress_struct* cinfo);
  void ErrorHandler(jpeg_common_struct* cinfo);
  void OutputHandler(jpeg_common_struct* cinfo);
  
  MJpegDecoder::MJpegDecoder()
      : has_scanline_padding_(LIBYUV_FALSE),
        num_outbufs_(0),
        scanlines_(NULL),
        scanlines_sizes_(NULL),
        databuf_(NULL),
        databuf_strides_(NULL) {
    decompress_struct_ = new jpeg_decompress_struct;
    source_mgr_ = new jpeg_source_mgr;
  #ifdef HAVE_SETJMP
    error_mgr_ = new SetJmpErrorMgr;
    decompress_struct_->err = jpeg_std_error(&error_mgr_->base);
    // Override standard exit()-based error handler.
    error_mgr_->base.error_exit = &ErrorHandler;
    error_mgr_->base.output_message = &OutputHandler;
  #endif
    decompress_struct_->client_data = NULL;
    source_mgr_->init_source = &init_source;
    source_mgr_->fill_input_buffer = &fill_input_buffer;
    source_mgr_->skip_input_data = &skip_input_data;
    source_mgr_->resync_to_restart = &jpeg_resync_to_restart;
    source_mgr_->term_source = &term_source;
    jpeg_create_decompress(decompress_struct_);
    decompress_struct_->src = source_mgr_;
    buf_vec_.buffers = &buf_;
    buf_vec_.len = 1;
  }
  
  MJpegDecoder::~MJpegDecoder() {
    jpeg_destroy_decompress(decompress_struct_);
    delete decompress_struct_;
    delete source_mgr_;
  #ifdef HAVE_SETJMP
    delete error_mgr_;
  #endif
    DestroyOutputBuffers();
  }
  
  LIBYUV_BOOL MJpegDecoder::LoadFrame(const uint8_t* src, size_t src_len) {
    if (!ValidateJpeg(src, src_len)) {
      return LIBYUV_FALSE;
    }
  
    buf_.data = src;
    buf_.len = static_cast<int>(src_len);
    buf_vec_.pos = 0;
    decompress_struct_->client_data = &buf_vec_;
  #ifdef HAVE_SETJMP
    if (setjmp(error_mgr_->setjmp_buffer)) {
      // We called jpeg_read_header, it experienced an error, and we called
      // longjmp() and rewound the stack to here. Return error.
      return LIBYUV_FALSE;
    }
  #endif
    if (jpeg_read_header(decompress_struct_, TRUE) != JPEG_HEADER_OK) {
      // ERROR: Bad MJPEG header
      return LIBYUV_FALSE;
    }
    AllocOutputBuffers(GetNumComponents());
    for (int i = 0; i < num_outbufs_; ++i) {
      int scanlines_size = GetComponentScanlinesPerImcuRow(i);
      if (scanlines_sizes_[i] != scanlines_size) {
        if (scanlines_[i]) {
          delete scanlines_[i];
        }
        scanlines_[i] = new uint8_t*[scanlines_size];
        scanlines_sizes_[i] = scanlines_size;
      }
  
      // We allocate padding for the final scanline to pad it up to DCTSIZE bytes
      // to avoid memory errors, since jpeglib only reads full MCUs blocks. For
      // the preceding scanlines, the padding is not needed/wanted because the
      // following addresses will already be valid (they are the initial bytes of
      // the next scanline) and will be overwritten when jpeglib writes out that
      // next scanline.
      int databuf_stride = GetComponentStride(i);
      int databuf_size = scanlines_size * databuf_stride;
      if (databuf_strides_[i] != databuf_stride) {
        if (databuf_[i]) {
          delete databuf_[i];
        }
        databuf_[i] = new uint8_t[databuf_size];
        databuf_strides_[i] = databuf_stride;
      }
  
      if (GetComponentStride(i) != GetComponentWidth(i)) {
        has_scanline_padding_ = LIBYUV_TRUE;
      }
    }
    return LIBYUV_TRUE;
  }
  
  static int DivideAndRoundUp(int numerator, int denominator) {
    return (numerator + denominator - 1) / denominator;
  }
  
  static int DivideAndRoundDown(int numerator, int denominator) {
    return numerator / denominator;
  }
  
  // Returns width of the last loaded frame.
  int MJpegDecoder::GetWidth() {
    return decompress_struct_->image_width;
  }
  
  // Returns height of the last loaded frame.
  int MJpegDecoder::GetHeight() {
    return decompress_struct_->image_height;
  }
  
  // Returns format of the last loaded frame. The return value is one of the
  // kColorSpace* constants.
  int MJpegDecoder::GetColorSpace() {
    return decompress_struct_->jpeg_color_space;
  }
  
  // Number of color components in the color space.
  int MJpegDecoder::GetNumComponents() {
    return decompress_struct_->num_components;
  }
  
  // Sample factors of the n-th component.
  int MJpegDecoder::GetHorizSampFactor(int component) {
    return decompress_struct_->comp_info[component].h_samp_factor;
  }
  
  int MJpegDecoder::GetVertSampFactor(int component) {
    return decompress_struct_->comp_info[component].v_samp_factor;
  }
  
  int MJpegDecoder::GetHorizSubSampFactor(int component) {
    return decompress_struct_->max_h_samp_factor / GetHorizSampFactor(component);
  }
  
  int MJpegDecoder::GetVertSubSampFactor(int component) {
    return decompress_struct_->max_v_samp_factor / GetVertSampFactor(component);
  }
  
  int MJpegDecoder::GetImageScanlinesPerImcuRow() {
    return decompress_struct_->max_v_samp_factor * DCTSIZE;
  }
  
  int MJpegDecoder::GetComponentScanlinesPerImcuRow(int component) {
    int vs = GetVertSubSampFactor(component);
    return DivideAndRoundUp(GetImageScanlinesPerImcuRow(), vs);
  }
  
  int MJpegDecoder::GetComponentWidth(int component) {
    int hs = GetHorizSubSampFactor(component);
    return DivideAndRoundUp(GetWidth(), hs);
  }
  
  int MJpegDecoder::GetComponentHeight(int component) {
    int vs = GetVertSubSampFactor(component);
    return DivideAndRoundUp(GetHeight(), vs);
  }
  
  // Get width in bytes padded out to a multiple of DCTSIZE
  int MJpegDecoder::GetComponentStride(int component) {
    return (GetComponentWidth(component) + DCTSIZE - 1) & ~(DCTSIZE - 1);
  }
  
  int MJpegDecoder::GetComponentSize(int component) {
    return GetComponentWidth(component) * GetComponentHeight(component);
  }
  
  LIBYUV_BOOL MJpegDecoder::UnloadFrame() {
  #ifdef HAVE_SETJMP
    if (setjmp(error_mgr_->setjmp_buffer)) {
      // We called jpeg_abort_decompress, it experienced an error, and we called
      // longjmp() and rewound the stack to here. Return error.
      return LIBYUV_FALSE;
    }
  #endif
    jpeg_abort_decompress(decompress_struct_);
    return LIBYUV_TRUE;
  }
  
  // TODO(fbarchard): Allow rectangle to be specified: x, y, width, height.
  LIBYUV_BOOL MJpegDecoder::DecodeToBuffers(uint8_t** planes,
                                            int dst_width,
                                            int dst_height) {
    if (dst_width != GetWidth() || dst_height > GetHeight()) {
      // ERROR: Bad dimensions
      return LIBYUV_FALSE;
    }
  #ifdef HAVE_SETJMP
    if (setjmp(error_mgr_->setjmp_buffer)) {
      // We called into jpeglib, it experienced an error sometime during this
      // function call, and we called longjmp() and rewound the stack to here.
      // Return error.
      return LIBYUV_FALSE;
    }
  #endif
    if (!StartDecode()) {
      return LIBYUV_FALSE;
    }
    SetScanlinePointers(databuf_);
    int lines_left = dst_height;
    // Compute amount of lines to skip to implement vertical crop.
    // TODO(fbarchard): Ensure skip is a multiple of maximum component
    // subsample. ie 2
    int skip = (GetHeight() - dst_height) / 2;
    if (skip > 0) {
      // There is no API to skip lines in the output data, so we read them
      // into the temp buffer.
      while (skip >= GetImageScanlinesPerImcuRow()) {
        if (!DecodeImcuRow()) {
          FinishDecode();
          return LIBYUV_FALSE;
        }
        skip -= GetImageScanlinesPerImcuRow();
      }
      if (skip > 0) {
        // Have a partial iMCU row left over to skip. Must read it and then
        // copy the parts we want into the destination.
        if (!DecodeImcuRow()) {
          FinishDecode();
          return LIBYUV_FALSE;
        }
        for (int i = 0; i < num_outbufs_; ++i) {
          // TODO(fbarchard): Compute skip to avoid this
          assert(skip % GetVertSubSampFactor(i) == 0);
          int rows_to_skip = DivideAndRoundDown(skip, GetVertSubSampFactor(i));
          int scanlines_to_copy =
              GetComponentScanlinesPerImcuRow(i) - rows_to_skip;
          int data_to_skip = rows_to_skip * GetComponentStride(i);
          CopyPlane(databuf_[i] + data_to_skip, GetComponentStride(i), planes[i],
                    GetComponentWidth(i), GetComponentWidth(i),
                    scanlines_to_copy);
          planes[i] += scanlines_to_copy * GetComponentWidth(i);
        }
        lines_left -= (GetImageScanlinesPerImcuRow() - skip);
      }
    }
  
    // Read full MCUs but cropped horizontally
    for (; lines_left > GetImageScanlinesPerImcuRow();
         lines_left -= GetImageScanlinesPerImcuRow()) {
      if (!DecodeImcuRow()) {
        FinishDecode();
        return LIBYUV_FALSE;
      }
      for (int i = 0; i < num_outbufs_; ++i) {
        int scanlines_to_copy = GetComponentScanlinesPerImcuRow(i);
        CopyPlane(databuf_[i], GetComponentStride(i), planes[i],
                  GetComponentWidth(i), GetComponentWidth(i), scanlines_to_copy);
        planes[i] += scanlines_to_copy * GetComponentWidth(i);
      }
    }
  
    if (lines_left > 0) {
      // Have a partial iMCU row left over to decode.
      if (!DecodeImcuRow()) {
        FinishDecode();
        return LIBYUV_FALSE;
      }
      for (int i = 0; i < num_outbufs_; ++i) {
        int scanlines_to_copy =
            DivideAndRoundUp(lines_left, GetVertSubSampFactor(i));
        CopyPlane(databuf_[i], GetComponentStride(i), planes[i],
                  GetComponentWidth(i), GetComponentWidth(i), scanlines_to_copy);
        planes[i] += scanlines_to_copy * GetComponentWidth(i);
      }
    }
    return FinishDecode();
  }
  
  LIBYUV_BOOL MJpegDecoder::DecodeToCallback(CallbackFunction fn,
                                             void* opaque,
                                             int dst_width,
                                             int dst_height) {
    if (dst_width != GetWidth() || dst_height > GetHeight()) {
      // ERROR: Bad dimensions
      return LIBYUV_FALSE;
    }
  #ifdef HAVE_SETJMP
    if (setjmp(error_mgr_->setjmp_buffer)) {
      // We called into jpeglib, it experienced an error sometime during this
      // function call, and we called longjmp() and rewound the stack to here.
      // Return error.
      return LIBYUV_FALSE;
    }
  #endif
    if (!StartDecode()) {
      return LIBYUV_FALSE;
    }
    SetScanlinePointers(databuf_);
    int lines_left = dst_height;
    // TODO(fbarchard): Compute amount of lines to skip to implement vertical crop
    int skip = (GetHeight() - dst_height) / 2;
    if (skip > 0) {
      while (skip >= GetImageScanlinesPerImcuRow()) {
        if (!DecodeImcuRow()) {
          FinishDecode();
          return LIBYUV_FALSE;
        }
        skip -= GetImageScanlinesPerImcuRow();
      }
      if (skip > 0) {
        // Have a partial iMCU row left over to skip.
        if (!DecodeImcuRow()) {
          FinishDecode();
          return LIBYUV_FALSE;
        }
        for (int i = 0; i < num_outbufs_; ++i) {
          // TODO(fbarchard): Compute skip to avoid this
          assert(skip % GetVertSubSampFactor(i) == 0);
          int rows_to_skip = DivideAndRoundDown(skip, GetVertSubSampFactor(i));
          int data_to_skip = rows_to_skip * GetComponentStride(i);
          // Change our own data buffer pointers so we can pass them to the
          // callback.
          databuf_[i] += data_to_skip;
        }
        int scanlines_to_copy = GetImageScanlinesPerImcuRow() - skip;
        (*fn)(opaque, databuf_, databuf_strides_, scanlines_to_copy);
        // Now change them back.
        for (int i = 0; i < num_outbufs_; ++i) {
          int rows_to_skip = DivideAndRoundDown(skip, GetVertSubSampFactor(i));
          int data_to_skip = rows_to_skip * GetComponentStride(i);
          databuf_[i] -= data_to_skip;
        }
        lines_left -= scanlines_to_copy;
      }
    }
    // Read full MCUs until we get to the crop point.
    for (; lines_left >= GetImageScanlinesPerImcuRow();
         lines_left -= GetImageScanlinesPerImcuRow()) {
      if (!DecodeImcuRow()) {
        FinishDecode();
        return LIBYUV_FALSE;
      }
      (*fn)(opaque, databuf_, databuf_strides_, GetImageScanlinesPerImcuRow());
    }
    if (lines_left > 0) {
      // Have a partial iMCU row left over to decode.
      if (!DecodeImcuRow()) {
        FinishDecode();
        return LIBYUV_FALSE;
      }
      (*fn)(opaque, databuf_, databuf_strides_, lines_left);
    }
    return FinishDecode();
  }
  
  void init_source(j_decompress_ptr cinfo) {
    fill_input_buffer(cinfo);
  }
  
  boolean fill_input_buffer(j_decompress_ptr cinfo) {
    BufferVector* buf_vec = reinterpret_cast<BufferVector*>(cinfo->client_data);
    if (buf_vec->pos >= buf_vec->len) {
      assert(0 && "No more data");
      // ERROR: No more data
      return FALSE;
    }
    cinfo->src->next_input_byte = buf_vec->buffers[buf_vec->pos].data;
    cinfo->src->bytes_in_buffer = buf_vec->buffers[buf_vec->pos].len;
    ++buf_vec->pos;
    return TRUE;
  }
  
  void skip_input_data(j_decompress_ptr cinfo, long num_bytes) {  // NOLINT
    cinfo->src->next_input_byte += num_bytes;
  }
  
  void term_source(j_decompress_ptr cinfo) {
    (void)cinfo;  // Nothing to do.
  }
  
  #ifdef HAVE_SETJMP
  void ErrorHandler(j_common_ptr cinfo) {
  // This is called when a jpeglib command experiences an error. Unfortunately
  // jpeglib's error handling model is not very flexible, because it expects the
  // error handler to not return--i.e., it wants the program to terminate. To
  // recover from errors we use setjmp() as shown in their example. setjmp() is
  // C's implementation for the "call with current continuation" functionality
  // seen in some functional programming languages.
  // A formatted message can be output, but is unsafe for release.
  #ifdef DEBUG
    char buf[JMSG_LENGTH_MAX];
    (*cinfo->err->format_message)(cinfo, buf);
  // ERROR: Error in jpeglib: buf
  #endif
  
    SetJmpErrorMgr* mgr = reinterpret_cast<SetJmpErrorMgr*>(cinfo->err);
    // This rewinds the call stack to the point of the corresponding setjmp()
    // and causes it to return (for a second time) with value 1.
    longjmp(mgr->setjmp_buffer, 1);
  }
  
  // Suppress fprintf warnings.
  void OutputHandler(j_common_ptr cinfo) {
    (void)cinfo;
  }
  
  #endif  // HAVE_SETJMP
  
  void MJpegDecoder::AllocOutputBuffers(int num_outbufs) {
    if (num_outbufs != num_outbufs_) {
      // We could perhaps optimize this case to resize the output buffers without
      // necessarily having to delete and recreate each one, but it's not worth
      // it.
      DestroyOutputBuffers();
  
      scanlines_ = new uint8_t**[num_outbufs];
      scanlines_sizes_ = new int[num_outbufs];
      databuf_ = new uint8_t*[num_outbufs];
      databuf_strides_ = new int[num_outbufs];
  
      for (int i = 0; i < num_outbufs; ++i) {
        scanlines_[i] = NULL;
        scanlines_sizes_[i] = 0;
        databuf_[i] = NULL;
        databuf_strides_[i] = 0;
      }
  
      num_outbufs_ = num_outbufs;
    }
  }
  
  void MJpegDecoder::DestroyOutputBuffers() {
    for (int i = 0; i < num_outbufs_; ++i) {
      delete[] scanlines_[i];
      delete[] databuf_[i];
    }
    delete[] scanlines_;
    delete[] databuf_;
    delete[] scanlines_sizes_;
    delete[] databuf_strides_;
    scanlines_ = NULL;
    databuf_ = NULL;
    scanlines_sizes_ = NULL;
    databuf_strides_ = NULL;
    num_outbufs_ = 0;
  }
  
  // JDCT_IFAST and do_block_smoothing improve performance substantially.
  LIBYUV_BOOL MJpegDecoder::StartDecode() {
    decompress_struct_->raw_data_out = TRUE;
    decompress_struct_->dct_method = JDCT_IFAST;  // JDCT_ISLOW is default
    decompress_struct_->dither_mode = JDITHER_NONE;
    // Not applicable to 'raw':
    decompress_struct_->do_fancy_upsampling = (boolean)(LIBYUV_FALSE);
    // Only for buffered mode:
    decompress_struct_->enable_2pass_quant = (boolean)(LIBYUV_FALSE);
    // Blocky but fast:
    decompress_struct_->do_block_smoothing = (boolean)(LIBYUV_FALSE);
  
    if (!jpeg_start_decompress(decompress_struct_)) {
      // ERROR: Couldn't start JPEG decompressor";
      return LIBYUV_FALSE;
    }
    return LIBYUV_TRUE;
  }
  
  LIBYUV_BOOL MJpegDecoder::FinishDecode() {
    // jpeglib considers it an error if we finish without decoding the whole
    // image, so we call "abort" rather than "finish".
    jpeg_abort_decompress(decompress_struct_);
    return LIBYUV_TRUE;
  }
  
  void MJpegDecoder::SetScanlinePointers(uint8_t** data) {
    for (int i = 0; i < num_outbufs_; ++i) {
      uint8_t* data_i = data[i];
      for (int j = 0; j < scanlines_sizes_[i]; ++j) {
        scanlines_[i][j] = data_i;
        data_i += GetComponentStride(i);
      }
    }
  }
  
  inline LIBYUV_BOOL MJpegDecoder::DecodeImcuRow() {
    return (unsigned int)(GetImageScanlinesPerImcuRow()) ==
           jpeg_read_raw_data(decompress_struct_, scanlines_,
                              GetImageScanlinesPerImcuRow());
  }
  
  // The helper function which recognizes the jpeg sub-sampling type.
  JpegSubsamplingType MJpegDecoder::JpegSubsamplingTypeHelper(
      int* subsample_x,
      int* subsample_y,
      int number_of_components) {
    if (number_of_components == 3) {  // Color images.
      if (subsample_x[0] == 1 && subsample_y[0] == 1 && subsample_x[1] == 2 &&
          subsample_y[1] == 2 && subsample_x[2] == 2 && subsample_y[2] == 2) {
        return kJpegYuv420;
      }
      if (subsample_x[0] == 1 && subsample_y[0] == 1 && subsample_x[1] == 2 &&
          subsample_y[1] == 1 && subsample_x[2] == 2 && subsample_y[2] == 1) {
        return kJpegYuv422;
      }
      if (subsample_x[0] == 1 && subsample_y[0] == 1 && subsample_x[1] == 1 &&
          subsample_y[1] == 1 && subsample_x[2] == 1 && subsample_y[2] == 1) {
        return kJpegYuv444;
      }
    } else if (number_of_components == 1) {  // Grey-scale images.
      if (subsample_x[0] == 1 && subsample_y[0] == 1) {
        return kJpegYuv400;
      }
    }
    return kJpegUnknown;
  }
  
  }  // namespace libyuv
  #endif  // HAVE_JPEG
  