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