diff --git a/app/render/colortransform.h b/app/render/colortransform.h index 3598d3b5e..703bcf0e6 100644 --- a/app/render/colortransform.h +++ b/app/render/colortransform.h @@ -87,4 +87,6 @@ private: } +Q_DECLARE_METATYPE(olive::ColorTransform) + #endif // COLORTRANSFORM_H diff --git a/app/render/ipc/ipcmessage.cpp b/app/render/ipc/ipcmessage.cpp index 899620da7..f8790ae68 100644 --- a/app/render/ipc/ipcmessage.cpp +++ b/app/render/ipc/ipcmessage.cpp @@ -123,6 +123,14 @@ QJsonObject RenderFrameMsg::ToJson() const input_slot_array.append(slot); } o["input_slots"] = input_slot_array; + + if (has_color_transform) { + o["has_color_transform"] = true; + o["color_is_display"] = color_is_display; + o["color_output"] = color_output; + o["color_view"] = color_view; + o["color_look"] = color_look; + } return o; } @@ -149,6 +157,14 @@ bool RenderFrameMsg::FromJson(const QJsonObject &o, RenderFrameMsg *out) if (out->input_slots.isEmpty() && out->input_slot >= 0) { out->input_slots.append(out->input_slot); } + + out->has_color_transform = o["has_color_transform"].toBool(false); + if (out->has_color_transform) { + out->color_is_display = o["color_is_display"].toBool(false); + out->color_output = o["color_output"].toString(); + out->color_view = o["color_view"].toString(); + out->color_look = o["color_look"].toString(); + } return true; } diff --git a/app/render/ipc/ipcmessage.h b/app/render/ipc/ipcmessage.h index 6a581460f..316088c27 100644 --- a/app/render/ipc/ipcmessage.h +++ b/app/render/ipc/ipcmessage.h @@ -116,6 +116,14 @@ struct RenderFrameMsg { int input_slot = -1; ///< Optional main->worker decoded input slot for footage nodes. QVector input_slots; ///< Optional ordered decoded input slots for footage nodes. + // Output color transform to apply before returning the frame. When empty, + // the worker returns the image in the project's reference space. + bool has_color_transform = false; + bool color_is_display = false; + QString color_output; + QString color_view; + QString color_look; + QJsonObject ToJson() const; static bool FromJson(const QJsonObject &o, RenderFrameMsg *out); }; diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index 14cd0c3de..bbe62f7a4 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -196,6 +196,8 @@ RenderTicketPtr RenderManager::RenderFrame(const RenderVideoParams ¶ms) QtUtils::PtrToValue(params.color_manager)); ticket->setProperty("coloroutput", QVariant::fromValue(params.force_color_output)); + ticket->setProperty("colortransform", + QVariant::fromValue(params.force_color_transform)); Q_ASSERT(params.video_params.is_valid()); ticket->setProperty("vparam", QVariant::fromValue(params.video_params)); ticket->setProperty("aparam", QVariant::fromValue(params.audio_params)); diff --git a/app/render/rendermanager.h b/app/render/rendermanager.h index a701007e4..33d40f2ff 100644 --- a/app/render/rendermanager.h +++ b/app/render/rendermanager.h @@ -32,6 +32,7 @@ #include "node/traverser.h" #include "render/previewautocacher.h" #include "render/renderer.h" +#include "render/colortransform.h" #include "render/renderticket.h" #include "rendercache.h" @@ -120,6 +121,7 @@ public: return_type = kFrame; force_format = PixelFormat::INVALID; force_color_output = nullptr; + force_color_transform = ColorTransform(); force_size = QSize(0, 0); force_channel_count = 0; mode = m; @@ -152,6 +154,7 @@ public: QMatrix4x4 force_matrix; PixelFormat force_format; ColorProcessorPtr force_color_output; + ColorTransform force_color_transform; }; static const rational kDryRunInterval; diff --git a/app/render/renderworkerpool.cpp b/app/render/renderworkerpool.cpp index 79331f50b..fceb393f1 100644 --- a/app/render/renderworkerpool.cpp +++ b/app/render/renderworkerpool.cpp @@ -941,6 +941,13 @@ RenderWorkerPool::JobResult RenderWorkerPool::ProcessJobAttempt( render.input_slot = input_slots.isEmpty() ? -1 : input_slots.front(); render.input_slots = input_slots; + const ColorTransform &ct = job.params.force_color_transform; + render.has_color_transform = !ct.output().isEmpty(); + render.color_is_display = ct.is_display(); + render.color_output = ct.output(); + render.color_view = ct.view(); + render.color_look = ct.look(); + if (!WriteControlMessage(worker->process, render.ToJson())) { if (!job.ticket->IsCancelled()) { qWarning() << "RenderWorkerPool failed to send render_frame"; diff --git a/app/render/worker/workermain.cpp b/app/render/worker/workermain.cpp index 1e8ac70e9..2fcfc2aee 100644 --- a/app/render/worker/workermain.cpp +++ b/app/render/worker/workermain.cpp @@ -48,6 +48,8 @@ #include "render/opengl/openglrenderer.h" #include "render/rendermanager.h" #include "render/renderprocessor.h" +#include "render/colorprocessor.h" +#include "render/colortransform.h" namespace { @@ -390,7 +392,26 @@ private: ticket->setProperty("mode", olive::RenderMode::Mode(message.mode)); ticket->setProperty("type", olive::RenderManager::kTypeVideo); ticket->setProperty("colormanager", olive::QtUtils::PtrToValue(project_->color_manager())); - ticket->setProperty("coloroutput", QVariant::fromValue(olive::ColorProcessorPtr())); + + { + olive::ColorProcessorPtr color_output; + if (message.has_color_transform) { + olive::ColorTransform transform; + if (message.color_is_display) { + transform = olive::ColorTransform(message.color_output, + message.color_view, + message.color_look); + } else { + transform = olive::ColorTransform(message.color_output); + } + color_output = olive::ColorProcessor::Create( + project_->color_manager(), + project_->color_manager()->GetReferenceColorSpace(), + transform); + } + ticket->setProperty("coloroutput", + QVariant::fromValue(color_output)); + } ticket->setProperty("vparam", QVariant::fromValue(vparams)); ticket->setProperty("aparam", QVariant::fromValue(olive::AudioParams())); ticket->setProperty("return", olive::RenderManager::kFrame); diff --git a/app/task/export/export.cpp b/app/task/export/export.cpp index 42c054204..e499e93ab 100644 --- a/app/task/export/export.cpp +++ b/app/task/export/export.cpp @@ -173,7 +173,7 @@ bool ExportTask::Run() Render(color_manager_, video_range, audio_range, subtitle_range, RenderMode::kOnline, nullptr, video_force_size, video_force_matrix, encoder_->GetDesiredPixelFormat(), VideoParams::kRGBAChannelCount, - color_processor_); + color_processor_, params_.color_transform()); bool success = true; diff --git a/app/task/render/render.cpp b/app/task/render/render.cpp index 1ebcf6fe4..593d1adc9 100644 --- a/app/task/render/render.cpp +++ b/app/task/render/render.cpp @@ -43,7 +43,8 @@ bool RenderTask::Render(ColorManager *manager, const TimeRangeList &video_range, FrameHashCache *cache, const QSize &force_size, const QMatrix4x4 &force_matrix, PixelFormat force_format, int force_channel_count, - ColorProcessorPtr force_color_output) + ColorProcessorPtr force_color_output, + const ColorTransform &force_color_transform) { QMetaObject::invokeMethod(RenderManager::instance(), "SetAggressiveGarbageCollection", @@ -91,7 +92,7 @@ bool RenderTask::Render(ColorManager *manager, const TimeRangeList &video_range, i < maximum_rendered_frames && iterator.GetNext(&next_frame); i++) { StartTicket(&watcher_thread, manager, next_frame, mode, cache, force_size, force_matrix, force_format, force_channel_count, - force_color_output); + force_color_output, force_color_transform); } bool result = true; @@ -224,7 +225,8 @@ bool RenderTask::Render(ColorManager *manager, const TimeRangeList &video_range, if (iterator.GetNext(&next_frame)) { StartTicket(&watcher_thread, manager, next_frame, mode, cache, force_size, force_matrix, force_format, - force_channel_count, force_color_output); + force_channel_count, force_color_output, + force_color_transform); } } @@ -313,7 +315,8 @@ void RenderTask::StartTicket(QThread *watcher_thread, ColorManager *manager, FrameHashCache *cache, const QSize &force_size, const QMatrix4x4 &force_matrix, PixelFormat force_format, int force_channel_count, - ColorProcessorPtr force_color_output) + ColorProcessorPtr force_color_output, + const ColorTransform &force_color_transform) { RenderManager::RenderVideoParams rvp(viewer_->GetConnectedTextureOutput(), video_params_, audio_params_, time, @@ -323,6 +326,7 @@ void RenderTask::StartTicket(QThread *watcher_thread, ColorManager *manager, rvp.force_matrix = force_matrix; rvp.force_format = force_format; rvp.force_color_output = force_color_output; + rvp.force_color_transform = force_color_transform; rvp.force_channel_count = force_channel_count; if (cache) { diff --git a/app/task/render/render.h b/app/task/render/render.h index 51a589661..f943a243b 100644 --- a/app/task/render/render.h +++ b/app/task/render/render.h @@ -48,7 +48,8 @@ protected: const QMatrix4x4 &force_matrix = QMatrix4x4(), PixelFormat force_format = PixelFormat::INVALID, int force_channel_count = 0, - ColorProcessorPtr force_color_output = nullptr); + ColorProcessorPtr force_color_output = nullptr, + const ColorTransform &force_color_transform = ColorTransform()); virtual bool DownloadFrame(QThread *thread, FramePtr frame, const rational &time); @@ -124,8 +125,8 @@ private: const rational &time, RenderMode::Mode mode, FrameHashCache *cache, const QSize &force_size, const QMatrix4x4 &force_matrix, PixelFormat force_format, - int force_channel_count, - ColorProcessorPtr force_color_output); + int force_channel_count, ColorProcessorPtr force_color_output, + const ColorTransform &force_color_transform); ViewerOutput *viewer_;