Support auto-crop for YUV422P10LE.
[dcpomatic.git] / src / lib / guess_crop.cc
1 /*
2     Copyright (C) 2021 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     DCP-o-matic is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21
22 #include "decoder.h"
23 #include "decoder_factory.h"
24 #include "image_proxy.h"
25 #include "guess_crop.h"
26 #include "image.h"
27 #include "video_decoder.h"
28
29
30 using std::shared_ptr;
31 using namespace dcpomatic;
32
33
34 Crop
35 guess_crop (shared_ptr<const Image> image, double threshold)
36 {
37         auto const width = image->size().width;
38         auto const height = image->size().height;
39
40         auto image_in_line = [image, threshold](int start_x, int start_y, int pixels, bool rows) {
41                 double brightest = 0;
42
43                 switch (image->pixel_format()) {
44                 case AV_PIX_FMT_RGB24:
45                 {
46                         uint8_t const* data = image->data()[0] + start_x * 3 + start_y * image->stride()[0];
47                         for (int p = 0; p < pixels; ++p) {
48                                 /* Averaging R, G and B */
49                                 brightest = std::max(brightest, static_cast<double>(data[0] + data[1] + data[2]) / (3 * 256));
50                                 data += rows ? 3 : image->stride()[0];
51                         }
52                         break;
53                 }
54                 case AV_PIX_FMT_YUV420P:
55                 {
56                         uint8_t const* data = image->data()[0] + start_x + start_y * image->stride()[0];
57                         for (int p = 0; p < pixels; ++p) {
58                                 /* Just using Y */
59                                 brightest = std::max(brightest, static_cast<double>(*data) / 256);
60                                 data += rows ? 1 : image->stride()[0];
61                         }
62                         break;
63                 }
64                 case AV_PIX_FMT_YUV422P10LE:
65                 {
66                         uint16_t const* data = reinterpret_cast<uint16_t*>(image->data()[0] + (start_x * 2) + (start_y * image->stride()[0]));
67                         for (int p = 0; p < pixels; ++p) {
68                                 /* Just using Y */
69                                 brightest = std::max(brightest, static_cast<double>(*data) / 1024);
70                                 data += rows ? 1 : (image->stride()[0] / 2);
71                         }
72                         break;
73                 }
74                 default:
75                         throw PixelFormatError("guess_crop()", image->pixel_format());
76                 }
77
78                 return brightest > threshold;
79         };
80
81         auto crop = Crop{};
82
83         for (auto y = 0; y < height; ++y) {
84                 if (image_in_line(0, y, width, true)) {
85                         crop.top = y;
86                         break;
87                 }
88         }
89
90         for (auto y = height - 1; y >= 0; --y) {
91                 if (image_in_line(0, y, width, true)) {
92                         crop.bottom = height - 1 - y;
93                         break;
94                 }
95         }
96
97         for (auto x = 0; x < width; ++x) {
98                 if (image_in_line(x, 0, height, false)) {
99                         crop.left = x;
100                         break;
101                 }
102         }
103
104         for (auto x = width - 1; x >= 0; --x) {
105                 if (image_in_line(x, 0, height, false)) {
106                         crop.right = width - 1 - x;
107                         break;
108                 }
109         }
110
111         return crop;
112 }
113
114
115 /** @param position Time within the content to get a video frame from when guessing the crop */
116 Crop
117 guess_crop (shared_ptr<const Film> film, shared_ptr<const Content> content, double threshold, ContentTime position)
118 {
119         DCPOMATIC_ASSERT (content->video);
120
121         auto decoder = decoder_factory (film, content, false, false, {});
122         DCPOMATIC_ASSERT (decoder->video);
123
124         bool done = false;
125         auto crop = Crop{};
126
127         auto handle_video = [&done, &crop, threshold](ContentVideo video) {
128                 crop = guess_crop(video.image->image(Image::Alignment::COMPACT).image, threshold);
129                 done = true;
130         };
131
132         decoder->video->Data.connect (handle_video);
133         decoder->seek (position, false);
134
135         int tries_left = 50;
136         while (!done && tries_left >= 0) {
137                 decoder->pass();
138                 --tries_left;
139         }
140
141         return crop;
142 }
143