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