Apply export color transform in render worker

The export color transform was passed to RenderTask as a ColorProcessor,
but the worker IPC render_frame message never carried it. The worker
always set the ticket's coloroutput to null, so frames were returned in
the project's reference space and encoded without the chosen output
transform (e.g. Rec.709 / sRGB), causing the exported video to look
wrongly tinted.

Serialize the ColorTransform through the render_frame control message
and reconstruct the ColorProcessor on the worker side before rendering.
Also expose ColorTransform as a Qt metatype so it can be stored in a
ticket QVariant.
This commit is contained in:
2026-07-13 10:19:30 +08:00
parent 43599dba9e
commit d66779d281
10 changed files with 73 additions and 9 deletions
+2
View File
@@ -87,4 +87,6 @@ private:
}
Q_DECLARE_METATYPE(olive::ColorTransform)
#endif // COLORTRANSFORM_H
+16
View File
@@ -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;
}
+8
View File
@@ -116,6 +116,14 @@ struct RenderFrameMsg {
int input_slot = -1; ///< Optional main->worker decoded input slot for footage nodes.
QVector<int> 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);
};
+2
View File
@@ -196,6 +196,8 @@ RenderTicketPtr RenderManager::RenderFrame(const RenderVideoParams &params)
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));
+3
View File
@@ -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;
+7
View File
@@ -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";
+22 -1
View File
@@ -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);
+1 -1
View File
@@ -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;
+8 -4
View File
@@ -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) {
+4 -3
View File
@@ -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_;