From accdec63a79b43c6349597b15243dc41e521521d Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Wed, 25 Nov 2020 00:11:55 +0100 Subject: [PATCH] Fix corrupted image when over-cropping black filler frames. FFmpegDecoder can emit small black frames (128x128 pixels) when it wants to fill in a gap. Image::crop_scale_window would do the wrong thing if we then applied a crop of greater than 128 in either direction; though cropped_size is correctly clamped, the crop value itself was not and is used to calculate the input data pointers. This would result in random frames, usually at the end of DCPs, often made up of blurry colour washes. --- src/lib/image.cc | 21 ++++++++++++++++----- test/data | 2 +- test/image_test.cc | 14 ++++++++++++++ 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/lib/image.cc b/src/lib/image.cc index 97aeccd76..49acdb5c2 100644 --- a/src/lib/image.cc +++ b/src/lib/image.cc @@ -173,15 +173,26 @@ Image::crop_scale_window ( /* Round down so that we crop only the number of pixels that is straightforward * considering any subsampling. */ - Crop rounded_crop( + Crop corrected_crop( round_width_for_subsampling(crop.left, in_desc), round_width_for_subsampling(crop.right, in_desc), round_height_for_subsampling(crop.top, in_desc), round_height_for_subsampling(crop.bottom, in_desc) ); + /* Also check that we aren't cropping more image than there actually is */ + if ((corrected_crop.left + corrected_crop.right) >= (size().width - 4)) { + corrected_crop.left = 0; + corrected_crop.right = size().width - 4; + } + + if ((corrected_crop.top + corrected_crop.bottom) >= (size().height - 4)) { + corrected_crop.top = 0; + corrected_crop.bottom = size().height - 4; + } + /* Size of the image after any crop */ - dcp::Size const cropped_size = rounded_crop.apply (size()); + dcp::Size const cropped_size = corrected_crop.apply (size()); /* Hack: if we're not doing quite the crop that we were asked for, and we carry on scaling * to the inter_size we were asked for, there is a small but noticeable wobble in the image @@ -230,8 +241,8 @@ Image::crop_scale_window ( /* Prepare input data pointers with crop */ uint8_t* scale_in_data[planes()]; for (int c = 0; c < planes(); ++c) { - int const x = lrintf(bytes_per_pixel(c) * rounded_crop.left); - scale_in_data[c] = data()[c] + x + stride()[c] * (rounded_crop.top / vertical_factor(c)); + int const x = lrintf(bytes_per_pixel(c) * corrected_crop.left); + scale_in_data[c] = data()[c] + x + stride()[c] * (corrected_crop.top / vertical_factor(c)); } AVPixFmtDescriptor const * out_desc = av_pix_fmt_desc_get (out_format); @@ -260,7 +271,7 @@ Image::crop_scale_window ( sws_freeContext (scale_context); - if (rounded_crop != Crop() && cropped_size == inter_size) { + if (corrected_crop != Crop() && cropped_size == inter_size) { /* We are cropping without any scaling or pixel format conversion, so FFmpeg may have left some data behind in our image. Clear it out. It may get to the point where we should just stop trying to be clever with cropping. diff --git a/test/data b/test/data index 3b2a8c56d..33736dcba 160000 --- a/test/data +++ b/test/data @@ -1 +1 @@ -Subproject commit 3b2a8c56dff15050a9eca576214399ef4a5abc0f +Subproject commit 33736dcba84fee688b431c00daa2cc17a9dabab7 diff --git a/test/image_test.cc b/test/image_test.cc index 9fe793d70..2f66cf41d 100644 --- a/test/image_test.cc +++ b/test/image_test.cc @@ -393,3 +393,17 @@ BOOST_AUTO_TEST_CASE (fade_test) fade_test_format_red (AV_PIX_FMT_RGB48LE, 0.5, "rgb48le_50"); fade_test_format_red (AV_PIX_FMT_RGB48LE, 1, "rgb48le_100"); } + +/** Make sure the image isn't corrupted if it is cropped too much. This can happen when a + * filler 128x128 black frame is emitted from the FFmpegDecoder and the overall crop in either direction + * is greater than 128 pixels. + */ +BOOST_AUTO_TEST_CASE (over_crop_test) +{ + shared_ptr image (new Image (AV_PIX_FMT_RGB24, dcp::Size(128, 128), true)); + image->make_black (); + shared_ptr scaled = image->crop_scale_window (Crop(0, 0, 128, 128), dcp::Size(1323, 565), dcp::Size(1349, 565), dcp::YUV_TO_RGB_REC709, AV_PIX_FMT_RGB24, true, true); + string const filename = "over_crop_test.png"; + write_image (scaled, "build/test/" + filename, "RGB"); + check_image ("test/data/" + filename, "build/test/" + filename); +} -- 2.30.2