build: split the engine into liboakengine.so; worker drops the UI entirely

Physical split: app/{audio,cli,codec,common,config,node,pluginSupport,
render,task,timeline,undo,tool,shaders} plus coreengine, version and
ui/icons+colorcoding move to a new top-level engine/ tree, built as
liboakengine.so (shared). The render backends (oakgl/oakvulkan) move
with it and link the engine library instead of embedding a static
render-core subset (libolive-rendercore is gone).

- oak-render-worker now links liboakengine instead of the whole
  libolive-editor object set: 336MB -> 2.9MB, no Qt Widgets UI
- the editor links liboakengine for the engine and keeps only UI
  objects in libolive-editor
- install/packaging: GNUInstallDirs libdir on Linux, bundle copy on
  macOS, oakengine.dll staged for NSIS, AppImage validation entry
- fix backend lookup for the new layout: DynamicRenderer searched
  ../app but backends now live in engine/; a stale pre-split liboakgl
  in the build tree got dlopened instead, re-initialized and later
  destroyed the interposed engine statics (full-suite segfault at
  DialogSequenceParameterTab, found via gdb watchpoint)
This commit is contained in:
2026-07-20 03:23:28 +08:00
parent 026ff94b5e
commit 28c4426236
604 changed files with 243 additions and 172 deletions
+27
View File
@@ -0,0 +1,27 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2022 Olive 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/>.
add_subdirectory(matrix)
add_subdirectory(noise)
add_subdirectory(polygon)
add_subdirectory(shape)
add_subdirectory(solid)
add_subdirectory(text)
set(OLIVE_SOURCES
${OLIVE_SOURCES}
PARENT_SCOPE
)
@@ -0,0 +1,22 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2022 Olive 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/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
node/generator/matrix/matrix.h
node/generator/matrix/matrix.cpp
PARENT_SCOPE
)
+167
View File
@@ -0,0 +1,167 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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 "matrix.h"
#include <QMatrix4x4>
#include <QVector2D>
#include "node/sliderdisplaytype.h"
namespace olive
{
const QString MatrixGenerator::k_position_input = QStringLiteral("pos_in");
const QString MatrixGenerator::k_rotation_input = QStringLiteral("rot_in");
const QString MatrixGenerator::k_scale_input = QStringLiteral("scale_in");
const QString MatrixGenerator::k_uniform_scale_input =
QStringLiteral("uniform_scale_in");
const QString MatrixGenerator::k_anchor_input = QStringLiteral("anchor_in");
#define super Node
MatrixGenerator::MatrixGenerator()
{
add_input(k_position_input, NodeValue::k_vec2, QVector2D(0.0, 0.0));
add_input(k_rotation_input, NodeValue::k_float, 0.0);
add_input(k_scale_input, NodeValue::k_vec2, QVector2D(1.0f, 1.0f));
set_input_property(k_scale_input, QStringLiteral("min"), QVector2D(0, 0));
set_input_property(k_scale_input, QStringLiteral("view"),
slider::k_percentage);
set_input_property(k_scale_input, QStringLiteral("disable1"), true);
add_input(k_uniform_scale_input, NodeValue::k_boolean, true,
InputFlags(k_input_flag_not_connectable | k_input_flag_not_keyframable));
add_input(k_anchor_input, NodeValue::k_vec2, QVector2D(0.0, 0.0));
}
QString MatrixGenerator::name() const
{
return tr("Orthographic Matrix");
}
QString MatrixGenerator::short_name() const
{
return tr("Ortho");
}
QString MatrixGenerator::id() const
{
return QStringLiteral("org.olivevideoeditor.Olive.ortho");
}
QVector<Node::CategoryID> MatrixGenerator::category() const
{
return { k_category_generator, k_category_math };
}
QString MatrixGenerator::description() const
{
return tr(
"Generate an orthographic matrix using position, rotation, and scale.");
}
void MatrixGenerator::retranslate()
{
super::retranslate();
set_input_name(k_position_input, tr("Position"));
set_input_name(k_rotation_input, tr("Rotation"));
set_input_name(k_scale_input, tr("Scale"));
set_input_name(k_uniform_scale_input, tr("Uniform Scale"));
set_input_name(k_anchor_input, tr("Anchor Point"));
}
void MatrixGenerator::value(const NodeValueRow &value,
const NodeGlobals &globals,
NodeValueTable *table) const
{
// Push matrix output
QMatrix4x4 mat = generate_matrix(value, false, false, false, QMatrix4x4());
table->push(NodeValue::k_matrix, mat, this);
}
QMatrix4x4 MatrixGenerator::generate_matrix(const NodeValueRow &value,
bool ignore_anchor,
bool ignore_position,
bool ignore_scale,
const QMatrix4x4 &mat) const
{
QVector2D anchor;
QVector2D position;
QVector2D scale;
if (!ignore_anchor) {
anchor = value[k_anchor_input].to_vec2();
}
if (!ignore_scale) {
scale = value[k_scale_input].to_vec2();
}
if (!ignore_position) {
position = value[k_position_input].to_vec2();
}
return generate_matrix(position, value[k_rotation_input].to_double(), scale,
value[k_uniform_scale_input].to_bool(), anchor, mat);
}
QMatrix4x4
MatrixGenerator::generate_matrix(const QVector2D &pos, const float &rot,
const QVector2D &scale, bool uniform_scale,
const QVector2D &anchor, QMatrix4x4 mat)
{
// Position
mat.translate(pos.x(), pos.y());
// Rotation
mat.rotate(rot, 0, 0, 1);
// Scale (convert to a QVector3D so that the identity matrix is preserved if all values are 1.0f)
QVector3D full_scale;
if (uniform_scale) {
full_scale = QVector3D(scale.x(), scale.x(), 1.0f);
} else {
full_scale = QVector3D(scale, 1.0f);
}
mat.scale(full_scale);
// Anchor Point
mat.translate(-anchor.x(), -anchor.y());
return mat;
}
void MatrixGenerator::InputValueChangedEvent(const QString &input, int element)
{
Q_UNUSED(element)
if (input == k_uniform_scale_input) {
set_input_property(k_scale_input, QStringLiteral("disable1"),
get_standard_value(k_uniform_scale_input).toBool());
}
}
}
+71
View File
@@ -0,0 +1,71 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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/>.
***/
#ifndef OAK_MATRIXGENERATOR_H
#define OAK_MATRIXGENERATOR_H
#include <QVector2D>
#include "node/node.h"
#include "node/inputdragger.h"
namespace olive
{
class MatrixGenerator : public Node {
Q_OBJECT
public:
MatrixGenerator();
NODE_DEFAULT_FUNCTIONS(MatrixGenerator)
virtual QString name() const override;
virtual QString short_name() const override;
virtual QString id() const override;
virtual QVector<CategoryID> category() const override;
virtual QString description() const override;
virtual void retranslate() override;
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
NodeValueTable *table) const override;
static const QString k_position_input;
static const QString k_rotation_input;
static const QString k_scale_input;
static const QString k_uniform_scale_input;
static const QString k_anchor_input;
protected:
QMatrix4x4 generate_matrix(const NodeValueRow &value, bool ignore_anchor,
bool ignore_position, bool ignore_scale,
const QMatrix4x4 &mat) const;
static QMatrix4x4 generate_matrix(const QVector2D &pos, const float &rot,
const QVector2D &scale, bool uniform_scale,
const QVector2D &anchor, QMatrix4x4 mat);
virtual void InputValueChangedEvent(const QString &input,
int element) override;
};
}
#endif // TRANSFORMDISTORT_H
@@ -0,0 +1,22 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2022 Olive 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/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
node/generator/noise/noise.h
node/generator/noise/noise.cpp
PARENT_SCOPE
)
+103
View File
@@ -0,0 +1,103 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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 "noise.h"
#include "node/sliderdisplaytype.h"
namespace olive
{
const QString NoiseGeneratorNode::k_base_in = QStringLiteral("base_in");
const QString NoiseGeneratorNode::k_color_input = QStringLiteral("color_in");
const QString NoiseGeneratorNode::k_strength_input =
QStringLiteral("strength_in");
#define super Node
NoiseGeneratorNode::NoiseGeneratorNode()
{
add_input(k_base_in, NodeValue::k_texture,
InputFlags(k_input_flag_not_keyframable));
add_input(k_strength_input, NodeValue::k_float, 0.2);
set_input_property(k_strength_input, QStringLiteral("view"),
slider::k_percentage);
set_input_property(k_strength_input, QStringLiteral("min"), 0);
add_input(k_color_input, NodeValue::k_boolean, false);
set_effect_input(k_base_in);
set_flag(k_video_effect);
}
QString NoiseGeneratorNode::name() const
{
return tr("Noise");
}
QString NoiseGeneratorNode::id() const
{
return QStringLiteral("org.olivevideoeditor.Olive.noise");
}
QVector<Node::CategoryID> NoiseGeneratorNode::category() const
{
return { k_category_generator };
}
QString NoiseGeneratorNode::description() const
{
return tr("Generates noise patterns");
}
void NoiseGeneratorNode::retranslate()
{
super::retranslate();
set_input_name(k_base_in, tr("Base"));
set_input_name(k_strength_input, tr("Strength"));
set_input_name(k_color_input, tr("Color"));
}
ShaderCode NoiseGeneratorNode::get_shader_code(const ShaderRequest &request) const
{
return ShaderCode(FileFunctions::read_file_as_string(":/shaders/noise.frag"));
}
void NoiseGeneratorNode::value(const NodeValueRow &value,
const NodeGlobals &globals,
NodeValueTable *table) const
{
ShaderJob job(value);
job.insert(value);
job.insert(QStringLiteral("time_in"),
NodeValue(NodeValue::k_float, globals.time().in().to_double(),
this));
TexturePtr base = value[k_base_in].to_texture();
table->push(NodeValue::k_texture,
Texture::job(base ? base->params() : globals.vparams(), job),
this);
}
}
+56
View File
@@ -0,0 +1,56 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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/>.
***/
#ifndef OAK_NOISEGENERATORNODE_H
#define OAK_NOISEGENERATORNODE_H
#include "node/node.h"
namespace olive
{
class NoiseGeneratorNode : public Node {
Q_OBJECT
public:
NoiseGeneratorNode();
NODE_DEFAULT_FUNCTIONS(NoiseGeneratorNode)
virtual QString name() const override;
virtual QString id() const override;
virtual QVector<CategoryID> category() const override;
virtual QString description() const override;
virtual void retranslate() override;
virtual ShaderCode
get_shader_code(const ShaderRequest &request) const override;
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
NodeValueTable *table) const override;
static const QString k_base_in;
static const QString k_color_input;
static const QString k_strength_input;
};
} // namespace olive
#endif // OAK_NOISEGENERATORNODE_H
@@ -0,0 +1,22 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2022 Olive 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/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
node/generator/polygon/polygon.h
node/generator/polygon/polygon.cpp
PARENT_SCOPE
)
+303
View File
@@ -0,0 +1,303 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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 "polygon.h"
#include <QGuiApplication>
#include <QVector2D>
namespace olive
{
const QString PolygonGenerator::k_points_input = QStringLiteral("points_in");
const QString PolygonGenerator::k_color_input = QStringLiteral("color_in");
#define super GeneratorWithMerge
PolygonGenerator::PolygonGenerator()
{
add_input(k_points_input, NodeValue::k_bezier, QVector2D(0, 0),
InputFlags(k_input_flag_array));
add_input(k_color_input, NodeValue::k_color,
QVariant::fromValue(Color(1.0, 1.0, 1.0)));
const int k_middle_x = 135;
const int k_middle_y = 45;
const int k_bottom_x = 90;
const int k_bottom_y = 120;
const int k_top_y = 135;
// The Default Pentagon(tm)
input_array_resize(k_points_input, 5);
set_split_standard_value_on_track(k_points_input, 0, 0, 0);
set_split_standard_value_on_track(k_points_input, 1, -k_top_y, 0);
set_split_standard_value_on_track(k_points_input, 0, k_middle_x, 1);
set_split_standard_value_on_track(k_points_input, 1, -k_middle_y, 1);
set_split_standard_value_on_track(k_points_input, 0, k_bottom_x, 2);
set_split_standard_value_on_track(k_points_input, 1, k_bottom_y, 2);
set_split_standard_value_on_track(k_points_input, 0, -k_bottom_x, 3);
set_split_standard_value_on_track(k_points_input, 1, k_bottom_y, 3);
set_split_standard_value_on_track(k_points_input, 0, -k_middle_x, 4);
set_split_standard_value_on_track(k_points_input, 1, -k_middle_y, 4);
// Initiate gizmos
poly_gizmo_ = new PathGizmo(this);
}
QString PolygonGenerator::name() const
{
return tr("Polygon");
}
QString PolygonGenerator::id() const
{
return QStringLiteral("org.olivevideoeditor.Olive.polygon");
}
QVector<Node::CategoryID> PolygonGenerator::category() const
{
return { k_category_generator };
}
QString PolygonGenerator::description() const
{
return tr("Generate a 2D polygon of any amount of points.");
}
void PolygonGenerator::retranslate()
{
super::retranslate();
set_input_name(k_points_input, tr("Points"));
set_input_name(k_color_input, tr("Color"));
}
ShaderJob PolygonGenerator::get_generate_job(const NodeValueRow &value,
const VideoParams &params) const
{
VideoParams p = params;
p.set_format(PixelFormat::u8);
auto job = Texture::job(p, GenerateJob(value));
// Conversion to RGB
ShaderJob rgb;
rgb.set_shader_id(QStringLiteral("rgb"));
rgb.insert(QStringLiteral("texture_in"),
NodeValue(NodeValue::k_texture, job, this));
rgb.insert(QStringLiteral("color_in"), value[k_color_input]);
return rgb;
}
void PolygonGenerator::value(const NodeValueRow &value,
const NodeGlobals &globals,
NodeValueTable *table) const
{
push_mergable_job(value,
Texture::job(globals.vparams(),
get_generate_job(value, globals.vparams())),
table);
}
void PolygonGenerator::generate_frame(FramePtr frame,
const GenerateJob &job) const
{
// This could probably be more optimized, but for now we use Qt to draw to a QImage.
// QImages only support integer pixels and we use float pixels, so what we do here is draw onto
// a single-channel QImage (alpha only) and then transplant that alpha channel to our float buffer
// with correct float RGB.
QImage img((uchar *)frame->data(), frame->width(), frame->height(),
frame->linesize_bytes(), QImage::Format_RGBA8888_Premultiplied);
img.fill(Qt::transparent);
auto points = job.get(k_points_input).to_array();
QPainterPath path = generate_path(points, input_array_size(k_points_input));
QPainter p(&img);
double par = frame->video_params().pixel_aspect_ratio().to_double();
p.scale(1.0 / frame->video_params().divider() / par,
1.0 / frame->video_params().divider());
p.translate(frame->video_params().width() / 2 * par,
frame->video_params().height() / 2);
p.setBrush(Qt::white);
p.setPen(Qt::NoPen);
p.drawPath(path);
}
template <typename T> NodeGizmo *PolygonGenerator::create_appropriate_gizmo()
{
return new T(this);
}
template <> NodeGizmo *PolygonGenerator::create_appropriate_gizmo<PointGizmo>()
{
return add_draggable_gizmo<PointGizmo>();
}
template <typename T>
void PolygonGenerator::validate_gizmo_vector_size(QVector<T *> &vec, int new_sz)
{
int old_sz = vec.size();
if (old_sz != new_sz) {
if (old_sz > new_sz) {
for (int i = new_sz; i < old_sz; i++) {
delete vec.at(i);
}
}
vec.resize(new_sz);
if (old_sz < new_sz) {
for (int i = old_sz; i < new_sz; i++) {
vec[i] = static_cast<T *>(create_appropriate_gizmo<T>());
}
}
}
}
void PolygonGenerator::update_gizmo_positions(const NodeValueRow &row,
const NodeGlobals &globals)
{
QVector2D res;
if (TexturePtr tex = row[k_base_input].to_texture()) {
res = tex->virtual_resolution();
} else {
res = globals.square_resolution();
}
Imath::V2d half_res(res.x() / 2, res.y() / 2);
auto points = row[k_points_input].to_array();
int current_pos_sz = gizmo_position_handles_.size();
validate_gizmo_vector_size(gizmo_position_handles_, points.size());
validate_gizmo_vector_size(gizmo_bezier_handles_, points.size() * 2);
validate_gizmo_vector_size(gizmo_bezier_lines_, points.size() * 2);
for (int i = current_pos_sz; i < gizmo_position_handles_.size(); i++) {
gizmo_position_handles_.at(i)->add_input(
NodeKeyframeTrackReference(NodeInput(this, k_points_input, i), 0));
gizmo_position_handles_.at(i)->add_input(
NodeKeyframeTrackReference(NodeInput(this, k_points_input, i), 1));
PointGizmo *bez_gizmo1 = gizmo_bezier_handles_.at(i * 2 + 0);
bez_gizmo1->add_input(
NodeKeyframeTrackReference(NodeInput(this, k_points_input, i), 2));
bez_gizmo1->add_input(
NodeKeyframeTrackReference(NodeInput(this, k_points_input, i), 3));
bez_gizmo1->set_shape(PointGizmo::k_circle);
bez_gizmo1->set_smaller(true);
PointGizmo *bez_gizmo2 = gizmo_bezier_handles_.at(i * 2 + 1);
bez_gizmo2->add_input(
NodeKeyframeTrackReference(NodeInput(this, k_points_input, i), 4));
bez_gizmo2->add_input(
NodeKeyframeTrackReference(NodeInput(this, k_points_input, i), 5));
bez_gizmo2->set_shape(PointGizmo::k_circle);
bez_gizmo2->set_smaller(true);
}
int pts_sz = input_array_size(k_points_input);
if (!points.empty()) {
for (int i = 0; i < pts_sz; i++) {
const Bezier &pt = points.at(i).to_bezier();
Imath::V2d main = pt.to_vec() + half_res;
Imath::V2d cp1 = main + pt.control_point_1_to_vec();
Imath::V2d cp2 = main + pt.control_point_2_to_vec();
gizmo_position_handles_[i]->set_point(QPointF(main.x, main.y));
gizmo_bezier_handles_[i * 2]->set_point(QPointF(cp1.x, cp1.y));
gizmo_bezier_lines_[i * 2]->set_line(
QLineF(QPointF(main.x, main.y), QPointF(cp1.x, cp1.y)));
gizmo_bezier_handles_[i * 2 + 1]->set_point(QPointF(cp2.x, cp2.y));
gizmo_bezier_lines_[i * 2 + 1]->set_line(
QLineF(QPointF(main.x, main.y), QPointF(cp2.x, cp2.y)));
}
}
poly_gizmo_->set_path(generate_path(points, pts_sz)
.translated(QPointF(half_res.x, half_res.y)));
}
ShaderCode PolygonGenerator::get_shader_code(const ShaderRequest &request) const
{
if (request.id == QStringLiteral("rgb")) {
return ShaderCode(
FileFunctions::read_file_as_string(":/shaders/rgb.frag"));
} else {
return super::get_shader_code(request);
}
}
void PolygonGenerator::gizmo_drag_move(double x, double y,
const Qt::KeyboardModifiers &modifiers)
{
DraggableGizmo *gizmo = static_cast<DraggableGizmo *>(sender());
if (gizmo == poly_gizmo_) {
// FIXME: Drag all points
} else {
NodeInputDragger &x_drag = gizmo->get_draggers()[0];
NodeInputDragger &y_drag = gizmo->get_draggers()[1];
x_drag.drag(x_drag.get_start_value().toDouble() + x);
y_drag.drag(y_drag.get_start_value().toDouble() + y);
}
}
void PolygonGenerator::add_point_to_path(QPainterPath *path, const Bezier &before,
const Bezier &after)
{
Imath::V2d a = before.to_vec() + before.control_point_2_to_vec();
Imath::V2d b = after.to_vec() + after.control_point_1_to_vec();
Imath::V2d c = after.to_vec();
path->cubicTo(QPointF(a.x, a.y), QPointF(b.x, b.y), QPointF(c.x, c.y));
}
QPainterPath PolygonGenerator::generate_path(const NodeValueArray &points,
int size)
{
QPainterPath path;
if (!points.empty()) {
const Bezier &first_pt = points.at(0).to_bezier();
Imath::V2d v = first_pt.to_vec();
path.moveTo(QPointF(v.x, v.y));
for (int i = 1; i < size; i++) {
add_point_to_path(&path, points.at(i - 1).to_bezier(),
points.at(i).to_bezier());
}
add_point_to_path(&path, points.at(size - 1).to_bezier(), first_pt);
}
return path;
}
}
+93
View File
@@ -0,0 +1,93 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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/>.
***/
#ifndef OAK_POLYGONGENERATOR_H
#define OAK_POLYGONGENERATOR_H
#include <QPainterPath>
#include "node/generator/shape/generatorwithmerge.h"
#include "node/gizmo/line.h"
#include "node/gizmo/path.h"
#include "node/gizmo/point.h"
#include "node/node.h"
#include "node/inputdragger.h"
namespace olive
{
class PolygonGenerator : public GeneratorWithMerge {
Q_OBJECT
public:
PolygonGenerator();
NODE_DEFAULT_FUNCTIONS(PolygonGenerator)
virtual QString name() const override;
virtual QString id() const override;
virtual QVector<CategoryID> category() const override;
virtual QString description() const override;
virtual void retranslate() override;
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
NodeValueTable *table) const override;
virtual void generate_frame(FramePtr frame,
const GenerateJob &job) const override;
virtual void update_gizmo_positions(const NodeValueRow &row,
const NodeGlobals &globals) override;
virtual ShaderCode
get_shader_code(const ShaderRequest &request) const override;
static const QString k_points_input;
static const QString k_color_input;
protected:
ShaderJob get_generate_job(const NodeValueRow &value,
const VideoParams &params) const;
protected slots:
virtual void gizmo_drag_move(double x, double y,
const Qt::KeyboardModifiers &modifiers) override;
private:
static void add_point_to_path(QPainterPath *path, const Bezier &before,
const Bezier &after);
static QPainterPath generate_path(const NodeValueArray &points, int size);
template <typename T>
void validate_gizmo_vector_size(QVector<T *> &vec, int new_sz);
template <typename T> NodeGizmo *create_appropriate_gizmo();
PathGizmo *poly_gizmo_;
QVector<PointGizmo *> gizmo_position_handles_;
QVector<PointGizmo *> gizmo_bezier_handles_;
QVector<LineGizmo *> gizmo_bezier_lines_;
};
}
#endif // OAK_POLYGONGENERATOR_H
@@ -0,0 +1,26 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2022 Olive 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/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
node/generator/shape/generatorwithmerge.cpp
node/generator/shape/generatorwithmerge.h
node/generator/shape/shapenode.cpp
node/generator/shape/shapenode.h
node/generator/shape/shapenodebase.cpp
node/generator/shape/shapenodebase.h
PARENT_SCOPE
)
@@ -0,0 +1,78 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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 "generatorwithmerge.h"
#include "node/math/merge/merge.h"
namespace olive
{
#define super Node
const QString GeneratorWithMerge::k_base_input = QStringLiteral("base_in");
GeneratorWithMerge::GeneratorWithMerge()
{
add_input(k_base_input, NodeValue::k_texture,
InputFlags(k_input_flag_not_keyframable));
set_effect_input(k_base_input);
set_flag(k_video_effect);
}
void GeneratorWithMerge::retranslate()
{
super::retranslate();
set_input_name(k_base_input, tr("Base"));
}
ShaderCode GeneratorWithMerge::get_shader_code(const ShaderRequest &request) const
{
if (request.id == QStringLiteral("mrg")) {
return ShaderCode(
FileFunctions::read_file_as_string(":/shaders/alphaover.frag"));
}
return ShaderCode();
}
void GeneratorWithMerge::push_mergable_job(const NodeValueRow &value,
TexturePtr job,
NodeValueTable *table) const
{
if (TexturePtr base = value[k_base_input].to_texture()) {
// Push as merge node
ShaderJob merge;
merge.set_shader_id(QStringLiteral("mrg"));
merge.insert(MergeNode::k_base_in, value[k_base_input]);
merge.insert(MergeNode::k_blend_in,
NodeValue(NodeValue::k_texture, job, this));
table->push(NodeValue::k_texture, base->to_job(merge), this);
} else {
// Just push generate job
table->push(NodeValue::k_texture, job, this);
}
}
}
@@ -0,0 +1,49 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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/>.
***/
#ifndef OAK_GENERATORWITHMERGE_H
#define OAK_GENERATORWITHMERGE_H
#include "node/node.h"
namespace olive
{
class GeneratorWithMerge : public Node {
Q_OBJECT
public:
GeneratorWithMerge();
virtual void retranslate() override;
virtual ShaderCode
get_shader_code(const ShaderRequest &request) const override;
static const QString k_base_input;
protected:
void push_mergable_job(const NodeValueRow &value, TexturePtr job,
NodeValueTable *table) const;
};
}
#endif // OAK_GENERATORWITHMERGE_H
+111
View File
@@ -0,0 +1,111 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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 "shapenode.h"
namespace olive
{
#define super ShapeNodeBase
QString ShapeNode::k_type_input = QStringLiteral("type_in");
QString ShapeNode::k_radius_input = QStringLiteral("radius_in");
ShapeNode::ShapeNode()
{
prepend_input(k_type_input, NodeValue::k_combo);
add_input(k_radius_input, NodeValue::k_float, 20.0);
set_input_property(k_radius_input, QStringLiteral("min"), 0.0);
}
QString ShapeNode::name() const
{
return tr("Shape");
}
QString ShapeNode::id() const
{
return QStringLiteral("org.olivevideoeditor.Olive.shape");
}
QVector<Node::CategoryID> ShapeNode::category() const
{
return { k_category_generator };
}
QString ShapeNode::description() const
{
return tr("Generate a 2D primitive shape.");
}
void ShapeNode::retranslate()
{
super::retranslate();
set_input_name(k_type_input, tr("Type"));
set_input_name(k_radius_input, tr("Radius"));
// Coordinate with Type enum
set_combo_box_strings(k_type_input, { tr("Rectangle"), tr("Ellipse"),
tr("Rounded Rectangle") });
}
ShaderCode ShapeNode::get_shader_code(const ShaderRequest &request) const
{
if (request.id == QStringLiteral("shape")) {
return ShaderCode(FileFunctions::read_file_as_string(
QStringLiteral(":/shaders/shape.frag")));
} else {
return super::get_shader_code(request);
}
}
void ShapeNode::value(const NodeValueRow &value, const NodeGlobals &globals,
NodeValueTable *table) const
{
TexturePtr base = value[k_base_input].to_texture();
ShaderJob job(value);
job.insert(QStringLiteral("resolution_in"),
NodeValue(NodeValue::k_vec2,
base ? base->virtual_resolution() :
globals.square_resolution(),
this));
job.set_shader_id(QStringLiteral("shape"));
push_mergable_job(
value, Texture::job(base ? base->params() : globals.vparams(), job),
table);
}
void ShapeNode::InputValueChangedEvent(const QString &input, int element)
{
if (input == k_type_input) {
set_input_flag(k_radius_input, k_input_flag_hidden,
(get_standard_value(k_type_input).toInt() !=
k_rounded_rectangle));
}
super::InputValueChangedEvent(input, element);
}
}
+61
View File
@@ -0,0 +1,61 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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/>.
***/
#ifndef OAK_SHAPENODE_H
#define OAK_SHAPENODE_H
#include "shapenodebase.h"
namespace olive
{
class ShapeNode : public ShapeNodeBase {
Q_OBJECT
public:
ShapeNode();
enum Type { k_rectangle, k_ellipse, k_rounded_rectangle };
NODE_DEFAULT_FUNCTIONS(ShapeNode)
virtual QString name() const override;
virtual QString id() const override;
virtual QVector<CategoryID> category() const override;
virtual QString description() const override;
virtual void retranslate() override;
virtual ShaderCode
get_shader_code(const ShaderRequest &request) const override;
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
NodeValueTable *table) const override;
static QString k_type_input;
static QString k_radius_input;
protected:
virtual void InputValueChangedEvent(const QString &input,
int element) override;
};
}
#endif // OAK_SHAPENODE_H
@@ -0,0 +1,361 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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 "shapenodebase.h"
#include <QtMath>
#include <QVector2D>
#include "common/util.h"
#include "node/nodeundo.h"
namespace olive
{
#define super GeneratorWithMerge
const QString ShapeNodeBase::k_position_input = QStringLiteral("pos_in");
const QString ShapeNodeBase::k_size_input = QStringLiteral("size_in");
const QString ShapeNodeBase::k_color_input = QStringLiteral("color_in");
ShapeNodeBase::ShapeNodeBase(bool create_color_input)
{
add_input(k_position_input, NodeValue::k_vec2, QVector2D(0, 0));
add_input(k_size_input, NodeValue::k_vec2, QVector2D(100, 100));
set_input_property(k_size_input, QStringLiteral("min"), QVector2D(0, 0));
if (create_color_input) {
add_input(k_color_input, NodeValue::k_color,
QVariant::fromValue(Color(1.0, 0.0, 0.0, 1.0)));
}
// Initiate gizmos
QVector<NodeKeyframeTrackReference> pos_n_sz = {
NodeKeyframeTrackReference(NodeInput(this, k_position_input), 0),
NodeKeyframeTrackReference(NodeInput(this, k_position_input), 1),
NodeKeyframeTrackReference(NodeInput(this, k_size_input), 0),
NodeKeyframeTrackReference(NodeInput(this, k_size_input), 1)
};
poly_gizmo_ = add_draggable_gizmo<PolygonGizmo>({
NodeKeyframeTrackReference(NodeInput(this, k_position_input), 0),
NodeKeyframeTrackReference(NodeInput(this, k_position_input), 1),
});
for (int i = 0; i < k_gizmo_scale_count; i++) {
point_gizmo_[i] =
add_draggable_gizmo<PointGizmo>(pos_n_sz, PointGizmo::k_absolute);
}
}
void ShapeNodeBase::retranslate()
{
super::retranslate();
set_input_name(k_position_input, tr("Position"));
set_input_name(k_size_input, tr("Size"));
if (has_input_with_id(k_color_input)) {
set_input_name(k_color_input, tr("Color"));
}
}
void ShapeNodeBase::update_gizmo_positions(const NodeValueRow &row,
const NodeGlobals &globals)
{
// Use offsets to make the appearance of values that start in the top left, even though we
// really anchor around the center
QVector2D center_pt = globals.square_resolution() * 0.5;
set_input_property(k_position_input, QStringLiteral("offset"), center_pt);
QVector2D pos = row[k_position_input].to_vec2();
QVector2D sz = row[k_size_input].to_vec2();
QVector2D half_sz = sz * 0.5;
double left_pt = pos.x() + center_pt.x() - half_sz.x();
double top_pt = pos.y() + center_pt.y() - half_sz.y();
double right_pt = left_pt + sz.x();
double bottom_pt = top_pt + sz.y();
double center_x_pt = mid(left_pt, right_pt);
double center_y_pt = mid(top_pt, bottom_pt);
point_gizmo_[k_gizmo_scale_top_left]->set_point(QPointF(left_pt, top_pt));
point_gizmo_[k_gizmo_scale_top_center]->set_point(QPointF(center_x_pt, top_pt));
point_gizmo_[k_gizmo_scale_top_right]->set_point(QPointF(right_pt, top_pt));
point_gizmo_[k_gizmo_scale_bottom_left]->set_point(QPointF(left_pt, bottom_pt));
point_gizmo_[k_gizmo_scale_bottom_center]->set_point(
QPointF(center_x_pt, bottom_pt));
point_gizmo_[k_gizmo_scale_bottom_right]->set_point(
QPointF(right_pt, bottom_pt));
point_gizmo_[k_gizmo_scale_center_left]->set_point(
QPointF(left_pt, center_y_pt));
point_gizmo_[k_gizmo_scale_center_right]->set_point(
QPointF(right_pt, center_y_pt));
poly_gizmo_->set_polygon(
QRectF(left_pt, top_pt, right_pt - left_pt, bottom_pt - top_pt));
}
void ShapeNodeBase::set_rect(QRectF rect, const VideoParams &sequence_res,
MultiUndoCommand *command)
{
// Normalize around center of sequence
rect.translate(-sequence_res.width() * 0.5, -sequence_res.height() * 0.5);
rect.translate(rect.width() * 0.5, rect.height() * 0.5);
NodeInput pos(this, ShapeNodeBase::k_position_input);
NodeInput sz(this, ShapeNodeBase::k_size_input);
command->add_child(new NodeParamSetStandardValueCommand(
NodeKeyframeTrackReference(sz, 0), rect.width()));
command->add_child(new NodeParamSetStandardValueCommand(
NodeKeyframeTrackReference(sz, 1), rect.height()));
command->add_child(new NodeParamSetStandardValueCommand(
NodeKeyframeTrackReference(pos, 0), rect.x()));
command->add_child(new NodeParamSetStandardValueCommand(
NodeKeyframeTrackReference(pos, 1), rect.y()));
}
void ShapeNodeBase::gizmo_drag_move(double x, double y,
const Qt::KeyboardModifiers &modifiers)
{
DraggableGizmo *gizmo = static_cast<DraggableGizmo *>(sender());
NodeInputDragger &x_drag = gizmo->get_draggers()[0];
NodeInputDragger &y_drag = gizmo->get_draggers()[1];
if (gizmo == poly_gizmo_) {
x_drag.drag(x_drag.get_start_value().toDouble() + x);
y_drag.drag(y_drag.get_start_value().toDouble() + y);
} else {
bool from_center = modifiers & Qt::AltModifier;
bool keep_ratio = modifiers & Qt::ShiftModifier;
NodeInputDragger &w_drag = gizmo->get_draggers()[2];
NodeInputDragger &h_drag = gizmo->get_draggers()[3];
QVector2D gizmo_sz_start(w_drag.get_start_value().toDouble(),
h_drag.get_start_value().toDouble());
QVector2D gizmo_pos_start(x_drag.get_start_value().toDouble(),
y_drag.get_start_value().toDouble());
QVector2D gizmo_half_res = gizmo->get_globals().square_resolution() / 2;
QVector2D adjusted_pt(x, y);
QVector2D new_size;
QVector2D new_pos;
QVector2D anchor;
static const int k_xy_count = 2;
bool negative[k_xy_count] = { false };
double original_ratio;
if (keep_ratio) {
original_ratio = w_drag.get_start_value().toDouble() /
h_drag.get_start_value().toDouble();
}
// Calculate new size
if (from_center) {
// Calculate new size by using distance from center and doubling it
new_size = (adjusted_pt - gizmo_half_res - gizmo_pos_start) * 2;
if (is_gizmo_top(gizmo)) {
new_size.setY(-new_size.y());
}
if (is_gizmo_left(gizmo)) {
new_size.setX(-new_size.x());
}
} else {
// Calculate new size by using distance from "anchor" - i.e. the opposite point of the shape
// from the gizmo being dragged
adjusted_pt -= gizmo_half_res;
anchor = generate_gizmo_anchor(gizmo_pos_start, gizmo_sz_start, gizmo,
&adjusted_pt) +
gizmo_half_res;
adjusted_pt += gizmo_half_res;
// Calculate size and position
new_size = adjusted_pt - anchor;
// Abs size so neither coord is negative
for (int i = 0; i < k_xy_count; i++) {
if (new_size[i] < 0) {
negative[i] = true;
new_size[i] = -new_size[i];
}
}
}
// Restrict sizes by constraints
if (is_gizmo_vertical_center(gizmo)) {
if (keep_ratio) {
// Calculate width from new height
new_size.setX(new_size.y() * original_ratio);
} else {
// Constrain to original width
new_size.setX(gizmo_sz_start.x());
}
}
if (is_gizmo_horizontal_center(gizmo)) {
if (keep_ratio) {
// Calculate height from new width
new_size.setY(new_size.x() / original_ratio);
} else {
// Constrain to original height
new_size.setY(gizmo_sz_start.y());
}
}
if (is_gizmo_corner(gizmo)) {
if (keep_ratio) {
float hypot = std::hypot(new_size.x(), new_size.y());
float original_angle =
std::atan2(gizmo_sz_start.x(), gizmo_sz_start.y());
// Calculate new size based on original angle and hypotenuse
new_size.setX(std::sin(original_angle) * hypot);
new_size.setY(std::cos(original_angle) * hypot);
}
}
// Calculate position
if (from_center) {
new_pos = gizmo_pos_start;
} else {
QVector2D using_size = new_size;
// Un-abs size
for (int i = 0; i < k_xy_count; i++) {
if (negative[i]) {
using_size[i] = -using_size[i];
}
}
// I'm pretty sure there's an algorithmic way of doing this, but I'm tired and this works
if (is_gizmo_horizontal_center(gizmo)) {
using_size.setY(0);
}
if (is_gizmo_vertical_center(gizmo)) {
using_size.setX(0);
}
new_pos =
generate_gizmo_anchor(gizmo_pos_start, gizmo_sz_start, gizmo) +
using_size / 2;
}
x_drag.drag(new_pos.x());
y_drag.drag(new_pos.y());
w_drag.drag(new_size.x());
h_drag.drag(new_size.y());
}
}
QVector2D ShapeNodeBase::generate_gizmo_anchor(const QVector2D &pos,
const QVector2D &size,
NodeGizmo *gizmo,
QVector2D *pt) const
{
QVector2D anchor = pos;
QVector2D half_sz = size / 2;
if (is_gizmo_left(gizmo)) {
anchor.setX(anchor.x() + half_sz.x());
if (pt && pt->x() > anchor.x()) {
pt->setX(anchor.x());
}
}
if (is_gizmo_right(gizmo)) {
anchor.setX(anchor.x() - half_sz.x());
if (pt && pt->x() < anchor.x()) {
pt->setX(anchor.x());
}
}
if (is_gizmo_top(gizmo)) {
anchor.setY(anchor.y() + half_sz.y());
if (pt && pt->y() > anchor.y()) {
pt->setY(anchor.y());
}
}
if (is_gizmo_bottom(gizmo)) {
anchor.setY(anchor.y() - half_sz.y());
if (pt && pt->y() < anchor.y()) {
pt->setY(anchor.y());
}
}
return anchor;
}
bool ShapeNodeBase::is_gizmo_top(NodeGizmo *g) const
{
return g == point_gizmo_[k_gizmo_scale_top_center] ||
g == point_gizmo_[k_gizmo_scale_top_left] ||
g == point_gizmo_[k_gizmo_scale_top_right];
}
bool ShapeNodeBase::is_gizmo_bottom(NodeGizmo *g) const
{
return g == point_gizmo_[k_gizmo_scale_bottom_center] ||
g == point_gizmo_[k_gizmo_scale_bottom_left] ||
g == point_gizmo_[k_gizmo_scale_bottom_right];
}
bool ShapeNodeBase::is_gizmo_left(NodeGizmo *g) const
{
return g == point_gizmo_[k_gizmo_scale_top_left] ||
g == point_gizmo_[k_gizmo_scale_center_left] ||
g == point_gizmo_[k_gizmo_scale_bottom_left];
}
bool ShapeNodeBase::is_gizmo_right(NodeGizmo *g) const
{
return g == point_gizmo_[k_gizmo_scale_top_right] ||
g == point_gizmo_[k_gizmo_scale_center_right] ||
g == point_gizmo_[k_gizmo_scale_bottom_right];
}
bool ShapeNodeBase::is_gizmo_horizontal_center(NodeGizmo *g) const
{
return g == point_gizmo_[k_gizmo_scale_center_left] ||
g == point_gizmo_[k_gizmo_scale_center_right];
}
bool ShapeNodeBase::is_gizmo_vertical_center(NodeGizmo *g) const
{
return g == point_gizmo_[k_gizmo_scale_top_center] ||
g == point_gizmo_[k_gizmo_scale_bottom_center];
}
bool ShapeNodeBase::is_gizmo_corner(NodeGizmo *g) const
{
return g == point_gizmo_[k_gizmo_scale_top_left] ||
g == point_gizmo_[k_gizmo_scale_top_right] ||
g == point_gizmo_[k_gizmo_scale_bottom_right] ||
g == point_gizmo_[k_gizmo_scale_bottom_left];
}
}
@@ -0,0 +1,82 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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/>.
***/
#ifndef OAK_SHAPENODEBASE_H
#define OAK_SHAPENODEBASE_H
#include "generatorwithmerge.h"
#include "node/gizmo/point.h"
#include "node/gizmo/polygon.h"
#include "node/inputdragger.h"
#include "node/node.h"
namespace olive
{
class ShapeNodeBase : public GeneratorWithMerge {
Q_OBJECT
public:
ShapeNodeBase(bool create_color_input = true);
virtual void retranslate() override;
virtual void update_gizmo_positions(const NodeValueRow &row,
const NodeGlobals &globals) override;
void set_rect(QRectF rect, const VideoParams &sequence_res,
MultiUndoCommand *command);
static const QString k_position_input;
static const QString k_size_input;
static const QString k_color_input;
protected:
PolygonGizmo *poly_gizmo() const
{
return poly_gizmo_;
}
protected slots:
virtual void gizmo_drag_move(double x, double y,
const Qt::KeyboardModifiers &modifiers) override;
private:
QVector2D generate_gizmo_anchor(const QVector2D &pos, const QVector2D &size,
NodeGizmo *gizmo,
QVector2D *pt = nullptr) const;
bool is_gizmo_top(NodeGizmo *g) const;
bool is_gizmo_bottom(NodeGizmo *g) const;
bool is_gizmo_left(NodeGizmo *g) const;
bool is_gizmo_right(NodeGizmo *g) const;
bool is_gizmo_horizontal_center(NodeGizmo *g) const;
bool is_gizmo_vertical_center(NodeGizmo *g) const;
bool is_gizmo_corner(NodeGizmo *g) const;
// Gizmo variables
static const int k_gizmo_whole_rect = k_gizmo_scale_count;
PointGizmo *point_gizmo_[k_gizmo_scale_count];
PolygonGizmo *poly_gizmo_;
};
}
#endif // OAK_SHAPENODEBASE_H
@@ -0,0 +1,22 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2022 Olive 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/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
node/generator/solid/solid.h
node/generator/solid/solid.cpp
PARENT_SCOPE
)
+80
View File
@@ -0,0 +1,80 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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 "solid.h"
namespace olive
{
const QString SolidGenerator::k_color_input = QStringLiteral("color_in");
#define super Node
SolidGenerator::SolidGenerator()
{
// Default to a color that isn't black
add_input(k_color_input, NodeValue::k_color,
QVariant::fromValue(Color(1.0f, 0.0f, 0.0f, 1.0f)));
}
QString SolidGenerator::name() const
{
return tr("Solid");
}
QString SolidGenerator::id() const
{
return QStringLiteral("org.olivevideoeditor.Olive.solidgenerator");
}
QVector<Node::CategoryID> SolidGenerator::category() const
{
return { k_category_generator };
}
QString SolidGenerator::description() const
{
return tr("Generate a solid color.");
}
void SolidGenerator::retranslate()
{
super::retranslate();
set_input_name(k_color_input, tr("Color"));
}
void SolidGenerator::value(const NodeValueRow &value,
const NodeGlobals &globals,
NodeValueTable *table) const
{
table->push(NodeValue::k_texture,
Texture::job(globals.vparams(), ShaderJob(value)), this);
}
ShaderCode SolidGenerator::get_shader_code(const ShaderRequest &request) const
{
Q_UNUSED(request)
return ShaderCode(FileFunctions::read_file_as_string(":/shaders/solid.frag"));
}
}
+54
View File
@@ -0,0 +1,54 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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/>.
***/
#ifndef OAK_SOLIDGENERATOR_H
#define OAK_SOLIDGENERATOR_H
#include "node/node.h"
namespace olive
{
class SolidGenerator : public Node {
Q_OBJECT
public:
SolidGenerator();
NODE_DEFAULT_FUNCTIONS(SolidGenerator)
virtual QString name() const override;
virtual QString id() const override;
virtual QVector<CategoryID> category() const override;
virtual QString description() const override;
virtual void retranslate() override;
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
NodeValueTable *table) const override;
virtual ShaderCode
get_shader_code(const ShaderRequest &request) const override;
static const QString k_color_input;
};
}
#endif // OAK_SOLIDGENERATOR_H
+26
View File
@@ -0,0 +1,26 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2022 Olive 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/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
node/generator/text/textv1.cpp
node/generator/text/textv1.h
node/generator/text/textv2.cpp
node/generator/text/textv2.h
node/generator/text/textv3.cpp
node/generator/text/textv3.h
PARENT_SCOPE
)
+185
View File
@@ -0,0 +1,185 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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 "textv1.h"
#include <QAbstractTextDocumentLayout>
#include <QTextDocument>
namespace olive
{
enum TextVerticalAlign {
k_vertical_align_top,
k_vertical_align_center,
k_vertical_align_bottom,
};
const QString TextGeneratorV1::k_text_input = QStringLiteral("text_in");
const QString TextGeneratorV1::k_html_input = QStringLiteral("html_in");
const QString TextGeneratorV1::k_color_input = QStringLiteral("color_in");
const QString TextGeneratorV1::k_v_align_input = QStringLiteral("valign_in");
const QString TextGeneratorV1::k_font_input = QStringLiteral("font_in");
const QString TextGeneratorV1::k_font_size_input = QStringLiteral("font_size_in");
#define super Node
TextGeneratorV1::TextGeneratorV1()
{
add_input(k_text_input, NodeValue::k_text, tr("Sample Text"));
add_input(k_html_input, NodeValue::k_boolean, false);
add_input(k_color_input, NodeValue::k_color,
QVariant::fromValue(Color(1.0f, 1.0f, 1.0)));
add_input(k_v_align_input, NodeValue::k_combo, 1);
add_input(k_font_input, NodeValue::k_font);
add_input(k_font_size_input, NodeValue::k_float, 72.0f);
set_flag(k_dont_show_in_create_menu);
}
QString TextGeneratorV1::name() const
{
return tr("Text (Legacy)");
}
QString TextGeneratorV1::id() const
{
return QStringLiteral("org.olivevideoeditor.Olive.textgenerator");
}
QVector<Node::CategoryID> TextGeneratorV1::category() const
{
return { k_category_generator };
}
QString TextGeneratorV1::description() const
{
return tr("Generate rich text.");
}
void TextGeneratorV1::retranslate()
{
super::retranslate();
set_input_name(k_text_input, tr("Text"));
set_input_name(k_html_input, tr("Enable HTML"));
set_input_name(k_font_input, tr("Font"));
set_input_name(k_font_size_input, tr("Font Size"));
set_input_name(k_color_input, tr("Color"));
set_input_name(k_v_align_input, tr("Vertical Align"));
set_combo_box_strings(k_v_align_input, { tr("Top"), tr("Center"), tr("Bottom") });
}
void TextGeneratorV1::value(const NodeValueRow &value,
const NodeGlobals &globals,
NodeValueTable *table) const
{
if (!value[k_text_input].to_string().isEmpty()) {
table->push(NodeValue::k_texture,
Texture::job(globals.vparams(), GenerateJob(value)), this);
}
}
void TextGeneratorV1::generate_frame(FramePtr frame,
const GenerateJob &job) const
{
// This could probably be more optimized, but for now we use Qt to draw to a QImage.
// QImages only support integer pixels and we use float pixels, so what we do here is draw onto
// a single-channel QImage (alpha only) and then transplant that alpha channel to our float buffer
// with correct float RGB.
QImage img(frame->width(), frame->height(), QImage::Format_Grayscale8);
img.fill(0);
QTextDocument text_doc;
// Set default font
QFont default_font;
default_font.setFamily(job.get(k_font_input).to_string());
default_font.setPointSizeF(job.get(k_font_size_input).to_double());
text_doc.setDefaultFont(default_font);
// Center by default
text_doc.setDefaultTextOption(QTextOption(Qt::AlignCenter));
QString html = job.get(k_text_input).to_string();
if (job.get(k_html_input).to_bool()) {
html.replace('\n', QStringLiteral("<br>"));
text_doc.setHtml(html);
} else {
text_doc.setPlainText(html);
}
// Align to 80% width because that's considered the "title safe" area
int tenth_of_width = frame->video_params().width() / 10;
text_doc.setTextWidth(tenth_of_width * 8);
// Draw rich text onto image
QPainter p(&img);
p.scale(1.0 / frame->video_params().divider(),
1.0 / frame->video_params().divider());
// Push 10% inwards to compensate for title safe area
p.translate(tenth_of_width, 0);
TextVerticalAlign valign =
static_cast<TextVerticalAlign>(job.get(k_v_align_input).to_int());
int doc_height = text_doc.size().height();
switch (valign) {
case k_vertical_align_top:
// Push 10% inwards for title safe area
p.translate(0, frame->video_params().height() / 10);
break;
case k_vertical_align_center:
// Center align
p.translate(0, frame->video_params().height() / 2 - doc_height / 2);
break;
case k_vertical_align_bottom:
// Push 10% inwards for title safe area
p.translate(0, frame->video_params().height() - doc_height -
frame->video_params().height() / 10);
break;
}
QAbstractTextDocumentLayout::PaintContext ctx;
ctx.palette.setColor(QPalette::Text, Qt::white);
text_doc.documentLayout()->draw(&p, ctx);
// Transplant alpha channel to frame
Color rgb = job.get(k_color_input).to_color();
for (int x = 0; x < frame->width(); x++) {
for (int y = 0; y < frame->height(); y++) {
uchar src_alpha = img.bits()[img.bytesPerLine() * y + x];
float alpha = float(src_alpha) / 255.0f;
frame->set_pixel(x, y,
Color(rgb.red() * alpha, rgb.green() * alpha,
rgb.blue() * alpha, alpha));
}
}
}
}
+60
View File
@@ -0,0 +1,60 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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/>.
***/
#ifndef OAK_TEXTGENERATORV1_H
#define OAK_TEXTGENERATORV1_H
#include "node/node.h"
namespace olive
{
class TextGeneratorV1 : public Node {
Q_OBJECT
public:
TextGeneratorV1();
NODE_DEFAULT_FUNCTIONS(TextGeneratorV1)
virtual QString name() const override;
virtual QString id() const override;
virtual QVector<CategoryID> category() const override;
virtual QString description() const override;
virtual void retranslate() override;
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
NodeValueTable *table) const override;
virtual void generate_frame(FramePtr frame,
const GenerateJob &job) const override;
static const QString k_text_input;
static const QString k_html_input;
static const QString k_color_input;
static const QString k_v_align_input;
static const QString k_font_input;
static const QString k_font_size_input;
};
}
#endif // OAK_TEXTGENERATORV1_H
+206
View File
@@ -0,0 +1,206 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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 "textv2.h"
#include <olive/core/core.h>
#include <QAbstractTextDocumentLayout>
#include <QDateTime>
#include <QTextDocument>
namespace olive
{
#define super ShapeNodeBase
enum TextVerticalAlign {
k_vertical_align_top,
k_vertical_align_center,
k_vertical_align_bottom,
};
const QString TextGeneratorV2::k_text_input = QStringLiteral("text_in");
const QString TextGeneratorV2::k_html_input = QStringLiteral("html_in");
const QString TextGeneratorV2::k_v_align_input = QStringLiteral("valign_in");
const QString TextGeneratorV2::k_font_input = QStringLiteral("font_in");
const QString TextGeneratorV2::k_font_size_input = QStringLiteral("font_size_in");
TextGeneratorV2::TextGeneratorV2()
{
add_input(k_text_input, NodeValue::k_text, tr("Sample Text"));
add_input(k_html_input, NodeValue::k_boolean, false);
add_input(k_v_align_input, NodeValue::k_combo, k_vertical_align_top);
add_input(k_font_input, NodeValue::k_font);
add_input(k_font_size_input, NodeValue::k_float, 72.0f);
set_standard_value(k_color_input, QVariant::fromValue(Color(1.0f, 1.0f, 1.0)));
set_standard_value(k_size_input, QVector2D(400, 300));
set_flag(k_dont_show_in_create_menu);
}
QString TextGeneratorV2::name() const
{
return tr("Text (Legacy)");
}
QString TextGeneratorV2::id() const
{
return QStringLiteral("org.olivevideoeditor.Olive.text2");
}
QVector<Node::CategoryID> TextGeneratorV2::category() const
{
return { k_category_generator };
}
QString TextGeneratorV2::description() const
{
return tr("Generate rich text.");
}
void TextGeneratorV2::retranslate()
{
super::retranslate();
set_input_name(k_text_input, tr("Text"));
set_input_name(k_html_input, tr("Enable HTML"));
set_input_name(k_font_input, tr("Font"));
set_input_name(k_font_size_input, tr("Font Size"));
set_input_name(k_v_align_input, tr("Vertical Align"));
set_combo_box_strings(k_v_align_input, { tr("Top"), tr("Center"), tr("Bottom") });
}
void TextGeneratorV2::value(const NodeValueRow &value,
const NodeGlobals &globals,
NodeValueTable *table) const
{
if (!value[k_text_input].to_string().isEmpty()) {
GenerateJob job(value);
auto text_params = globals.vparams();
text_params.set_format(PixelFormat::f32);
table->push(NodeValue::k_texture, Texture::job(text_params, job), this);
}
}
void TextGeneratorV2::generate_frame(FramePtr frame,
const GenerateJob &job) const
{
// This could probably be more optimized, but for now we use Qt to draw to a QImage.
// QImages only support integer pixels and we use float pixels, so what we do here is draw onto
// a single-channel QImage (alpha only) and then transplant that alpha channel to our float buffer
// with correct float RGB.
QImage img(frame->width(), frame->height(), QImage::Format_Grayscale8);
img.fill(Qt::transparent);
// 72 DPI in DPM (72 / 2.54 * 100)
const int dpm = 2835;
img.setDotsPerMeterX(dpm);
img.setDotsPerMeterY(dpm);
QTextDocument text_doc;
text_doc.documentLayout()->setPaintDevice(&img);
// Set default font
QFont default_font;
default_font.setFamily(job.get(k_font_input).to_string());
default_font.setPointSizeF(job.get(k_font_size_input).to_double());
text_doc.setDefaultFont(default_font);
QString html = job.get(k_text_input).to_string();
if (job.get(k_html_input).to_bool()) {
html.replace('\n', QStringLiteral("<br>"));
text_doc.setHtml(html);
} else {
text_doc.setPlainText(html);
}
QVector2D size = job.get(k_size_input).to_vec2();
text_doc.setTextWidth(size.x());
// Draw rich text onto image
QPainter p(&img);
p.scale(1.0 / frame->video_params().divider(),
1.0 / frame->video_params().divider());
QVector2D pos = job.get(k_position_input).to_vec2();
p.translate(pos.x() - size.x() / 2, pos.y() - size.y() / 2);
p.translate(frame->video_params().width() / 2,
frame->video_params().height() / 2);
p.setClipRect(0, 0, size.x(), size.y());
TextVerticalAlign valign =
static_cast<TextVerticalAlign>(job.get(k_v_align_input).to_int());
int doc_height = text_doc.size().height();
switch (valign) {
case k_vertical_align_top:
// Do nothing
break;
case k_vertical_align_center:
// Center align
p.translate(0, size.y() / 2 - doc_height / 2);
break;
case k_vertical_align_bottom:
p.translate(0, size.y() - doc_height);
break;
}
QAbstractTextDocumentLayout::PaintContext ctx;
ctx.palette.setColor(QPalette::Text, Qt::white);
text_doc.documentLayout()->draw(&p, ctx);
// Transplant alpha channel to frame
Color rgba = job.get(k_color_input).to_color();
#if defined(Q_PROCESSOR_X86) || defined(Q_PROCESSOR_ARM)
__m128 sse_color = _mm_loadu_ps(rgba.data());
#endif
float *frame_dst = reinterpret_cast<float *>(frame->data());
for (int y = 0; y < frame->height(); y++) {
uchar *src_y = img.bits() + img.bytesPerLine() * y;
float *dst_y = frame_dst + y * frame->linesize_pixels() *
VideoParams::k_rgba_channel_count;
for (int x = 0; x < frame->width(); x++) {
float alpha = float(src_y[x]) / 255.0f;
float *dst = dst_y + x * VideoParams::k_rgba_channel_count;
#if defined(Q_PROCESSOR_X86) || defined(Q_PROCESSOR_ARM)
__m128 sse_alpha = _mm_load1_ps(&alpha);
__m128 sse_res = _mm_mul_ps(sse_color, sse_alpha);
_mm_store_ps(dst, sse_res);
#else
for (int i = 0; i < VideoParams::kRGBAChannelCount; i++) {
dst[i] = rgba.data()[i] * alpha;
}
#endif
}
}
}
}
+59
View File
@@ -0,0 +1,59 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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/>.
***/
#ifndef OAK_TEXTGENERATORV2_H
#define OAK_TEXTGENERATORV2_H
#include "node/generator/shape/shapenodebase.h"
namespace olive
{
class TextGeneratorV2 : public ShapeNodeBase {
Q_OBJECT
public:
TextGeneratorV2();
NODE_DEFAULT_FUNCTIONS(TextGeneratorV2)
virtual QString name() const override;
virtual QString id() const override;
virtual QVector<CategoryID> category() const override;
virtual QString description() const override;
virtual void retranslate() override;
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
NodeValueTable *table) const override;
virtual void generate_frame(FramePtr frame,
const GenerateJob &job) const override;
static const QString k_text_input;
static const QString k_html_input;
static const QString k_v_align_input;
static const QString k_font_input;
static const QString k_font_size_input;
};
}
#endif // OAK_TEXTGENERATORV2_H
+310
View File
@@ -0,0 +1,310 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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 "textv3.h"
#include <QAbstractTextDocumentLayout>
#include <QDateTime>
#include <QTextDocument>
#include "common/html.h"
#include "coreengine.h"
#include "node/project.h"
#include "node/nodeundo.h"
namespace olive
{
#define super ShapeNodeBase
enum TextVerticalAlign {
k_vertical_align_top,
k_vertical_align_center,
k_vertical_align_bottom,
};
const QString TextGeneratorV3::k_text_input = QStringLiteral("text_in");
const QString TextGeneratorV3::k_vertical_alignment_input =
QStringLiteral("valign_in");
const QString TextGeneratorV3::k_use_args_input = QStringLiteral("use_args_in");
const QString TextGeneratorV3::k_args_input = QStringLiteral("args_in");
TextGeneratorV3::TextGeneratorV3()
: ShapeNodeBase(false)
, dont_emit_valign_(false)
{
add_input(k_text_input, NodeValue::k_text,
QStringLiteral("<p style='font-size: 72pt; color: white;'>%1</p>")
.arg(tr("Sample Text")));
set_input_property(k_text_input, QStringLiteral("vieweronly"), true);
set_standard_value(k_size_input, QVector2D(400, 300));
add_input(k_vertical_alignment_input, NodeValue::k_combo,
InputFlags(k_input_flag_hidden | k_input_flag_static));
add_input(k_use_args_input, NodeValue::k_boolean, true,
InputFlags(k_input_flag_hidden | k_input_flag_static));
add_input(k_args_input, NodeValue::k_text, InputFlags(k_input_flag_array));
set_input_property(k_args_input, QStringLiteral("arraystart"), 1);
text_gizmo_ = new TextGizmo(this);
text_gizmo_->set_input(NodeInput(this, k_text_input));
connect(text_gizmo_, &TextGizmo::activated, this,
&TextGeneratorV3::gizmo_activated);
connect(text_gizmo_, &TextGizmo::deactivated, this,
&TextGeneratorV3::gizmo_deactivated);
}
QString TextGeneratorV3::name() const
{
return tr("Text");
}
QString TextGeneratorV3::id() const
{
return QStringLiteral("org.olivevideoeditor.Olive.text3");
}
QVector<Node::CategoryID> TextGeneratorV3::category() const
{
return { k_category_generator };
}
QString TextGeneratorV3::description() const
{
return tr("Generate rich text.");
}
void TextGeneratorV3::retranslate()
{
super::retranslate();
set_input_name(k_text_input, tr("Text"));
set_input_name(k_vertical_alignment_input, tr("Vertical Alignment"));
set_combo_box_strings(k_vertical_alignment_input,
{ tr("Top"), tr("Middle"), tr("Bottom") });
set_input_name(k_args_input, tr("Arguments"));
}
void TextGeneratorV3::value(const NodeValueRow &value,
const NodeGlobals &globals,
NodeValueTable *table) const
{
QString text = value[k_text_input].to_string();
if (value[k_use_args_input].to_bool()) {
auto args = value[k_args_input].to_array();
if (!args.empty()) {
QStringList list;
list.reserve(args.size());
for (size_t i = 0; i < args.size(); i++) {
list.append(args[i].to_string());
}
text = format_string(text, list);
}
}
if (!text.isEmpty()) {
TexturePtr base = value[k_text_input].to_texture();
VideoParams text_params = base ? base->params() : globals.vparams();
text_params.set_format(PixelFormat::u8);
text_params.set_colorspace(
project()->color_manager()->get_default_input_color_space());
GenerateJob job(value);
job.insert(k_text_input, NodeValue(NodeValue::k_text, text));
push_mergable_job(value, Texture::job(text_params, job), table);
} else if (value[k_base_input].to_texture()) {
table->push(value[k_base_input]);
}
}
void TextGeneratorV3::generate_frame(FramePtr frame,
const GenerateJob &job) const
{
QImage img(reinterpret_cast<uchar *>(frame->data()), frame->width(),
frame->height(), frame->linesize_bytes(),
QImage::Format_RGBA8888_Premultiplied);
img.fill(Qt::transparent);
// 96 DPI in DPM (96 / 2.54 * 100)
const int dpm = 3780;
img.setDotsPerMeterX(dpm);
img.setDotsPerMeterY(dpm);
QTextDocument text_doc;
text_doc.documentLayout()->setPaintDevice(&img);
QString html = job.get(k_text_input).to_string();
Html::html_to_doc(&text_doc, html);
QVector2D size = job.get(k_size_input).to_vec2();
text_doc.setTextWidth(size.x());
// Draw rich text onto image
QPainter p(&img);
p.scale(1.0 / frame->video_params().divider(),
1.0 / frame->video_params().divider());
QVector2D pos = job.get(k_position_input).to_vec2();
p.translate(pos.x() - size.x() / 2, pos.y() - size.y() / 2);
p.translate(frame->video_params().width() / 2,
frame->video_params().height() / 2);
p.setClipRect(0, 0, size.x(), size.y());
switch (static_cast<VerticalAlignment>(
job.get(k_vertical_alignment_input).to_int())) {
case k_v_align_top:
// Do nothing
break;
case k_v_align_middle:
p.translate(0, size.y() / 2 - text_doc.size().height() / 2);
break;
case k_v_align_bottom:
p.translate(0, size.y() - text_doc.size().height());
break;
}
// Ensure default text color is white
QAbstractTextDocumentLayout::PaintContext ctx;
ctx.palette.setColor(QPalette::Text, Qt::white);
text_doc.documentLayout()->draw(&p, ctx);
}
void TextGeneratorV3::update_gizmo_positions(const NodeValueRow &row,
const NodeGlobals &globals)
{
super::update_gizmo_positions(row, globals);
QRectF rect = poly_gizmo()->get_polygon().boundingRect();
text_gizmo_->set_rect(rect);
text_gizmo_->set_html(row[k_text_input].to_string());
}
Qt::Alignment TextGeneratorV3::get_qt_alignment_from_ours(VerticalAlignment v)
{
switch (v) {
case k_v_align_top:
return Qt::AlignTop;
case k_v_align_middle:
return Qt::AlignVCenter;
case k_v_align_bottom:
return Qt::AlignBottom;
}
return Qt::Alignment();
}
TextGeneratorV3::VerticalAlignment
TextGeneratorV3::get_our_alignment_from_qts(Qt::Alignment v)
{
switch (v) {
case Qt::AlignTop:
return k_v_align_top;
case Qt::AlignVCenter:
return k_v_align_middle;
case Qt::AlignBottom:
return k_v_align_bottom;
}
return k_v_align_top;
}
QString TextGeneratorV3::format_string(const QString &input,
const QStringList &args)
{
QString output;
output.reserve(input.size());
for (int i = 0; i < input.size(); i++) {
const QChar &this_char = input.at(i);
if (i < input.size() - 1 && this_char == '%') {
const QChar &next_char = input.at(i + 1);
if (next_char == '%') {
// Double percent, append a single percent
output.append('%');
i++;
} else if (next_char.isDigit()) {
// Find length of number
QString num;
i++;
while (i < input.size() && input.at(i).isDigit()) {
num.append(input.at(i));
i++;
}
i--;
int index = num.toInt() - 1;
if (index >= 0 && index < args.size()) {
output.append(args.at(index));
}
} else {
output.append(this_char);
}
} else {
output.append(this_char);
}
}
return output;
}
void TextGeneratorV3::InputValueChangedEvent(const QString &input, int element)
{
if (input == k_vertical_alignment_input && !dont_emit_valign_) {
text_gizmo_->set_vertical_alignment(
get_qt_alignment_from_ours(get_vertical_alignment()));
}
super::InputValueChangedEvent(input, element);
}
void TextGeneratorV3::gizmo_activated()
{
set_standard_value(k_use_args_input, false);
connect(text_gizmo_, &TextGizmo::vertical_alignment_changed, this,
&TextGeneratorV3::set_vertical_alignment_undoable);
dont_emit_valign_ = true;
}
void TextGeneratorV3::gizmo_deactivated()
{
set_standard_value(k_use_args_input, true);
disconnect(text_gizmo_, &TextGizmo::vertical_alignment_changed, this,
&TextGeneratorV3::set_vertical_alignment_undoable);
dont_emit_valign_ = true;
}
void TextGeneratorV3::set_vertical_alignment_undoable(Qt::Alignment a)
{
EngineCore::instance()->undo_stack()->push(
new NodeParamSetStandardValueCommand(NodeInput(this,
k_vertical_alignment_input),
get_our_alignment_from_qts(a)),
tr("Set Text Vertical Alignment"));
}
}
+89
View File
@@ -0,0 +1,89 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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/>.
***/
#ifndef OAK_TEXTGENERATORV3_H
#define OAK_TEXTGENERATORV3_H
#include "node/generator/shape/shapenodebase.h"
#include "node/gizmo/text.h"
namespace olive
{
class TextGeneratorV3 : public ShapeNodeBase {
Q_OBJECT
public:
TextGeneratorV3();
NODE_DEFAULT_FUNCTIONS(TextGeneratorV3)
virtual QString name() const override;
virtual QString id() const override;
virtual QVector<CategoryID> category() const override;
virtual QString description() const override;
virtual void retranslate() override;
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
NodeValueTable *table) const override;
virtual void generate_frame(FramePtr frame,
const GenerateJob &job) const override;
virtual void update_gizmo_positions(const NodeValueRow &row,
const NodeGlobals &globals) override;
enum VerticalAlignment { k_v_align_top, k_v_align_middle, k_v_align_bottom };
VerticalAlignment get_vertical_alignment() const
{
return static_cast<VerticalAlignment>(
get_standard_value(k_vertical_alignment_input).toInt());
}
static Qt::Alignment get_qt_alignment_from_ours(VerticalAlignment v);
static VerticalAlignment get_our_alignment_from_qts(Qt::Alignment v);
static const QString k_text_input;
static const QString k_vertical_alignment_input;
static const QString k_use_args_input;
static const QString k_args_input;
static QString format_string(const QString &input, const QStringList &args);
protected:
virtual void InputValueChangedEvent(const QString &input,
int element) override;
private:
TextGizmo *text_gizmo_;
bool dont_emit_valign_;
private slots:
void gizmo_activated();
void gizmo_deactivated();
void set_vertical_alignment_undoable(Qt::Alignment a);
};
}
#endif // OAK_TEXTGENERATORV3_H