• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Copyright 2012 The LibYuv Project Authors. All rights reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS. All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 #include "libyuv/mjpeg_decoder.h"
12 
13 #ifdef HAVE_JPEG
14 #include <assert.h>
15 
16 #if !defined(__pnacl__) && !defined(__CLR_VER) && \
17     !defined(COVERAGE_ENABLED) && !defined(TARGET_IPHONE_SIMULATOR)
18 // Must be included before jpeglib.
19 #include <setjmp.h>
20 #define HAVE_SETJMP
21 
22 #if defined(_MSC_VER)
23 // disable warning 4324: structure was padded due to __declspec(align())
24 #pragma warning(disable : 4324)
25 #endif
26 
27 #endif
28 
29 #include <stdio.h>  // For jpeglib.h.
30 
31 // C++ build requires extern C for jpeg internals.
32 #ifdef __cplusplus
33 extern "C" {
34 #endif
35 
36 #include <jpeglib.h>
37 
38 #ifdef __cplusplus
39 }  // extern "C"
40 #endif
41 
42 #include "libyuv/planar_functions.h"  // For CopyPlane().
43 
44 namespace libyuv {
45 
46 #ifdef HAVE_SETJMP
47 struct SetJmpErrorMgr {
48   jpeg_error_mgr base;  // Must be at the top
49   jmp_buf setjmp_buffer;
50 };
51 #endif
52 
53 const int MJpegDecoder::kColorSpaceUnknown = JCS_UNKNOWN;
54 const int MJpegDecoder::kColorSpaceGrayscale = JCS_GRAYSCALE;
55 const int MJpegDecoder::kColorSpaceRgb = JCS_RGB;
56 const int MJpegDecoder::kColorSpaceYCbCr = JCS_YCbCr;
57 const int MJpegDecoder::kColorSpaceCMYK = JCS_CMYK;
58 const int MJpegDecoder::kColorSpaceYCCK = JCS_YCCK;
59 
60 // Methods that are passed to jpeglib.
61 boolean fill_input_buffer(jpeg_decompress_struct* cinfo);
62 void init_source(jpeg_decompress_struct* cinfo);
63 void skip_input_data(jpeg_decompress_struct* cinfo, long num_bytes);  // NOLINT
64 void term_source(jpeg_decompress_struct* cinfo);
65 void ErrorHandler(jpeg_common_struct* cinfo);
66 void OutputHandler(jpeg_common_struct* cinfo);
67 
MJpegDecoder()68 MJpegDecoder::MJpegDecoder()
69     : has_scanline_padding_(LIBYUV_FALSE),
70       num_outbufs_(0),
71       scanlines_(NULL),
72       scanlines_sizes_(NULL),
73       databuf_(NULL),
74       databuf_strides_(NULL) {
75   decompress_struct_ = new jpeg_decompress_struct;
76   source_mgr_ = new jpeg_source_mgr;
77 #ifdef HAVE_SETJMP
78   error_mgr_ = new SetJmpErrorMgr;
79   decompress_struct_->err = jpeg_std_error(&error_mgr_->base);
80   // Override standard exit()-based error handler.
81   error_mgr_->base.error_exit = &ErrorHandler;
82   error_mgr_->base.output_message = &OutputHandler;
83 #endif
84   decompress_struct_->client_data = NULL;
85   source_mgr_->init_source = &init_source;
86   source_mgr_->fill_input_buffer = &fill_input_buffer;
87   source_mgr_->skip_input_data = &skip_input_data;
88   source_mgr_->resync_to_restart = &jpeg_resync_to_restart;
89   source_mgr_->term_source = &term_source;
90   jpeg_create_decompress(decompress_struct_);
91   decompress_struct_->src = source_mgr_;
92   buf_vec_.buffers = &buf_;
93   buf_vec_.len = 1;
94 }
95 
~MJpegDecoder()96 MJpegDecoder::~MJpegDecoder() {
97   jpeg_destroy_decompress(decompress_struct_);
98   delete decompress_struct_;
99   delete source_mgr_;
100 #ifdef HAVE_SETJMP
101   delete error_mgr_;
102 #endif
103   DestroyOutputBuffers();
104 }
105 
LoadFrame(const uint8_t * src,size_t src_len)106 LIBYUV_BOOL MJpegDecoder::LoadFrame(const uint8_t* src, size_t src_len) {
107   if (!ValidateJpeg(src, src_len)) {
108     return LIBYUV_FALSE;
109   }
110 
111   buf_.data = src;
112   buf_.len = static_cast<int>(src_len);
113   buf_vec_.pos = 0;
114   decompress_struct_->client_data = &buf_vec_;
115 #ifdef HAVE_SETJMP
116   if (setjmp(error_mgr_->setjmp_buffer)) {
117     // We called jpeg_read_header, it experienced an error, and we called
118     // longjmp() and rewound the stack to here. Return error.
119     return LIBYUV_FALSE;
120   }
121 #endif
122   if (jpeg_read_header(decompress_struct_, TRUE) != JPEG_HEADER_OK) {
123     // ERROR: Bad MJPEG header
124     return LIBYUV_FALSE;
125   }
126   AllocOutputBuffers(GetNumComponents());
127   for (int i = 0; i < num_outbufs_; ++i) {
128     int scanlines_size = GetComponentScanlinesPerImcuRow(i);
129     if (scanlines_sizes_[i] != scanlines_size) {
130       if (scanlines_[i]) {
131         delete scanlines_[i];
132       }
133       scanlines_[i] = new uint8_t*[scanlines_size];
134       scanlines_sizes_[i] = scanlines_size;
135     }
136 
137     // We allocate padding for the final scanline to pad it up to DCTSIZE bytes
138     // to avoid memory errors, since jpeglib only reads full MCUs blocks. For
139     // the preceding scanlines, the padding is not needed/wanted because the
140     // following addresses will already be valid (they are the initial bytes of
141     // the next scanline) and will be overwritten when jpeglib writes out that
142     // next scanline.
143     int databuf_stride = GetComponentStride(i);
144     int databuf_size = scanlines_size * databuf_stride;
145     if (databuf_strides_[i] != databuf_stride) {
146       if (databuf_[i]) {
147         delete databuf_[i];
148       }
149       databuf_[i] = new uint8_t[databuf_size];
150       databuf_strides_[i] = databuf_stride;
151     }
152 
153     if (GetComponentStride(i) != GetComponentWidth(i)) {
154       has_scanline_padding_ = LIBYUV_TRUE;
155     }
156   }
157   return LIBYUV_TRUE;
158 }
159 
DivideAndRoundUp(int numerator,int denominator)160 static int DivideAndRoundUp(int numerator, int denominator) {
161   return (numerator + denominator - 1) / denominator;
162 }
163 
DivideAndRoundDown(int numerator,int denominator)164 static int DivideAndRoundDown(int numerator, int denominator) {
165   return numerator / denominator;
166 }
167 
168 // Returns width of the last loaded frame.
GetWidth()169 int MJpegDecoder::GetWidth() {
170   return decompress_struct_->image_width;
171 }
172 
173 // Returns height of the last loaded frame.
GetHeight()174 int MJpegDecoder::GetHeight() {
175   return decompress_struct_->image_height;
176 }
177 
178 // Returns format of the last loaded frame. The return value is one of the
179 // kColorSpace* constants.
GetColorSpace()180 int MJpegDecoder::GetColorSpace() {
181   return decompress_struct_->jpeg_color_space;
182 }
183 
184 // Number of color components in the color space.
GetNumComponents()185 int MJpegDecoder::GetNumComponents() {
186   return decompress_struct_->num_components;
187 }
188 
189 // Sample factors of the n-th component.
GetHorizSampFactor(int component)190 int MJpegDecoder::GetHorizSampFactor(int component) {
191   return decompress_struct_->comp_info[component].h_samp_factor;
192 }
193 
GetVertSampFactor(int component)194 int MJpegDecoder::GetVertSampFactor(int component) {
195   return decompress_struct_->comp_info[component].v_samp_factor;
196 }
197 
GetHorizSubSampFactor(int component)198 int MJpegDecoder::GetHorizSubSampFactor(int component) {
199   return decompress_struct_->max_h_samp_factor / GetHorizSampFactor(component);
200 }
201 
GetVertSubSampFactor(int component)202 int MJpegDecoder::GetVertSubSampFactor(int component) {
203   return decompress_struct_->max_v_samp_factor / GetVertSampFactor(component);
204 }
205 
GetImageScanlinesPerImcuRow()206 int MJpegDecoder::GetImageScanlinesPerImcuRow() {
207   return decompress_struct_->max_v_samp_factor * DCTSIZE;
208 }
209 
GetComponentScanlinesPerImcuRow(int component)210 int MJpegDecoder::GetComponentScanlinesPerImcuRow(int component) {
211   int vs = GetVertSubSampFactor(component);
212   return DivideAndRoundUp(GetImageScanlinesPerImcuRow(), vs);
213 }
214 
GetComponentWidth(int component)215 int MJpegDecoder::GetComponentWidth(int component) {
216   int hs = GetHorizSubSampFactor(component);
217   return DivideAndRoundUp(GetWidth(), hs);
218 }
219 
GetComponentHeight(int component)220 int MJpegDecoder::GetComponentHeight(int component) {
221   int vs = GetVertSubSampFactor(component);
222   return DivideAndRoundUp(GetHeight(), vs);
223 }
224 
225 // Get width in bytes padded out to a multiple of DCTSIZE
GetComponentStride(int component)226 int MJpegDecoder::GetComponentStride(int component) {
227   return (GetComponentWidth(component) + DCTSIZE - 1) & ~(DCTSIZE - 1);
228 }
229 
GetComponentSize(int component)230 int MJpegDecoder::GetComponentSize(int component) {
231   return GetComponentWidth(component) * GetComponentHeight(component);
232 }
233 
UnloadFrame()234 LIBYUV_BOOL MJpegDecoder::UnloadFrame() {
235 #ifdef HAVE_SETJMP
236   if (setjmp(error_mgr_->setjmp_buffer)) {
237     // We called jpeg_abort_decompress, it experienced an error, and we called
238     // longjmp() and rewound the stack to here. Return error.
239     return LIBYUV_FALSE;
240   }
241 #endif
242   jpeg_abort_decompress(decompress_struct_);
243   return LIBYUV_TRUE;
244 }
245 
246 // TODO(fbarchard): Allow rectangle to be specified: x, y, width, height.
DecodeToBuffers(uint8_t ** planes,int dst_width,int dst_height)247 LIBYUV_BOOL MJpegDecoder::DecodeToBuffers(uint8_t** planes,
248                                           int dst_width,
249                                           int dst_height) {
250   if (dst_width != GetWidth() || dst_height > GetHeight()) {
251     // ERROR: Bad dimensions
252     return LIBYUV_FALSE;
253   }
254 #ifdef HAVE_SETJMP
255   if (setjmp(error_mgr_->setjmp_buffer)) {
256     // We called into jpeglib, it experienced an error sometime during this
257     // function call, and we called longjmp() and rewound the stack to here.
258     // Return error.
259     return LIBYUV_FALSE;
260   }
261 #endif
262   if (!StartDecode()) {
263     return LIBYUV_FALSE;
264   }
265   SetScanlinePointers(databuf_);
266   int lines_left = dst_height;
267   // Compute amount of lines to skip to implement vertical crop.
268   // TODO(fbarchard): Ensure skip is a multiple of maximum component
269   // subsample. ie 2
270   int skip = (GetHeight() - dst_height) / 2;
271   if (skip > 0) {
272     // There is no API to skip lines in the output data, so we read them
273     // into the temp buffer.
274     while (skip >= GetImageScanlinesPerImcuRow()) {
275       if (!DecodeImcuRow()) {
276         FinishDecode();
277         return LIBYUV_FALSE;
278       }
279       skip -= GetImageScanlinesPerImcuRow();
280     }
281     if (skip > 0) {
282       // Have a partial iMCU row left over to skip. Must read it and then
283       // copy the parts we want into the destination.
284       if (!DecodeImcuRow()) {
285         FinishDecode();
286         return LIBYUV_FALSE;
287       }
288       for (int i = 0; i < num_outbufs_; ++i) {
289         // TODO(fbarchard): Compute skip to avoid this
290         assert(skip % GetVertSubSampFactor(i) == 0);
291         int rows_to_skip = DivideAndRoundDown(skip, GetVertSubSampFactor(i));
292         int scanlines_to_copy =
293             GetComponentScanlinesPerImcuRow(i) - rows_to_skip;
294         int data_to_skip = rows_to_skip * GetComponentStride(i);
295         CopyPlane(databuf_[i] + data_to_skip, GetComponentStride(i), planes[i],
296                   GetComponentWidth(i), GetComponentWidth(i),
297                   scanlines_to_copy);
298         planes[i] += scanlines_to_copy * GetComponentWidth(i);
299       }
300       lines_left -= (GetImageScanlinesPerImcuRow() - skip);
301     }
302   }
303 
304   // Read full MCUs but cropped horizontally
305   for (; lines_left > GetImageScanlinesPerImcuRow();
306        lines_left -= GetImageScanlinesPerImcuRow()) {
307     if (!DecodeImcuRow()) {
308       FinishDecode();
309       return LIBYUV_FALSE;
310     }
311     for (int i = 0; i < num_outbufs_; ++i) {
312       int scanlines_to_copy = GetComponentScanlinesPerImcuRow(i);
313       CopyPlane(databuf_[i], GetComponentStride(i), planes[i],
314                 GetComponentWidth(i), GetComponentWidth(i), scanlines_to_copy);
315       planes[i] += scanlines_to_copy * GetComponentWidth(i);
316     }
317   }
318 
319   if (lines_left > 0) {
320     // Have a partial iMCU row left over to decode.
321     if (!DecodeImcuRow()) {
322       FinishDecode();
323       return LIBYUV_FALSE;
324     }
325     for (int i = 0; i < num_outbufs_; ++i) {
326       int scanlines_to_copy =
327           DivideAndRoundUp(lines_left, GetVertSubSampFactor(i));
328       CopyPlane(databuf_[i], GetComponentStride(i), planes[i],
329                 GetComponentWidth(i), GetComponentWidth(i), scanlines_to_copy);
330       planes[i] += scanlines_to_copy * GetComponentWidth(i);
331     }
332   }
333   return FinishDecode();
334 }
335 
DecodeToCallback(CallbackFunction fn,void * opaque,int dst_width,int dst_height)336 LIBYUV_BOOL MJpegDecoder::DecodeToCallback(CallbackFunction fn,
337                                            void* opaque,
338                                            int dst_width,
339                                            int dst_height) {
340   if (dst_width != GetWidth() || dst_height > GetHeight()) {
341     // ERROR: Bad dimensions
342     return LIBYUV_FALSE;
343   }
344 #ifdef HAVE_SETJMP
345   if (setjmp(error_mgr_->setjmp_buffer)) {
346     // We called into jpeglib, it experienced an error sometime during this
347     // function call, and we called longjmp() and rewound the stack to here.
348     // Return error.
349     return LIBYUV_FALSE;
350   }
351 #endif
352   if (!StartDecode()) {
353     return LIBYUV_FALSE;
354   }
355   SetScanlinePointers(databuf_);
356   int lines_left = dst_height;
357   // TODO(fbarchard): Compute amount of lines to skip to implement vertical crop
358   int skip = (GetHeight() - dst_height) / 2;
359   if (skip > 0) {
360     while (skip >= GetImageScanlinesPerImcuRow()) {
361       if (!DecodeImcuRow()) {
362         FinishDecode();
363         return LIBYUV_FALSE;
364       }
365       skip -= GetImageScanlinesPerImcuRow();
366     }
367     if (skip > 0) {
368       // Have a partial iMCU row left over to skip.
369       if (!DecodeImcuRow()) {
370         FinishDecode();
371         return LIBYUV_FALSE;
372       }
373       for (int i = 0; i < num_outbufs_; ++i) {
374         // TODO(fbarchard): Compute skip to avoid this
375         assert(skip % GetVertSubSampFactor(i) == 0);
376         int rows_to_skip = DivideAndRoundDown(skip, GetVertSubSampFactor(i));
377         int data_to_skip = rows_to_skip * GetComponentStride(i);
378         // Change our own data buffer pointers so we can pass them to the
379         // callback.
380         databuf_[i] += data_to_skip;
381       }
382       int scanlines_to_copy = GetImageScanlinesPerImcuRow() - skip;
383       (*fn)(opaque, databuf_, databuf_strides_, scanlines_to_copy);
384       // Now change them back.
385       for (int i = 0; i < num_outbufs_; ++i) {
386         int rows_to_skip = DivideAndRoundDown(skip, GetVertSubSampFactor(i));
387         int data_to_skip = rows_to_skip * GetComponentStride(i);
388         databuf_[i] -= data_to_skip;
389       }
390       lines_left -= scanlines_to_copy;
391     }
392   }
393   // Read full MCUs until we get to the crop point.
394   for (; lines_left >= GetImageScanlinesPerImcuRow();
395        lines_left -= GetImageScanlinesPerImcuRow()) {
396     if (!DecodeImcuRow()) {
397       FinishDecode();
398       return LIBYUV_FALSE;
399     }
400     (*fn)(opaque, databuf_, databuf_strides_, GetImageScanlinesPerImcuRow());
401   }
402   if (lines_left > 0) {
403     // Have a partial iMCU row left over to decode.
404     if (!DecodeImcuRow()) {
405       FinishDecode();
406       return LIBYUV_FALSE;
407     }
408     (*fn)(opaque, databuf_, databuf_strides_, lines_left);
409   }
410   return FinishDecode();
411 }
412 
init_source(j_decompress_ptr cinfo)413 void init_source(j_decompress_ptr cinfo) {
414   fill_input_buffer(cinfo);
415 }
416 
fill_input_buffer(j_decompress_ptr cinfo)417 boolean fill_input_buffer(j_decompress_ptr cinfo) {
418   BufferVector* buf_vec = reinterpret_cast<BufferVector*>(cinfo->client_data);
419   if (buf_vec->pos >= buf_vec->len) {
420     assert(0 && "No more data");
421     // ERROR: No more data
422     return FALSE;
423   }
424   cinfo->src->next_input_byte = buf_vec->buffers[buf_vec->pos].data;
425   cinfo->src->bytes_in_buffer = buf_vec->buffers[buf_vec->pos].len;
426   ++buf_vec->pos;
427   return TRUE;
428 }
429 
skip_input_data(j_decompress_ptr cinfo,long num_bytes)430 void skip_input_data(j_decompress_ptr cinfo, long num_bytes) {  // NOLINT
431   jpeg_source_mgr* src = cinfo->src;
432   size_t bytes = static_cast<size_t>(num_bytes);
433   if(bytes > src->bytes_in_buffer) {
434     src->next_input_byte = nullptr;
435     src->bytes_in_buffer = 0;
436   } else {
437     src->next_input_byte += bytes;
438     src->bytes_in_buffer -= bytes;
439   }
440 }
441 
term_source(j_decompress_ptr cinfo)442 void term_source(j_decompress_ptr cinfo) {
443   (void)cinfo;  // Nothing to do.
444 }
445 
446 #ifdef HAVE_SETJMP
ErrorHandler(j_common_ptr cinfo)447 void ErrorHandler(j_common_ptr cinfo) {
448 // This is called when a jpeglib command experiences an error. Unfortunately
449 // jpeglib's error handling model is not very flexible, because it expects the
450 // error handler to not return--i.e., it wants the program to terminate. To
451 // recover from errors we use setjmp() as shown in their example. setjmp() is
452 // C's implementation for the "call with current continuation" functionality
453 // seen in some functional programming languages.
454 // A formatted message can be output, but is unsafe for release.
455 #ifdef DEBUG
456   char buf[JMSG_LENGTH_MAX];
457   (*cinfo->err->format_message)(cinfo, buf);
458 // ERROR: Error in jpeglib: buf
459 #endif
460 
461   SetJmpErrorMgr* mgr = reinterpret_cast<SetJmpErrorMgr*>(cinfo->err);
462   // This rewinds the call stack to the point of the corresponding setjmp()
463   // and causes it to return (for a second time) with value 1.
464   longjmp(mgr->setjmp_buffer, 1);
465 }
466 
467 // Suppress fprintf warnings.
OutputHandler(j_common_ptr cinfo)468 void OutputHandler(j_common_ptr cinfo) {
469   (void)cinfo;
470 }
471 
472 #endif  // HAVE_SETJMP
473 
AllocOutputBuffers(int num_outbufs)474 void MJpegDecoder::AllocOutputBuffers(int num_outbufs) {
475   if (num_outbufs != num_outbufs_) {
476     // We could perhaps optimize this case to resize the output buffers without
477     // necessarily having to delete and recreate each one, but it's not worth
478     // it.
479     DestroyOutputBuffers();
480 
481     scanlines_ = new uint8_t**[num_outbufs];
482     scanlines_sizes_ = new int[num_outbufs];
483     databuf_ = new uint8_t*[num_outbufs];
484     databuf_strides_ = new int[num_outbufs];
485 
486     for (int i = 0; i < num_outbufs; ++i) {
487       scanlines_[i] = NULL;
488       scanlines_sizes_[i] = 0;
489       databuf_[i] = NULL;
490       databuf_strides_[i] = 0;
491     }
492 
493     num_outbufs_ = num_outbufs;
494   }
495 }
496 
DestroyOutputBuffers()497 void MJpegDecoder::DestroyOutputBuffers() {
498   for (int i = 0; i < num_outbufs_; ++i) {
499     delete[] scanlines_[i];
500     delete[] databuf_[i];
501   }
502   delete[] scanlines_;
503   delete[] databuf_;
504   delete[] scanlines_sizes_;
505   delete[] databuf_strides_;
506   scanlines_ = NULL;
507   databuf_ = NULL;
508   scanlines_sizes_ = NULL;
509   databuf_strides_ = NULL;
510   num_outbufs_ = 0;
511 }
512 
513 // JDCT_IFAST and do_block_smoothing improve performance substantially.
StartDecode()514 LIBYUV_BOOL MJpegDecoder::StartDecode() {
515   decompress_struct_->raw_data_out = TRUE;
516   decompress_struct_->dct_method = JDCT_IFAST;  // JDCT_ISLOW is default
517   decompress_struct_->dither_mode = JDITHER_NONE;
518   // Not applicable to 'raw':
519   decompress_struct_->do_fancy_upsampling = (boolean)(LIBYUV_FALSE);
520   // Only for buffered mode:
521   decompress_struct_->enable_2pass_quant = (boolean)(LIBYUV_FALSE);
522   // Blocky but fast:
523   decompress_struct_->do_block_smoothing = (boolean)(LIBYUV_FALSE);
524 
525   if (!jpeg_start_decompress(decompress_struct_)) {
526     // ERROR: Couldn't start JPEG decompressor";
527     return LIBYUV_FALSE;
528   }
529   return LIBYUV_TRUE;
530 }
531 
FinishDecode()532 LIBYUV_BOOL MJpegDecoder::FinishDecode() {
533   // jpeglib considers it an error if we finish without decoding the whole
534   // image, so we call "abort" rather than "finish".
535   jpeg_abort_decompress(decompress_struct_);
536   return LIBYUV_TRUE;
537 }
538 
SetScanlinePointers(uint8_t ** data)539 void MJpegDecoder::SetScanlinePointers(uint8_t** data) {
540   for (int i = 0; i < num_outbufs_; ++i) {
541     uint8_t* data_i = data[i];
542     for (int j = 0; j < scanlines_sizes_[i]; ++j) {
543       scanlines_[i][j] = data_i;
544       data_i += GetComponentStride(i);
545     }
546   }
547 }
548 
DecodeImcuRow()549 inline LIBYUV_BOOL MJpegDecoder::DecodeImcuRow() {
550   return (unsigned int)(GetImageScanlinesPerImcuRow()) ==
551          jpeg_read_raw_data(decompress_struct_, scanlines_,
552                             GetImageScanlinesPerImcuRow());
553 }
554 
555 // The helper function which recognizes the jpeg sub-sampling type.
JpegSubsamplingTypeHelper(int * subsample_x,int * subsample_y,int number_of_components)556 JpegSubsamplingType MJpegDecoder::JpegSubsamplingTypeHelper(
557     int* subsample_x,
558     int* subsample_y,
559     int number_of_components) {
560   if (number_of_components == 3) {  // Color images.
561     if (subsample_x[0] == 1 && subsample_y[0] == 1 && subsample_x[1] == 2 &&
562         subsample_y[1] == 2 && subsample_x[2] == 2 && subsample_y[2] == 2) {
563       return kJpegYuv420;
564     }
565     if (subsample_x[0] == 1 && subsample_y[0] == 1 && subsample_x[1] == 2 &&
566         subsample_y[1] == 1 && subsample_x[2] == 2 && subsample_y[2] == 1) {
567       return kJpegYuv422;
568     }
569     if (subsample_x[0] == 1 && subsample_y[0] == 1 && subsample_x[1] == 1 &&
570         subsample_y[1] == 1 && subsample_x[2] == 1 && subsample_y[2] == 1) {
571       return kJpegYuv444;
572     }
573   } else if (number_of_components == 1) {  // Grey-scale images.
574     if (subsample_x[0] == 1 && subsample_y[0] == 1) {
575       return kJpegYuv400;
576     }
577   }
578   return kJpegUnknown;
579 }
580 
581 }  // namespace libyuv
582 #endif  // HAVE_JPEG
583