Fix corrupted image when over-cropping black filler frames.
authorCarl Hetherington <cth@carlh.net>
Tue, 24 Nov 2020 23:11:55 +0000 (00:11 +0100)
committerCarl Hetherington <cth@carlh.net>
Tue, 24 Nov 2020 23:36:22 +0000 (00:36 +0100)
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
test/data
test/image_test.cc

index abbc6e71aa87c5bdee0983496850f9efd48f5329..fb9efdf8926a1c0791e5bf8229a5ad5647720838 100644 (file)
@@ -183,15 +183,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());
 
        /* Scale context for a scale from cropped_size to inter_size */
        struct SwsContext* scale_context = sws_getContext (
@@ -231,8 +242,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);
@@ -261,7 +272,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.
index f47d073d6610bdb3ae0ad9d842c4d66a6e9a16d2..b5863343b9157102144f70aa4269ec03922692fe 160000 (submodule)
--- a/test/data
+++ b/test/data
@@ -1 +1 @@
-Subproject commit f47d073d6610bdb3ae0ad9d842c4d66a6e9a16d2
+Subproject commit b5863343b9157102144f70aa4269ec03922692fe
index bf7effe092dcbe7541a1f48173ce7bdb8840f5e5..9c1c7457e774974073e1abe85b2b9b26018d35b1 100644 (file)
@@ -479,3 +479,20 @@ BOOST_AUTO_TEST_CASE (make_black_test)
                ++N;
        }
 }
+
+
+/** 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> image (new Image (AV_PIX_FMT_RGB24, dcp::Size(128, 128), true));
+       image->make_black ();
+       shared_ptr<Image> scaled = image->crop_scale_window (
+               Crop(0, 0, 128, 128), dcp::Size(1323, 565), dcp::Size(1349, 565), dcp::YUV_TO_RGB_REC709, VIDEO_RANGE_FULL, AV_PIX_FMT_RGB24, VIDEO_RANGE_FULL, true, true
+               );
+       string const filename = "over_crop_test.png";
+       write_image (scaled, "build/test/" + filename);
+       check_image ("test/data/" + filename, "build/test/" + filename);
+}