Commit 9f5a550e authored by Andres Calderon Jaramillo's avatar Andres Calderon Jaramillo Committed by Commit Bot

blink: Detect YUV format in non-interleaved JPEGs.

This CL modifies the JPEGImageDecoder to detect the YUV subsampling
format correctly when not all the components are in the first scan.
Prior to this change, the subsampling calculation was based on the
information in the first scan. However, for some JPEGs
(non-interleaved), this would produce YUV_UNKNOWN. This change makes the
computation based on the information in the SOF which is global for the
image and includes the information of all the scans.

Bug: 900446
Test: Add a test to cover the non-interleaved case.
Change-Id: If1d810f0d921cba3603cf82ac9fba19da02e8982
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1844094Reviewed-by: default avatarLeon Scroggins <scroggo@chromium.org>
Commit-Queue: Andres Calderon Jaramillo <andrescj@chromium.org>
Cr-Commit-Position: refs/heads/master@{#703366}
parent e028a304
...@@ -81,6 +81,54 @@ const int exifMarker = JPEG_APP0 + 1; ...@@ -81,6 +81,54 @@ const int exifMarker = JPEG_APP0 + 1;
// JPEG only supports a denominator of 8. // JPEG only supports a denominator of 8.
const unsigned g_scale_denominator = 8; const unsigned g_scale_denominator = 8;
enum yuv_subsampling {
YUV_UNKNOWN,
YUV_410,
YUV_411,
YUV_420,
YUV_422,
YUV_440,
YUV_444
};
// Extracts the YUV subsampling format of an image given |info| which is assumed
// to have gone through a jpeg_read_header() call.
yuv_subsampling YuvSubsampling(const jpeg_decompress_struct& info) {
if (info.jpeg_color_space == JCS_YCbCr && info.num_components == 3 &&
info.comp_info && info.comp_info[1].h_samp_factor == 1 &&
info.comp_info[1].v_samp_factor == 1 &&
info.comp_info[2].h_samp_factor == 1 &&
info.comp_info[2].v_samp_factor == 1) {
const int h = info.comp_info[0].h_samp_factor;
const int v = info.comp_info[0].v_samp_factor;
if (v == 1) {
switch (h) {
case 1:
return YUV_444;
case 2:
return YUV_422;
case 4:
return YUV_411;
default:
break;
}
} else if (v == 2) {
switch (h) {
case 1:
return YUV_440;
case 2:
return YUV_420;
case 4:
return YUV_410;
default:
break;
}
}
}
return YUV_UNKNOWN;
}
// Extracts the JPEG color space of an image for UMA purposes given |info| which // Extracts the JPEG color space of an image for UMA purposes given |info| which
// is assumed to have gone through a jpeg_read_header(). When the color space is // is assumed to have gone through a jpeg_read_header(). When the color space is
// YCbCr, we also extract the chroma subsampling. The caveat is that the // YCbCr, we also extract the chroma subsampling. The caveat is that the
...@@ -101,39 +149,23 @@ blink::BitmapImageMetrics::JpegColorSpace ExtractUMAJpegColorSpace( ...@@ -101,39 +149,23 @@ blink::BitmapImageMetrics::JpegColorSpace ExtractUMAJpegColorSpace(
case JCS_YCCK: case JCS_YCCK:
return blink::BitmapImageMetrics::JpegColorSpace::kYCCK; return blink::BitmapImageMetrics::JpegColorSpace::kYCCK;
case JCS_YCbCr: case JCS_YCbCr:
// The following logic is mostly reused from YuvSubsampling(). However, switch (YuvSubsampling(info)) {
// here we use |info.comp_info| instead of |info.cur_comp_info| to read case YUV_444:
// the components from the SOF instead of the first scan. We also don't return blink::BitmapImageMetrics::JpegColorSpace::kYCbCr444;
// care about |info.scale_denom|. case YUV_422:
// TODO: can we use this same logic in YuvSubsampling()? return blink::BitmapImageMetrics::JpegColorSpace::kYCbCr422;
if (info.num_components == 3 && info.comp_info && case YUV_411:
info.comp_info[1].h_samp_factor == 1 && return blink::BitmapImageMetrics::JpegColorSpace::kYCbCr411;
info.comp_info[1].v_samp_factor == 1 && case YUV_440:
info.comp_info[2].h_samp_factor == 1 && return blink::BitmapImageMetrics::JpegColorSpace::kYCbCr440;
info.comp_info[2].v_samp_factor == 1) { case YUV_420:
const int h = info.comp_info[0].h_samp_factor; return blink::BitmapImageMetrics::JpegColorSpace::kYCbCr420;
const int v = info.comp_info[0].v_samp_factor; case YUV_410:
if (v == 1) { return blink::BitmapImageMetrics::JpegColorSpace::kYCbCr410;
switch (h) { case YUV_UNKNOWN:
case 1: return blink::BitmapImageMetrics::JpegColorSpace::kYCbCrOther;
return blink::BitmapImageMetrics::JpegColorSpace::kYCbCr444;
case 2:
return blink::BitmapImageMetrics::JpegColorSpace::kYCbCr422;
case 4:
return blink::BitmapImageMetrics::JpegColorSpace::kYCbCr411;
}
} else if (v == 2) {
switch (h) {
case 1:
return blink::BitmapImageMetrics::JpegColorSpace::kYCbCr440;
case 2:
return blink::BitmapImageMetrics::JpegColorSpace::kYCbCr420;
case 4:
return blink::BitmapImageMetrics::JpegColorSpace::kYCbCr410;
}
}
} }
return blink::BitmapImageMetrics::JpegColorSpace::kYCbCrOther; NOTREACHED();
default: default:
return blink::BitmapImageMetrics::JpegColorSpace::kUnknown; return blink::BitmapImageMetrics::JpegColorSpace::kUnknown;
} }
...@@ -164,16 +196,6 @@ enum jstate { ...@@ -164,16 +196,6 @@ enum jstate {
JPEG_DONE JPEG_DONE
}; };
enum yuv_subsampling {
YUV_UNKNOWN,
YUV_410,
YUV_411,
YUV_420,
YUV_422,
YUV_440,
YUV_444
};
void init_source(j_decompress_ptr jd); void init_source(j_decompress_ptr jd);
boolean fill_input_buffer(j_decompress_ptr jd); boolean fill_input_buffer(j_decompress_ptr jd);
void skip_input_data(j_decompress_ptr jd, long num_bytes); void skip_input_data(j_decompress_ptr jd, long num_bytes);
...@@ -273,56 +295,13 @@ static ImageOrientation ReadImageOrientation(jpeg_decompress_struct* info) { ...@@ -273,56 +295,13 @@ static ImageOrientation ReadImageOrientation(jpeg_decompress_struct* info) {
static IntSize ComputeYUVSize(const jpeg_decompress_struct* info, static IntSize ComputeYUVSize(const jpeg_decompress_struct* info,
int component) { int component) {
return IntSize(info->cur_comp_info[component]->downsampled_width, return IntSize(info->comp_info[component].downsampled_width,
info->cur_comp_info[component]->downsampled_height); info->comp_info[component].downsampled_height);
} }
static size_t ComputeYUVWidthBytes(const jpeg_decompress_struct* info, static size_t ComputeYUVWidthBytes(const jpeg_decompress_struct* info,
int component) { int component) {
return info->cur_comp_info[component]->width_in_blocks * DCTSIZE; return info->comp_info[component].width_in_blocks * DCTSIZE;
}
static yuv_subsampling YuvSubsampling(const jpeg_decompress_struct& info) {
if ((DCTSIZE == 8) && (info.num_components == 3) && (info.scale_denom <= 8) &&
(info.cur_comp_info[0]) && (info.cur_comp_info[1]) &&
(info.cur_comp_info[2]) && (info.cur_comp_info[1]->h_samp_factor == 1) &&
(info.cur_comp_info[1]->v_samp_factor == 1) &&
(info.cur_comp_info[2]->h_samp_factor == 1) &&
(info.cur_comp_info[2]->v_samp_factor == 1)) {
int h = info.cur_comp_info[0]->h_samp_factor;
int v = info.cur_comp_info[0]->v_samp_factor;
// 4:4:4 : (h == 1) && (v == 1)
// 4:4:0 : (h == 1) && (v == 2)
// 4:2:2 : (h == 2) && (v == 1)
// 4:2:0 : (h == 2) && (v == 2)
// 4:1:1 : (h == 4) && (v == 1)
// 4:1:0 : (h == 4) && (v == 2)
if (v == 1) {
switch (h) {
case 1:
return YUV_444;
case 2:
return YUV_422;
case 4:
return YUV_411;
default:
break;
}
} else if (v == 2) {
switch (h) {
case 1:
return YUV_440;
case 2:
return YUV_420;
case 4:
return YUV_410;
default:
break;
}
}
}
return YUV_UNKNOWN;
} }
static void ProgressMonitor(j_common_ptr info) { static void ProgressMonitor(j_common_ptr info) {
...@@ -483,8 +462,10 @@ class JPEGImageReader final { ...@@ -483,8 +462,10 @@ class JPEGImageReader final {
switch (info_.jpeg_color_space) { switch (info_.jpeg_color_space) {
case JCS_YCbCr: case JCS_YCbCr:
// libjpeg can convert YCbCr image pixels to RGB. // libjpeg can convert YCbCr image pixels to RGB.
// TODO(crbug.com/919627): is the info_.scale_denom <= 8 actually
// needed?
info_.out_color_space = rgbOutputColorSpace(); info_.out_color_space = rgbOutputColorSpace();
if (decoder_->HasImagePlanes() && if (decoder_->HasImagePlanes() && (info_.scale_denom <= 8) &&
(YuvSubsampling(info_) != YUV_UNKNOWN)) (YuvSubsampling(info_) != YUV_UNKNOWN))
override_color_space = JCS_YCbCr; override_color_space = JCS_YCbCr;
break; break;
......
...@@ -250,6 +250,16 @@ TEST(JPEGImageDecoderTest, yuv) { ...@@ -250,6 +250,16 @@ TEST(JPEGImageDecoderTest, yuv) {
EXPECT_EQ(128u, output_uv_width); EXPECT_EQ(128u, output_uv_width);
EXPECT_EQ(128u, output_uv_height); EXPECT_EQ(128u, output_uv_height);
// Each plane is in its own scan.
const char* jpeg_file_non_interleaved =
"/images/resources/cs-uma-ycbcr-420-non-interleaved.jpg"; // 64x64
ReadYUV(kLargeEnoughSize, &output_y_width, &output_y_height, &output_uv_width,
&output_uv_height, jpeg_file_non_interleaved);
EXPECT_EQ(64u, output_y_width);
EXPECT_EQ(64u, output_y_height);
EXPECT_EQ(32u, output_uv_width);
EXPECT_EQ(32u, output_uv_height);
const char* jpeg_file_image_size_not_multiple_of8 = const char* jpeg_file_image_size_not_multiple_of8 =
"/images/resources/cropped_mandrill.jpg"; // 439x154 "/images/resources/cropped_mandrill.jpg"; // 439x154
ReadYUV(kLargeEnoughSize, &output_y_width, &output_y_height, &output_uv_width, ReadYUV(kLargeEnoughSize, &output_y_width, &output_y_height, &output_uv_width,
...@@ -493,7 +503,7 @@ const ColorSpaceUMATest::ParamType kColorSpaceUMATestParams[] = { ...@@ -493,7 +503,7 @@ const ColorSpaceUMATest::ParamType kColorSpaceUMATestParams[] = {
{"cs-uma-grayscale.jpg", true, {"cs-uma-grayscale.jpg", true,
BitmapImageMetrics::JpegColorSpace::kGrayscale}, BitmapImageMetrics::JpegColorSpace::kGrayscale},
{"cs-uma-rgb.jpg", true, BitmapImageMetrics::JpegColorSpace::kRGB}, {"cs-uma-rgb.jpg", true, BitmapImageMetrics::JpegColorSpace::kRGB},
// Each component is in a separate plane. Should not make a difference. // Each component is in a separate scan. Should not make a difference.
{"cs-uma-rgb-non-interleaved.jpg", true, {"cs-uma-rgb-non-interleaved.jpg", true,
BitmapImageMetrics::JpegColorSpace::kRGB}, BitmapImageMetrics::JpegColorSpace::kRGB},
{"cs-uma-cmyk.jpg", true, BitmapImageMetrics::JpegColorSpace::kCMYK}, {"cs-uma-cmyk.jpg", true, BitmapImageMetrics::JpegColorSpace::kCMYK},
...@@ -515,7 +525,7 @@ const ColorSpaceUMATest::ParamType kColorSpaceUMATestParams[] = { ...@@ -515,7 +525,7 @@ const ColorSpaceUMATest::ParamType kColorSpaceUMATestParams[] = {
BitmapImageMetrics::JpegColorSpace::kYCbCr411}, BitmapImageMetrics::JpegColorSpace::kYCbCr411},
{"cs-uma-ycbcr-420.jpg", true, {"cs-uma-ycbcr-420.jpg", true,
BitmapImageMetrics::JpegColorSpace::kYCbCr420}, BitmapImageMetrics::JpegColorSpace::kYCbCr420},
// Each component is in a separate plane. Should not make a difference. // Each component is in a separate scan. Should not make a difference.
{"cs-uma-ycbcr-420-non-interleaved.jpg", true, {"cs-uma-ycbcr-420-non-interleaved.jpg", true,
BitmapImageMetrics::JpegColorSpace::kYCbCr420}, BitmapImageMetrics::JpegColorSpace::kYCbCr420},
// 3 components/both JFIF and Adobe markers, so we expect libjpeg_turbo to // 3 components/both JFIF and Adobe markers, so we expect libjpeg_turbo to
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment