Files
oak-editor/app/render/ipc/ipcmessage.cpp
T
Mike-Solar d66779d281 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.
2026-07-13 10:19:30 +08:00

232 lines
6.1 KiB
C++

/***
Oak - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program 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 3 of the License, or
(at your option) any later version.
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "ipcmessage.h"
#include <QJsonArray>
#include <QJsonDocument>
#include <QIODevice>
namespace olive
{
namespace ipc
{
bool WriteMessage(QIODevice *device, const QJsonObject &obj)
{
QByteArray line = QJsonDocument(obj).toJson(QJsonDocument::Compact);
line.append('\n');
return device->write(line) == line.size();
}
bool ReadMessage(QByteArray *buffer, QJsonObject *out, bool *ok)
{
const int newline = buffer->indexOf('\n');
if (newline < 0) {
// No complete line buffered yet.
return false;
}
const QByteArray line = buffer->left(newline);
buffer->remove(0, newline + 1);
// Skip blank lines silently (e.g. a stray newline) without flagging an error.
if (line.trimmed().isEmpty()) {
if (ok) {
*ok = false;
}
return false;
}
QJsonParseError err;
const QJsonDocument doc = QJsonDocument::fromJson(line, &err);
if (err.error != QJsonParseError::NoError || !doc.isObject()) {
if (ok) {
*ok = false;
}
return false;
}
*out = doc.object();
if (ok) {
*ok = true;
}
return true;
}
// ---- HandshakeMsg ---------------------------------------------------------------------------
QJsonObject HandshakeMsg::ToJson() const
{
QJsonObject o;
o["type"] = msgtype::kHandshake;
o["protocol_version"] = protocol_version;
o["shm_key"] = shm_key;
o["input_shm_key"] = input_shm_key;
o["input_slots"] = input_slots;
o["output_slots"] = output_slots;
o["slot_data_bytes"] = double(slot_data_bytes);
o["input_slot_data_bytes"] = double(input_slot_data_bytes);
return o;
}
bool HandshakeMsg::FromJson(const QJsonObject &o, HandshakeMsg *out)
{
if (o["type"].toString() != QLatin1String(msgtype::kHandshake)) {
return false;
}
out->protocol_version = o["protocol_version"].toInt();
out->shm_key = o["shm_key"].toString();
out->input_shm_key = o["input_shm_key"].toString();
out->input_slots = o["input_slots"].toInt();
out->output_slots = o["output_slots"].toInt();
out->slot_data_bytes = qint64(o["slot_data_bytes"].toDouble());
out->input_slot_data_bytes = qint64(o["input_slot_data_bytes"].toDouble());
return true;
}
// ---- RenderFrameMsg -------------------------------------------------------------------------
QJsonObject RenderFrameMsg::ToJson() const
{
QJsonObject o;
o["type"] = msgtype::kRenderFrame;
o["ticket"] = double(ticket_id);
o["node"] = node_uuid;
o["time_num"] = double(time_num);
o["time_den"] = double(time_den);
o["width"] = width;
o["height"] = height;
o["format"] = format;
o["channels"] = channel_count;
o["mode"] = mode;
o["input_slot"] = input_slot;
QJsonArray input_slot_array;
for (int slot : input_slots) {
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;
}
bool RenderFrameMsg::FromJson(const QJsonObject &o, RenderFrameMsg *out)
{
if (o["type"].toString() != QLatin1String(msgtype::kRenderFrame)) {
return false;
}
out->ticket_id = qint64(o["ticket"].toDouble());
out->node_uuid = o["node"].toString();
out->time_num = qint64(o["time_num"].toDouble());
out->time_den = qint64(o["time_den"].toDouble(1));
out->width = o["width"].toInt();
out->height = o["height"].toInt();
out->format = o["format"].toInt(-1);
out->channel_count = o["channels"].toInt();
out->mode = o["mode"].toInt();
out->input_slot = o["input_slot"].toInt(-1);
out->input_slots.clear();
const QJsonArray input_slot_array = o["input_slots"].toArray();
for (const QJsonValue &slot : input_slot_array) {
out->input_slots.append(slot.toInt(-1));
}
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;
}
// ---- FrameReadyMsg --------------------------------------------------------------------------
QJsonObject FrameReadyMsg::ToJson() const
{
QJsonObject o;
o["type"] = msgtype::kFrameReady;
o["ticket"] = double(ticket_id);
o["slot"] = output_slot;
return o;
}
bool FrameReadyMsg::FromJson(const QJsonObject &o, FrameReadyMsg *out)
{
if (o["type"].toString() != QLatin1String(msgtype::kFrameReady)) {
return false;
}
out->ticket_id = qint64(o["ticket"].toDouble());
out->output_slot = o["slot"].toInt();
return true;
}
// ---- CancelMsg ------------------------------------------------------------------------------
QJsonObject CancelMsg::ToJson() const
{
QJsonObject o;
o["type"] = msgtype::kCancel;
o["ticket"] = double(ticket_id);
return o;
}
bool CancelMsg::FromJson(const QJsonObject &o, CancelMsg *out)
{
if (o["type"].toString() != QLatin1String(msgtype::kCancel)) {
return false;
}
out->ticket_id = qint64(o["ticket"].toDouble());
return true;
}
// ---- LoadGraphMsg ---------------------------------------------------------------------------
QJsonObject LoadGraphMsg::ToJson() const
{
QJsonObject o;
o["type"] = msgtype::kLoadGraph;
o["path"] = path;
return o;
}
bool LoadGraphMsg::FromJson(const QJsonObject &o, LoadGraphMsg *out)
{
if (o["type"].toString() != QLatin1String(msgtype::kLoadGraph)) {
return false;
}
out->path = o["path"].toString();
return true;
}
} // namespace ipc
} // namespace olive