summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2022-10-18 20:26:05 +0200
committerCarl Hetherington <cth@carlh.net>2022-10-18 20:26:05 +0200
commit94eff17bdb94c170d456c7dab5ad3d4c00af4b14 (patch)
tree54281a554d800b49c149a69fe037035f903498ad /src
parent053bf81d7ef24843cc2eea62e24c2296fed48950 (diff)
parentc9a1e2040bf5600aeafdefe56a3bf46e15419a16 (diff)
Hopefully fix colour of Rec.2020 sources in the preview.
Diffstat (limited to 'src')
-rw-r--r--src/lib/ffmpeg_decoder.cc32
-rw-r--r--src/lib/ffmpeg_decoder.h4
-rw-r--r--src/lib/image.cc2
-rw-r--r--src/lib/raw_image_proxy.cc7
-rw-r--r--src/lib/raw_image_proxy.h4
-rw-r--r--src/lib/video_filter_graph.cc48
-rw-r--r--src/lib/video_filter_graph.h3
-rw-r--r--src/lib/video_filter_graph_set.cc62
-rw-r--r--src/lib/video_filter_graph_set.h61
-rw-r--r--src/lib/wscript1
-rw-r--r--src/wx/colour_conversion_editor.cc1
-rw-r--r--src/wx/gl_video_view.cc79
-rw-r--r--src/wx/gl_video_view.h1
-rw-r--r--src/wx/simple_video_view.cc10
-rw-r--r--src/wx/simple_video_view.h7
15 files changed, 268 insertions, 54 deletions
diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc
index a5b36d04f..27b7aa7b7 100644
--- a/src/lib/ffmpeg_decoder.cc
+++ b/src/lib/ffmpeg_decoder.cc
@@ -77,6 +77,7 @@ using namespace dcpomatic;
FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> film, shared_ptr<const FFmpegContent> c, bool fast)
: FFmpeg (c)
, Decoder (film)
+ , _filter_graphs(c->filters(), dcp::Fraction(lrint(_ffmpeg_content->video_frame_rate().get_value_or(24) * 1000), 1000))
{
if (c->video && c->video->use()) {
video = make_shared<VideoDecoder>(this, c);
@@ -401,13 +402,10 @@ FFmpegDecoder::seek (ContentTime time, bool accurate)
AVSEEK_FLAG_BACKWARD
);
- {
- /* Force re-creation of filter graphs to reset them and hence to make sure
- they don't have any pre-seek frames knocking about.
- */
- boost::mutex::scoped_lock lm (_filter_graphs_mutex);
- _filter_graphs.clear ();
- }
+ /* Force re-creation of filter graphs to reset them and hence to make sure
+ they don't have any pre-seek frames knocking about.
+ */
+ _filter_graphs.clear();
if (video_codec_context ()) {
avcodec_flush_buffers (video_codec_context());
@@ -580,25 +578,7 @@ FFmpegDecoder::decode_and_process_video_packet (AVPacket* packet)
void
FFmpegDecoder::process_video_frame ()
{
- boost::mutex::scoped_lock lm (_filter_graphs_mutex);
-
- shared_ptr<VideoFilterGraph> graph;
-
- auto i = _filter_graphs.begin();
- while (i != _filter_graphs.end() && !(*i)->can_process(dcp::Size(_video_frame->width, _video_frame->height), (AVPixelFormat) _video_frame->format)) {
- ++i;
- }
-
- if (i == _filter_graphs.end ()) {
- dcp::Fraction vfr (lrint(_ffmpeg_content->video_frame_rate().get() * 1000), 1000);
- graph = make_shared<VideoFilterGraph>(dcp::Size(_video_frame->width, _video_frame->height), (AVPixelFormat) _video_frame->format, vfr);
- graph->setup (_ffmpeg_content->filters ());
- _filter_graphs.push_back (graph);
- LOG_GENERAL (N_("New graph for %1x%2, pixel format %3"), _video_frame->width, _video_frame->height, _video_frame->format);
- } else {
- graph = *i;
- }
-
+ auto graph = _filter_graphs.get(dcp::Size(_video_frame->width, _video_frame->height), static_cast<AVPixelFormat>(_video_frame->format));
auto images = graph->process (_video_frame);
for (auto const& i: images) {
diff --git a/src/lib/ffmpeg_decoder.h b/src/lib/ffmpeg_decoder.h
index e1a023453..9de44333c 100644
--- a/src/lib/ffmpeg_decoder.h
+++ b/src/lib/ffmpeg_decoder.h
@@ -27,6 +27,7 @@
#include "bitmap_text.h"
#include "decoder.h"
#include "ffmpeg.h"
+#include "video_filter_graph_set.h"
#include "util.h"
extern "C" {
#include <libavcodec/avcodec.h>
@@ -76,8 +77,7 @@ private:
void maybe_add_subtitle ();
- std::list<std::shared_ptr<VideoFilterGraph>> _filter_graphs;
- boost::mutex _filter_graphs_mutex;
+ VideoFilterGraphSet _filter_graphs;
dcpomatic::ContentTime _pts_offset;
boost::optional<dcpomatic::ContentTime> _current_subtitle_to;
diff --git a/src/lib/image.cc b/src/lib/image.cc
index 2ac84e26c..d84755df3 100644
--- a/src/lib/image.cc
+++ b/src/lib/image.cc
@@ -240,6 +240,7 @@ Image::crop_scale_window (
EnumIndexedVector<int, dcp::YUVToRGB> lut;
lut[dcp::YUVToRGB::REC601] = SWS_CS_ITU601;
lut[dcp::YUVToRGB::REC709] = SWS_CS_ITU709;
+ lut[dcp::YUVToRGB::REC2020] = SWS_CS_BT2020;
/* The 3rd parameter here is:
0 -> source range MPEG (i.e. "video", 16-235)
@@ -350,6 +351,7 @@ Image::scale (dcp::Size out_size, dcp::YUVToRGB yuv_to_rgb, AVPixelFormat out_fo
EnumIndexedVector<int, dcp::YUVToRGB> lut;
lut[dcp::YUVToRGB::REC601] = SWS_CS_ITU601;
lut[dcp::YUVToRGB::REC709] = SWS_CS_ITU709;
+ lut[dcp::YUVToRGB::REC2020] = SWS_CS_BT2020;
/* The 3rd parameter here is:
0 -> source range MPEG (i.e. "video", 16-235)
diff --git a/src/lib/raw_image_proxy.cc b/src/lib/raw_image_proxy.cc
index 9b486dd5c..c606ddd99 100644
--- a/src/lib/raw_image_proxy.cc
+++ b/src/lib/raw_image_proxy.cc
@@ -45,7 +45,7 @@ using boost::optional;
using dcp::raw_convert;
-RawImageProxy::RawImageProxy (shared_ptr<Image> image)
+RawImageProxy::RawImageProxy(shared_ptr<const Image> image)
: _image (image)
{
@@ -58,8 +58,9 @@ RawImageProxy::RawImageProxy (shared_ptr<cxml::Node> xml, shared_ptr<Socket> soc
xml->number_child<int>("Width"), xml->number_child<int>("Height")
);
- _image = make_shared<Image>(static_cast<AVPixelFormat>(xml->number_child<int>("PixelFormat")), size, Image::Alignment::PADDED);
- _image->read_from_socket (socket);
+ auto image = make_shared<Image>(static_cast<AVPixelFormat>(xml->number_child<int>("PixelFormat")), size, Image::Alignment::PADDED);
+ image->read_from_socket (socket);
+ _image = image;
}
diff --git a/src/lib/raw_image_proxy.h b/src/lib/raw_image_proxy.h
index 1111b66c0..d5ae2457d 100644
--- a/src/lib/raw_image_proxy.h
+++ b/src/lib/raw_image_proxy.h
@@ -29,7 +29,7 @@
class RawImageProxy : public ImageProxy
{
public:
- explicit RawImageProxy (std::shared_ptr<Image>);
+ explicit RawImageProxy(std::shared_ptr<const Image>);
RawImageProxy (std::shared_ptr<cxml::Node> xml, std::shared_ptr<Socket> socket);
Result image (
@@ -43,7 +43,7 @@ public:
size_t memory_used () const override;
private:
- std::shared_ptr<Image> _image;
+ std::shared_ptr<const Image> _image;
};
diff --git a/src/lib/video_filter_graph.cc b/src/lib/video_filter_graph.cc
index 1cc142520..26f858437 100644
--- a/src/lib/video_filter_graph.cc
+++ b/src/lib/video_filter_graph.cc
@@ -21,6 +21,7 @@
#include "compose.hpp"
#include "image.h"
+#include "scope_guard.h"
#include "video_filter_graph.h"
extern "C" {
#include <libavfilter/buffersrc.h>
@@ -48,13 +49,56 @@ VideoFilterGraph::VideoFilterGraph (dcp::Size s, AVPixelFormat p, dcp::Fraction
}
+list<shared_ptr<const Image>>
+VideoFilterGraph::process(shared_ptr<const Image> image)
+{
+ if (_copy) {
+ return { image };
+ }
+
+ auto frame = av_frame_alloc();
+ if (!frame) {
+ throw std::bad_alloc();
+ }
+
+ ScopeGuard sg = [&frame]() { av_frame_free(&frame); };
+
+ for (int i = 0; i < image->planes(); ++i) {
+ frame->data[i] = image->data()[i];
+ frame->linesize[i] = image->stride()[i];
+ }
+
+ frame->width = image->size().width;
+ frame->height = image->size().height;
+ frame->format = image->pixel_format();
+
+ int r = av_buffersrc_write_frame(_buffer_src_context, frame);
+ if (r < 0) {
+ throw DecodeError(String::compose(N_("could not push buffer into filter chain (%1)."), r));
+ }
+
+ list<shared_ptr<const Image>> images;
+
+ while (true) {
+ if (av_buffersink_get_frame(_buffer_sink_context, _frame) < 0) {
+ break;
+ }
+
+ images.push_back(make_shared<Image>(_frame, Image::Alignment::PADDED));
+ av_frame_unref (_frame);
+ }
+
+ return images;
+}
+
+
/** Take an AVFrame and process it using our configured filters, returning a
* set of Images. Caller handles memory management of the input frame.
*/
-list<pair<shared_ptr<Image>, int64_t>>
+list<pair<shared_ptr<const Image>, int64_t>>
VideoFilterGraph::process (AVFrame* frame)
{
- list<pair<shared_ptr<Image>, int64_t>> images;
+ list<pair<shared_ptr<const Image>, int64_t>> images;
if (_copy) {
images.push_back (make_pair(make_shared<Image>(frame, Image::Alignment::PADDED), frame->best_effort_timestamp));
diff --git a/src/lib/video_filter_graph.h b/src/lib/video_filter_graph.h
index d887e551b..1fb322282 100644
--- a/src/lib/video_filter_graph.h
+++ b/src/lib/video_filter_graph.h
@@ -28,7 +28,8 @@ public:
VideoFilterGraph (dcp::Size s, AVPixelFormat p, dcp::Fraction r);
bool can_process (dcp::Size s, AVPixelFormat p) const;
- std::list<std::pair<std::shared_ptr<Image>, int64_t>> process (AVFrame * frame);
+ std::list<std::pair<std::shared_ptr<const Image>, int64_t>> process (AVFrame * frame);
+ std::list<std::shared_ptr<const Image>> process(std::shared_ptr<const Image> image);
protected:
std::string src_parameters () const override;
diff --git a/src/lib/video_filter_graph_set.cc b/src/lib/video_filter_graph_set.cc
new file mode 100644
index 000000000..dbae17d4c
--- /dev/null
+++ b/src/lib/video_filter_graph_set.cc
@@ -0,0 +1,62 @@
+/*
+ Copyright (C) 2022 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ DCP-o-matic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "dcpomatic_log.h"
+#include "video_filter_graph.h"
+#include "video_filter_graph_set.h"
+
+#include "i18n.h"
+
+
+using std::make_shared;
+using std::shared_ptr;
+
+
+shared_ptr<VideoFilterGraph>
+VideoFilterGraphSet::get(dcp::Size size, AVPixelFormat format)
+{
+ auto graph = std::find_if(
+ _graphs.begin(),
+ _graphs.end(),
+ [size, format](shared_ptr<VideoFilterGraph> g) {
+ return g->can_process(size, format);
+ });
+
+ if (graph != _graphs.end()) {
+ return *graph;
+ }
+
+ auto new_graph = make_shared<VideoFilterGraph>(size, format, _frame_rate);
+ new_graph->setup(_filters);
+ _graphs.push_back(new_graph);
+
+ LOG_GENERAL(N_("New graph for %1x%2, pixel format %3"), size.width, size.height, static_cast<int>(format));
+
+ return new_graph;
+}
+
+
+void
+VideoFilterGraphSet::clear()
+{
+ _graphs.clear();
+}
+
diff --git a/src/lib/video_filter_graph_set.h b/src/lib/video_filter_graph_set.h
new file mode 100644
index 000000000..935378432
--- /dev/null
+++ b/src/lib/video_filter_graph_set.h
@@ -0,0 +1,61 @@
+/*
+ Copyright (C) 2022 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ DCP-o-matic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#ifndef DCPOMATIC_VIDEO_FILTER_GRAPH_SET_H
+#define DCPOMATIC_VIDEO_FILTER_GRAPH_SET_H
+
+
+#include <dcp/types.h>
+extern "C" {
+#include <libavutil/avutil.h>
+}
+#include <memory>
+#include <vector>
+
+
+class Filter;
+class VideoFilterGraph;
+
+
+class VideoFilterGraphSet
+{
+public:
+ VideoFilterGraphSet(std::vector<Filter const*> filters, dcp::Fraction frame_rate)
+ : _filters(filters)
+ , _frame_rate(frame_rate)
+ {}
+
+ VideoFilterGraphSet(VideoFilterGraphSet const&) = delete;
+ VideoFilterGraphSet& operator=(VideoFilterGraphSet const&) = delete;
+
+ std::shared_ptr<VideoFilterGraph> get(dcp::Size size, AVPixelFormat format);
+
+ void clear();
+
+private:
+ std::vector<Filter const*> _filters;
+ dcp::Fraction _frame_rate;
+ std::vector<std::shared_ptr<VideoFilterGraph>> _graphs;
+};
+
+
+#endif
+
diff --git a/src/lib/wscript b/src/lib/wscript
index 55c4e735f..26fcb21e5 100644
--- a/src/lib/wscript
+++ b/src/lib/wscript
@@ -194,6 +194,7 @@ sources = """
video_content.cc
video_decoder.cc
video_filter_graph.cc
+ video_filter_graph_set.cc
video_mxf_content.cc
video_mxf_decoder.cc
video_mxf_examiner.cc
diff --git a/src/wx/colour_conversion_editor.cc b/src/wx/colour_conversion_editor.cc
index b7c054e27..4cc9aaa9e 100644
--- a/src/wx/colour_conversion_editor.cc
+++ b/src/wx/colour_conversion_editor.cc
@@ -118,6 +118,7 @@ ColourConversionEditor::ColourConversionEditor (wxWindow* parent, bool yuv)
_yuv_to_rgb = new wxChoice (this, wxID_ANY);
_yuv_to_rgb->Append (_("Rec. 601"));
_yuv_to_rgb->Append (_("Rec. 709"));
+ _yuv_to_rgb->Append (_("Rec. 2020"));
table->Add (_yuv_to_rgb, wxGBPosition (r, 1));
++r;
diff --git a/src/wx/gl_video_view.cc b/src/wx/gl_video_view.cc
index 1f60af69d..4fc620936 100644
--- a/src/wx/gl_video_view.cc
+++ b/src/wx/gl_video_view.cc
@@ -197,13 +197,15 @@ static constexpr char fragment_source[] =
/* type = 0: draw outline content rectangle
* type = 1: draw crop guess rectangle
* type = 2: draw XYZ image
- * type = 3: draw RGB image
+ * type = 3: draw RGB image (with sRGB/Rec709 primaries)
+ * type = 4: draw RGB image (converting from Rec2020 primaries)
* See FragmentType enum below.
*/
"uniform int type = 0;\n"
"uniform vec4 outline_content_colour;\n"
"uniform vec4 crop_guess_colour;\n"
-"uniform mat4 colour_conversion;\n"
+"uniform mat4 xyz_rec709_colour_conversion;\n"
+"uniform mat4 rec2020_rec709_colour_conversion;\n"
"\n"
"out vec4 FragColor;\n"
"\n"
@@ -271,13 +273,17 @@ static constexpr char fragment_source[] =
" FragColor.x = pow(FragColor.x, IN_GAMMA) / DCI_COEFFICIENT;\n"
" FragColor.y = pow(FragColor.y, IN_GAMMA) / DCI_COEFFICIENT;\n"
" FragColor.z = pow(FragColor.z, IN_GAMMA) / DCI_COEFFICIENT;\n"
-" FragColor = colour_conversion * FragColor;\n"
+" FragColor = xyz_rec709_colour_conversion * FragColor;\n"
" FragColor.x = pow(FragColor.x, OUT_GAMMA);\n"
" FragColor.y = pow(FragColor.y, OUT_GAMMA);\n"
" FragColor.z = pow(FragColor.z, OUT_GAMMA);\n"
" break;\n"
" case 3:\n"
" FragColor = texture_bicubic(texture_sampler, TexCoord);\n"
+" break;\n"
+" case 4:\n"
+" FragColor = texture_bicubic(texture_sampler, TexCoord);\n"
+" FragColor = rec2020_rec709_colour_conversion * FragColor;\n"
" break;\n"
" }\n"
"}\n";
@@ -288,7 +294,8 @@ enum class FragmentType
OUTLINE_CONTENT = 0,
CROP_GUESS = 1,
XYZ_IMAGE = 2,
- RGB_IMAGE = 3,
+ REC709_IMAGE = 3,
+ REC2020_IMAGE = 4,
};
@@ -453,18 +460,48 @@ GLVideoView::setup_shaders ()
set_outline_content_colour (program);
set_crop_guess_colour (program);
- auto conversion = dcp::ColourConversion::rec709_to_xyz();
- boost::numeric::ublas::matrix<double> matrix = conversion.xyz_to_rgb ();
- GLfloat gl_matrix[] = {
- static_cast<float>(matrix(0, 0)), static_cast<float>(matrix(0, 1)), static_cast<float>(matrix(0, 2)), 0.0f,
- static_cast<float>(matrix(1, 0)), static_cast<float>(matrix(1, 1)), static_cast<float>(matrix(1, 2)), 0.0f,
- static_cast<float>(matrix(2, 0)), static_cast<float>(matrix(2, 1)), static_cast<float>(matrix(2, 2)), 0.0f,
- 0.0f, 0.0f, 0.0f, 1.0f
- };
+ auto ublas_to_gl = [](boost::numeric::ublas::matrix<double> const& ublas, GLfloat* gl) {
+ gl[0] = static_cast<float>(ublas(0, 0));
+ gl[1] = static_cast<float>(ublas(0, 1));
+ gl[2] = static_cast<float>(ublas(0, 2));
+ gl[3] = 0.0f;
+ gl[4] = static_cast<float>(ublas(1, 0));
+ gl[5] = static_cast<float>(ublas(1, 1));
+ gl[6] = static_cast<float>(ublas(1, 2));
+ gl[7] = 0.0f;
+ gl[8] = static_cast<float>(ublas(2, 0));
+ gl[9] = static_cast<float>(ublas(2, 1));
+ gl[10] = static_cast<float>(ublas(2, 2));
+ gl[11] = 0.0f;
+ gl[12] = 0.0f;
+ gl[13] = 0.0f;
+ gl[14] = 0.0f;
+ gl[15] = 1.0f;
+ };
- auto colour_conversion = glGetUniformLocation (program, "colour_conversion");
- check_gl_error ("glGetUniformLocation");
- glUniformMatrix4fv (colour_conversion, 1, GL_TRUE, gl_matrix);
+ {
+ auto conversion = dcp::ColourConversion::rec709_to_xyz();
+ boost::numeric::ublas::matrix<double> matrix = conversion.xyz_to_rgb ();
+ GLfloat gl_matrix[16];
+ ublas_to_gl(matrix, gl_matrix);
+
+ auto xyz_rec709_colour_conversion = glGetUniformLocation(program, "xyz_rec709_colour_conversion");
+ check_gl_error ("glGetUniformLocation");
+ glUniformMatrix4fv(xyz_rec709_colour_conversion, 1, GL_TRUE, gl_matrix);
+ }
+
+ {
+ auto xyz_rec709 = dcp::ColourConversion::rec709_to_xyz().xyz_to_rgb();
+ auto rec2020_xyz = dcp::ColourConversion::rec2020_to_xyz().rgb_to_xyz();
+ auto product = boost::numeric::ublas::prod(xyz_rec709, rec2020_xyz);
+
+ GLfloat gl_matrix[16];
+ ublas_to_gl(product, gl_matrix);
+
+ auto rec2020_rec709_colour_conversion = glGetUniformLocation(program, "rec2020_rec709_colour_conversion");
+ check_gl_error("glGetUniformLocation");
+ glUniformMatrix4fv(rec2020_rec709_colour_conversion, 1, GL_TRUE, gl_matrix);
+ }
glLineWidth (1.0f);
check_gl_error ("glLineWidth");
@@ -522,11 +559,17 @@ GLVideoView::draw ()
glBindVertexArray(_vao);
check_gl_error ("glBindVertexArray");
- glUniform1i(_fragment_type, static_cast<GLint>(_optimise_for_j2k ? FragmentType::XYZ_IMAGE : FragmentType::RGB_IMAGE));
+ if (_optimise_for_j2k) {
+ glUniform1i(_fragment_type, static_cast<GLint>(FragmentType::XYZ_IMAGE));
+ } else if (_rec2020) {
+ glUniform1i(_fragment_type, static_cast<GLint>(FragmentType::REC2020_IMAGE));
+ } else {
+ glUniform1i(_fragment_type, static_cast<GLint>(FragmentType::REC709_IMAGE));
+ }
_video_texture->bind();
glDrawElements (GL_TRIANGLES, indices_video_texture_number, GL_UNSIGNED_INT, reinterpret_cast<void*>(indices_video_texture_offset * sizeof(int)));
if (_have_subtitle_to_render) {
- glUniform1i(_fragment_type, static_cast<GLint>(FragmentType::RGB_IMAGE));
+ glUniform1i(_fragment_type, static_cast<GLint>(FragmentType::REC709_IMAGE));
_subtitle_texture->bind();
glDrawElements (GL_TRIANGLES, indices_subtitle_texture_number, GL_UNSIGNED_INT, reinterpret_cast<void*>(indices_subtitle_texture_offset * sizeof(int)));
}
@@ -692,6 +735,8 @@ GLVideoView::set_image (shared_ptr<const PlayerVideo> pv)
check_gl_error ("glBufferSubData (subtitle)");
}
+ _rec2020 = pv->colour_conversion() && pv->colour_conversion()->about_equal(dcp::ColourConversion::rec2020_to_xyz(), 1e-6);
+
/* opt: where should these go? */
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
diff --git a/src/wx/gl_video_view.h b/src/wx/gl_video_view.h
index fd9154a73..964f6f1ab 100644
--- a/src/wx/gl_video_view.h
+++ b/src/wx/gl_video_view.h
@@ -134,6 +134,7 @@ private:
Last<boost::optional<dcpomatic::Rect<float>>> _last_crop_guess;
boost::atomic<wxSize> _canvas_size;
+ boost::atomic<bool> _rec2020;
std::unique_ptr<Texture> _video_texture;
std::unique_ptr<Texture> _subtitle_texture;
bool _have_subtitle_to_render = false;
diff --git a/src/wx/simple_video_view.cc b/src/wx/simple_video_view.cc
index 00d81ab47..09c172171 100644
--- a/src/wx/simple_video_view.cc
+++ b/src/wx/simple_video_view.cc
@@ -26,6 +26,8 @@
#include "lib/butler.h"
#include "lib/dcpomatic_log.h"
#include "lib/image.h"
+#include "lib/video_filter_graph.h"
+#include "lib/video_filter_graph_set.h"
#include <dcp/util.h>
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
@@ -46,6 +48,8 @@ using namespace dcpomatic;
SimpleVideoView::SimpleVideoView (FilmViewer* viewer, wxWindow* parent)
: VideoView (viewer)
+ , _rec2020_filter("convert", "convert", "", "colorspace=all=bt709:iall=bt2020")
+ , _rec2020_filter_graph({ &_rec2020_filter }, dcp::Fraction(24, 1))
{
_panel = new wxPanel (parent);
@@ -241,7 +245,11 @@ SimpleVideoView::update ()
_state_timer.set ("get image");
- _image = player_video().first->image(boost::bind(&PlayerVideo::force, AV_PIX_FMT_RGB24), VideoRange::FULL, true);
+ auto const pv = player_video();
+ _image = pv.first->image(boost::bind(&PlayerVideo::force, AV_PIX_FMT_RGB24), VideoRange::FULL, true);
+ if (pv.first->colour_conversion() && pv.first->colour_conversion()->about_equal(dcp::ColourConversion::rec2020_to_xyz(), 1e-6)) {
+ _image = Image::ensure_alignment(_rec2020_filter_graph.get(_image->size(), _image->pixel_format())->process(_image).front(), Image::Alignment::COMPACT);
+ }
_state_timer.set ("ImageChanged");
_viewer->image_changed (player_video().first);
diff --git a/src/wx/simple_video_view.h b/src/wx/simple_video_view.h
index 7a48f8ef8..e19068979 100644
--- a/src/wx/simple_video_view.h
+++ b/src/wx/simple_video_view.h
@@ -20,14 +20,18 @@
#include "video_view.h"
+#include "lib/filter.h"
#include "lib/position.h"
+#include "lib/video_filter_graph_set.h"
#include <dcp/types.h>
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <wx/wx.h>
LIBDCP_ENABLE_WARNINGS
+
class FilmViewer;
+class Filter;
class SimpleVideoView : public VideoView
@@ -53,4 +57,7 @@ private:
wxTimer _timer;
Position<int> _inter_position;
dcp::Size _inter_size;
+
+ Filter _rec2020_filter;
+ VideoFilterGraphSet _rec2020_filter_graph;
};