Implement multi-input OFX clip wiring and texture handling
Store PluginJob input values for lookup Add per-clip texture inputs on plugin nodes Map input clips to textures during render (with Source fallback)
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
CompileFlags:
|
||||
Add: []
|
||||
Remove: [-mlongcalls, -fstrict-volatile-bitfields, -fno-shrink-wrap, -fno-tree-switch-conversion, -mno-direct-extern-access]
|
||||
|
||||
Diagnostics:
|
||||
Suppress: ['drv_unknown_argument', 'unused-includes', 'pp_file_not_found']
|
||||
@@ -14,3 +14,14 @@ Now, we are maintaining a community edition for this project.
|
||||
The original author compiled following binaries:
|
||||
- [0.1.0 alpha](https://github.com/olive-editor/olive/releases/tag/0.1.0)
|
||||
- [0.2.0 unstable development build](https://github.com/olive-editor/olive/releases/tag/0.2.0-nightly)
|
||||
|
||||
## OpenFX Support TODO
|
||||
- Implement plugin discovery/loading from a given path and populate the cache (currently creates host/cache only). `app/pluginSupport/OliveHost.cpp`
|
||||
- Wire output clip image storage: allocate a backing buffer, set `kOfxImagePropData`, and update bounds/rowBytes before render. `app/pluginSupport/OliveClip.cpp`
|
||||
- Provide real input clip image fetches (currently returns an empty `Image` for inputs). `app/pluginSupport/OliveClip.cpp`
|
||||
- Ensure render path sets per-frame output data and handles ROD/bounds correctly. `app/render/plugin/pluginrenderer.cpp`
|
||||
- Add missing param instance types (String, Double3D/Integer3D, Group/Page, Custom/Bytes) and mapping to node inputs. `app/pluginSupport/OlivePluginInstance.cpp`, `app/node/plugins/Plugin.cpp`
|
||||
- Implement `editBegin`/`editEnd`, progress, and timeline hooks instead of stubs. `app/pluginSupport/OlivePluginInstance.cpp`, `app/pluginSupport/OlivePluginInstance.h`
|
||||
- Integrate persistent message handling with the app UI (currently TODO placeholders). `app/pluginSupport/OlivePluginInstance.cpp`
|
||||
- Decide and enforce project extent/fielding behavior instead of the current placeholder comment. `app/pluginSupport/OlivePluginInstance.cpp`
|
||||
- Add OpenGL texture render suite support or explicitly disable it (currently `loadTexture` returns null). `app/pluginSupport/OliveClip.h`
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
# OpenFX 支持 TODO(中文说明)
|
||||
|
||||
下面是 README 里 OpenFX TODO 的中文翻译与详细说明。每条都尽量解释“是什么、为什么需要、该往哪里改”。
|
||||
|
||||
1) 实现插件发现与加载流程
|
||||
- 现状:`app/pluginSupport/OliveHost.cpp` 里只创建了 Host 和 PluginCache,但没有扫描路径/加载 OFX 插件。
|
||||
- 为什么需要:没有完整的发现/加载,就无法让用户看到插件或实例化插件。
|
||||
- 可能改动:补全 `loadPlugins()`,遍历指定目录(如 OFX 标准路径),调用 OpenFX 的插件缓存加载逻辑,注册可用插件并让 UI/节点系统可用。
|
||||
|
||||
2) 输出剪辑图像的缓冲区管理(Output Clip)
|
||||
- 现状:`OliveClipInstance::getImage()` 返回空的 OFX Image,没有分配像素内存,也没有设置 `kOfxImagePropData`。
|
||||
- 为什么需要:插件渲染时会往 `kOfxImagePropData` 写入像素,如果这里没分配就会导致崩溃或黑屏。
|
||||
- 可能改动:为 Output Clip 分配像素缓冲(例如 `std::vector<uint8_t>`),填充 `kOfxImagePropData`、`kOfxImagePropRowBytes`、`kOfxImagePropBounds`,并保证生命周期覆盖渲染过程。
|
||||
|
||||
3) 输入剪辑图像的拉取(Input Clip Fetch)
|
||||
- 现状:`OliveClipInstance::getImage()` 对输入剪辑直接返回一个空 Image,没有真正把输入帧填进去。
|
||||
- 为什么需要:多数插件需要输入图像做处理,没有输入就无法正确工作。
|
||||
- 可能改动:根据当前时间 `time` 从渲染管线/缓存/纹理中取出输入帧,设置 OFX Image 的 data/bounds/rowBytes 等属性并返回。
|
||||
|
||||
4) 渲染路径中设置每帧输出数据与 ROD/Bounds
|
||||
- 现状:`app/render/plugin/pluginrenderer.cpp` 中已接了 Image->AVFrame,但仍需要保证每帧输出图像属性正确(ROD/Bounds)。
|
||||
- 为什么需要:插件对 ROD 和 bounds 非常敏感,用错会导致裁剪错误或错位。
|
||||
- 可能改动:在渲染前或 render action 前,按当前时间/ROI 计算并设置 Output Clip 的 `kOfxImagePropBounds`、`kOfxImagePropRegionOfDefinition` 等。
|
||||
|
||||
5) 参数类型支持不完整(Param Instances)
|
||||
- 现状:`OlivePluginInstance::newParam()` 只支持少量类型,`app/node/plugins/Plugin.cpp` 里也没有处理 Group/Page 等。
|
||||
- 为什么需要:复杂插件大量依赖 String/3D/Custom 等参数,不支持就会缺参数或崩溃。
|
||||
- 可能改动:补齐 String、Double3D/Integer3D、Group/Page、Custom/Bytes 等参数实例,并在节点输入映射里增加对应类型。
|
||||
|
||||
6) editBegin/editEnd、Progress、Timeline 等回调还只是空实现
|
||||
- 现状:`OlivePluginInstance` 中多处函数是空壳或默认返回。
|
||||
- 为什么需要:插件在编辑参数、显示进度、根据时间线上下文渲染时依赖这些回调。
|
||||
- 可能改动:实现 editBegin/editEnd 通知;progressStart/Update/End 与 UI 进度条连接;timelineGetTime/GotoTime/Bounds 与工程时间轴连接。
|
||||
|
||||
8) Project Extent / Fielding 行为待确认
|
||||
- 现状:`OlivePluginInstance::getProjectExtent()` 有 “TODO” 注释。
|
||||
- 为什么需要:OFX 插件会根据项目尺寸、扫描线场信息做渲染决策。
|
||||
- 可能改动:明确工程是否支持不同 extent/fielding,确保返回值与项目设置一致。
|
||||
|
||||
9) OpenGL Render Suite 支持
|
||||
- 现状:`OliveClipInstance::loadTexture()` 返回 null,OpenGL 渲染路径未实现。
|
||||
- 为什么需要:有些 OFX 插件只支持 OpenGL 渲染,不支持 CPU 渲染。
|
||||
- 可能改动:要么实现 OpenGL texture 的加载与生命周期,要么在 host capability 中明确禁用 OpenGL render。
|
||||
|
||||
---
|
||||
|
||||
如果你希望,我可以把这些 TODO 拆成“优先级 + 预计工作量 + 依赖关系”的形式,方便你逐条推进。
|
||||
@@ -1303,6 +1303,8 @@ signals:
|
||||
|
||||
void KeyframeTimeChanged(NodeKeyframe *key);
|
||||
|
||||
void MessageCountChanged();
|
||||
|
||||
void KeyframeTypeChanged(NodeKeyframe *key);
|
||||
|
||||
void KeyframeValueChanged(NodeKeyframe *key);
|
||||
|
||||
@@ -24,6 +24,7 @@ olive::plugin::PluginNode::PluginNode(
|
||||
OFX::Host::ImageEffect::Instance *plugin)
|
||||
{
|
||||
plugin_instance_=plugin;
|
||||
bool has_texture_input = false;
|
||||
|
||||
auto params=plugin_instance_->getParams();
|
||||
for (auto param: params) {
|
||||
@@ -70,6 +71,17 @@ olive::plugin::PluginNode::PluginNode(
|
||||
AddInput(param.second->getName().data(), type);
|
||||
}
|
||||
|
||||
const auto &clips = plugin_instance_->getDescriptor().getClips();
|
||||
for (const auto &entry : clips) {
|
||||
if (entry.first == kOfxImageEffectOutputClipName) {
|
||||
continue;
|
||||
}
|
||||
AddInput(entry.first.data(), NodeValue::kTexture);
|
||||
has_texture_input = true;
|
||||
}
|
||||
if (!has_texture_input) {
|
||||
AddInput(kTextureInput, NodeValue::kTexture);
|
||||
}
|
||||
}
|
||||
QString olive::plugin::PluginNode::Name() const
|
||||
{
|
||||
@@ -94,9 +106,19 @@ void olive::plugin::PluginNode::Value(const NodeValueRow &value,
|
||||
const NodeGlobals &globals,
|
||||
NodeValueTable *table) const
|
||||
{
|
||||
TexturePtr tex = value[kTextureInput].toTexture();
|
||||
TexturePtr tex = value.value(kTextureInput).toTexture();
|
||||
if (!tex) {
|
||||
for (auto it = value.cbegin(); it != value.cend(); ++it) {
|
||||
if (it.value().type() == NodeValue::kTexture) {
|
||||
tex = it.value().toTexture();
|
||||
if (tex) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tex && plugin_instance_) {
|
||||
PluginJob job(plugin_instance_, value);
|
||||
PluginJob job(plugin_instance_, this, value);
|
||||
table->Push(NodeValue::kTexture, tex->toJob(job), this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#define PLUGIN_NODES_H
|
||||
#include "ofxhImageEffectAPI.h"
|
||||
#include "ofxhPluginCache.h"
|
||||
#include "ofxImageEffect.h"
|
||||
#include "node/node.h"
|
||||
|
||||
namespace olive
|
||||
|
||||
@@ -382,7 +382,7 @@ TexturePtr NodeTraverser::ProcessVideoCacheJob(const CacheJob *val)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TexturePtr NodeTraverser::ProcessPluginJob(plugin::PluginJob *job)
|
||||
TexturePtr NodeTraverser::ProcessPluginJob(TexturePtr texture, TexturePtr destination, const Node *node,)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
@@ -496,7 +496,13 @@ void NodeTraverser::ResolveJobs(NodeValue &val)
|
||||
val.set_value(tex);
|
||||
}
|
||||
else if (plugin::PluginJob* plugin_job=dynamic_cast<plugin::PluginJob*>(base_job)) {
|
||||
ProcessPluginJob(plugin_job);
|
||||
VideoParams tex_params = job_tex->params();
|
||||
|
||||
TexturePtr tex = CreateTexture(tex_params);
|
||||
|
||||
ProcessPluginJob(job_tex, tex, val.source());
|
||||
val.set_value(tex);
|
||||
|
||||
}
|
||||
|
||||
// Cache resolved value
|
||||
|
||||
@@ -144,7 +144,7 @@ protected:
|
||||
return SampleBuffer();
|
||||
}
|
||||
|
||||
virtual TexturePtr ProcessPluginJob(plugin::PluginJob *job);
|
||||
virtual TexturePtr ProcessPluginJob(TexturePtr texture, TexturePtr destination, const Node *node);
|
||||
SampleBuffer CreateSampleBuffer(const AudioParams ¶ms,
|
||||
const rational &length)
|
||||
{
|
||||
|
||||
@@ -7,4 +7,6 @@ target_sources(libolive-editor PRIVATE
|
||||
OliveClip.h
|
||||
paraminstance.cpp
|
||||
paraminstance.h
|
||||
image.cpp
|
||||
image.h
|
||||
)
|
||||
+150
-15
@@ -24,6 +24,18 @@
|
||||
#include "OliveClip.h"
|
||||
|
||||
#include "common/Current.h"
|
||||
#include "common/ffmpegutils.h"
|
||||
#include "ofxCore.h"
|
||||
#include "ofxhClip.h"
|
||||
#include "pluginSupport/image.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
extern "C" {
|
||||
#include <libswscale/swscale.h>
|
||||
}
|
||||
const std::string &olive::plugin::OliveClipInstance::getUnmappedBitDepth() const
|
||||
{
|
||||
switch (params_.format()) {
|
||||
@@ -65,9 +77,7 @@ const std::string &olive::plugin::OliveClipInstance::getPremult() const
|
||||
}
|
||||
double olive::plugin::OliveClipInstance::getAspectRatio() const
|
||||
{
|
||||
return params_
|
||||
.pixel_aspect_ratio()
|
||||
.toDouble();
|
||||
return params_.pixel_aspect_ratio().toDouble();
|
||||
}
|
||||
double olive::plugin::OliveClipInstance::getFrameRate() const
|
||||
{
|
||||
@@ -76,13 +86,9 @@ double olive::plugin::OliveClipInstance::getFrameRate() const
|
||||
void olive::plugin::OliveClipInstance::getFrameRange(double &startFrame,
|
||||
double &endFrame) const
|
||||
{
|
||||
startFrame =
|
||||
params_.frame_rate().toDouble() *
|
||||
params_.start_time();
|
||||
startFrame = params_.frame_rate().toDouble() * params_.start_time();
|
||||
endFrame =
|
||||
startFrame +
|
||||
params_.frame_rate().toDouble() *
|
||||
params_.duration();
|
||||
startFrame + params_.frame_rate().toDouble() * params_.duration();
|
||||
}
|
||||
const std::string &olive::plugin::OliveClipInstance::getFieldOrder() const
|
||||
{
|
||||
@@ -98,8 +104,7 @@ const std::string &olive::plugin::OliveClipInstance::getFieldOrder() const
|
||||
}
|
||||
bool olive::plugin::OliveClipInstance::getConnected() const
|
||||
{
|
||||
return params_.format() ==
|
||||
PixelFormat::INVALID;
|
||||
return params_.format() == PixelFormat::INVALID;
|
||||
}
|
||||
double olive::plugin::OliveClipInstance::getUnmappedFrameRate() const
|
||||
{
|
||||
@@ -118,7 +123,54 @@ OFX::Host::ImageEffect::Image *
|
||||
olive::plugin::OliveClipInstance::getImage(OfxTime time,
|
||||
const OfxRectD *optionalBounds)
|
||||
{
|
||||
return &image_;
|
||||
OfxRectI bounds = {0, 0, params_.width(), params_.height()};
|
||||
if (optionalBounds) {
|
||||
bounds.x1 = static_cast<int>(std::floor(optionalBounds->x1));
|
||||
bounds.y1 = static_cast<int>(std::floor(optionalBounds->y1));
|
||||
bounds.x2 = static_cast<int>(std::ceil(optionalBounds->x2));
|
||||
bounds.y2 = static_cast<int>(std::ceil(optionalBounds->y2));
|
||||
}
|
||||
|
||||
OfxRectD rod_d = getRegionOfDefinition(time);
|
||||
OfxRectI rod = { static_cast<int>(std::floor(rod_d.x1)),
|
||||
static_cast<int>(std::floor(rod_d.y1)),
|
||||
static_cast<int>(std::ceil(rod_d.x2)),
|
||||
static_cast<int>(std::ceil(rod_d.y2)) };
|
||||
|
||||
if (name_ == "Output") {
|
||||
if (!images_.contains(time)) {
|
||||
// make a new ref counted image
|
||||
images_.insert(time, std::make_shared<Image>(*const_cast<OliveClipInstance *>(this),
|
||||
params_, bounds, rod, true));
|
||||
}
|
||||
|
||||
// add another reference to the member image for this fetch
|
||||
// as we have a ref count of 1 due to construction, this will
|
||||
// cause the output image never to delete by the plugin
|
||||
// when it releases the image
|
||||
images_[time]->addReference();
|
||||
|
||||
images_[time]->EnsureAllocatedFromParams(params_, bounds, rod, true);
|
||||
|
||||
// return it
|
||||
return images_[time].get();
|
||||
} else {
|
||||
if (images_.contains(time)) {
|
||||
std::shared_ptr<Image> image = images_.value(time);
|
||||
image->EnsureAllocatedFromParams(params_, bounds, rod, false);
|
||||
image->addReference();
|
||||
return image.get();
|
||||
}
|
||||
|
||||
// Fetch on demand for the input clip.
|
||||
// It does get deleted after the plugin is done with it as we
|
||||
// have not incremented the auto ref
|
||||
//
|
||||
// You should do somewhat more sophisticated image management
|
||||
// than this.
|
||||
Image *image = new Image(*this, params_, bounds, rod, true);
|
||||
return image;
|
||||
}
|
||||
}
|
||||
OfxRectD
|
||||
olive::plugin::OliveClipInstance::getRegionOfDefinition(OfxTime time) const
|
||||
@@ -127,9 +179,9 @@ olive::plugin::OliveClipInstance::getRegionOfDefinition(OfxTime time) const
|
||||
return regionOfDefinitions_[time];
|
||||
}
|
||||
OfxRectD regionOfDefinition;
|
||||
regionOfDefinition.x1=regionOfDefinition.y1=0;
|
||||
regionOfDefinition.x2=params_.width();
|
||||
regionOfDefinition.y2=params_.height();
|
||||
regionOfDefinition.x1 = regionOfDefinition.y1 = 0;
|
||||
regionOfDefinition.x2 = params_.width();
|
||||
regionOfDefinition.y2 = params_.height();
|
||||
return regionOfDefinition;
|
||||
}
|
||||
void olive::plugin::OliveClipInstance::setRegionOfDefinition(
|
||||
@@ -143,3 +195,86 @@ void olive::plugin::OliveClipInstance::setDefaultRegionOfDefinition(
|
||||
{
|
||||
defaultRegionOfDefinitions_ = regionOfDefinition;
|
||||
}
|
||||
void olive::plugin::OliveClipInstance::setInputTexture(TexturePtr texture, OfxTime time){
|
||||
if (!texture) {
|
||||
return;
|
||||
}
|
||||
AVFramePtr frame = texture->frame();
|
||||
if (!frame || !frame->data[0]) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->params_=texture->params();
|
||||
AVPixelFormat expected_fmt =
|
||||
FFmpegUtils::GetFFmpegPixelFormat(params_.format(),
|
||||
params_.channel_count());
|
||||
if (expected_fmt == AV_PIX_FMT_NONE) {
|
||||
return;
|
||||
}
|
||||
OfxRectI bounds = { 0, 0, params_.width(), params_.height() };
|
||||
OfxRectD rod_d = getRegionOfDefinition(time);
|
||||
OfxRectI regionOfDefinition = { static_cast<int>(std::floor(rod_d.x1)),
|
||||
static_cast<int>(std::floor(rod_d.y1)),
|
||||
static_cast<int>(std::ceil(rod_d.x2)),
|
||||
static_cast<int>(std::ceil(rod_d.y2)) };
|
||||
|
||||
std::shared_ptr<Image> image;
|
||||
if (images_.contains(time)) {
|
||||
image = images_.value(time);
|
||||
image->EnsureAllocatedFromParams(params_, bounds, regionOfDefinition,
|
||||
false);
|
||||
} else {
|
||||
image = std::make_shared<Image>(*this, params_, bounds,
|
||||
regionOfDefinition, false);
|
||||
images_.insert(time, image);
|
||||
}
|
||||
|
||||
uint8_t *dst = image->data();
|
||||
if (!dst) {
|
||||
return;
|
||||
}
|
||||
|
||||
AVFramePtr src_frame = frame;
|
||||
if (frame->format != expected_fmt ||
|
||||
frame->width != params_.width() ||
|
||||
frame->height != params_.height()) {
|
||||
AVFramePtr converted = CreateAVFramePtr();
|
||||
converted->format = expected_fmt;
|
||||
converted->width = params_.width();
|
||||
converted->height = params_.height();
|
||||
if (av_frame_get_buffer(converted.get(), 0) < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
SwsContext *sws_ctx = sws_getContext(
|
||||
frame->width, frame->height,
|
||||
static_cast<AVPixelFormat>(frame->format),
|
||||
converted->width, converted->height,
|
||||
static_cast<AVPixelFormat>(converted->format),
|
||||
SWS_POINT, nullptr, nullptr, nullptr);
|
||||
if (!sws_ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
sws_scale(sws_ctx, frame->data, frame->linesize, 0, frame->height,
|
||||
converted->data, converted->linesize);
|
||||
sws_freeContext(sws_ctx);
|
||||
|
||||
src_frame = converted;
|
||||
}
|
||||
|
||||
int bytes_per_component = params_.format().byte_count();
|
||||
int bytes_per_row = params_.width() * params_.channel_count() *
|
||||
bytes_per_component;
|
||||
int src_row_bytes = src_frame->linesize[0];
|
||||
int dst_row_bytes = image->row_bytes();
|
||||
int copy_bytes = std::min(bytes_per_row,
|
||||
std::min(src_row_bytes, dst_row_bytes));
|
||||
int copy_height = std::min(image->height(), src_frame->height);
|
||||
|
||||
const uint8_t *src = src_frame->data[0];
|
||||
for (int y = 0; y < copy_height; ++y) {
|
||||
std::memcpy(dst + y * dst_row_bytes, src + y * src_row_bytes,
|
||||
copy_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,10 +23,14 @@
|
||||
|
||||
#ifndef OLIVECLIP_H
|
||||
#define OLIVECLIP_H
|
||||
#include "image.h"
|
||||
#include "ofxCore.h"
|
||||
#include "ofxhClip.h"
|
||||
#include "render/texture.h"
|
||||
#include "render/videoparams.h"
|
||||
|
||||
#include <QMap>
|
||||
#include <memory>
|
||||
namespace olive
|
||||
{
|
||||
namespace plugin
|
||||
@@ -34,14 +38,15 @@ namespace plugin
|
||||
class OliveClipInstance: public OFX::Host::ImageEffect::ClipInstance {
|
||||
public:
|
||||
OliveClipInstance(OFX::Host::ImageEffect::Instance* effectInstance,
|
||||
OFX::Host::ImageEffect::ClipDescriptor& desc,VideoParams params)
|
||||
OFX::Host::ImageEffect::ClipDescriptor& desc,VideoParams ¶ms)
|
||||
: ClipInstance(effectInstance, desc)
|
||||
, name_(desc.getName())
|
||||
{
|
||||
params_ = params;
|
||||
}
|
||||
OFX::Host::ImageEffect::Image& getOutputImage()
|
||||
std::shared_ptr<OFX::Host::ImageEffect::Image> getOutputImage(OfxTime time)
|
||||
{
|
||||
return image_;
|
||||
return images_[time];
|
||||
}
|
||||
|
||||
const std::string &getUnmappedBitDepth() const override;
|
||||
@@ -59,11 +64,13 @@ public:
|
||||
OfxRectD getRegionOfDefinition(OfxTime time) const override;
|
||||
|
||||
void setRegionOfDefinition(OfxRectD regionOfDefinition, OfxTime time);
|
||||
void olive::plugin::OliveClipInstance::setDefaultRegionOfDefinition(
|
||||
OfxRectD regionOfDefinition);
|
||||
void setDefaultRegionOfDefinition(OfxRectD regionOfDefinition);
|
||||
# ifdef OFX_SUPPORTS_OPENGLRENDER
|
||||
OFX::Host::ImageEffect::Texture* loadTexture(OfxTime time, const char *format, const OfxRectD *optionalBounds) { return NULL; };
|
||||
# endif
|
||||
|
||||
void setInputTexture(TexturePtr texture, OfxTime time);
|
||||
|
||||
private:
|
||||
VideoParams params_;
|
||||
|
||||
@@ -71,7 +78,8 @@ private:
|
||||
|
||||
OfxRectD defaultRegionOfDefinitions_;
|
||||
|
||||
OFX::Host::ImageEffect::Image image_;
|
||||
std::string name_;
|
||||
QMap<OfxTime,std::shared_ptr<Image>> images_;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
#include "node/project.h"
|
||||
#include "ofxhImageEffect.h"
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <ofxhPluginCache.h>
|
||||
#include <ofxhBinary.h>
|
||||
|
||||
@@ -28,6 +30,8 @@
|
||||
|
||||
#include "OlivePluginInstance.h"
|
||||
#include "common/Current.h"
|
||||
#include "ofxMessage.h"
|
||||
#include <QMessageBox>
|
||||
using namespace OFX::Host;
|
||||
using namespace olive::plugin;
|
||||
|
||||
@@ -93,15 +97,76 @@ OliveHost::makeDescriptor(const std::string &bundlePath,
|
||||
return desc;
|
||||
}
|
||||
|
||||
OFX::Host::ImageEffect::Instance* OliveHost::newInstance(void* clientData,
|
||||
OFX::Host::ImageEffect::ImageEffectPlugin* plugin,
|
||||
OFX::Host::ImageEffect::Descriptor& desc,
|
||||
ImageEffect::Instance* OliveHost::newInstance(std::shared_ptr<PluginNode> clientData,
|
||||
ImageEffect::ImageEffectPlugin* plugin,
|
||||
ImageEffect::Descriptor& desc,
|
||||
const std::string& context){
|
||||
auto* instance = new OlivePluginInstance(
|
||||
plugin, desc, context, Current::getInstance().interactive());
|
||||
if (clientData) {
|
||||
instance->setNode(static_cast<olive::plugin::PluginNode*>(clientData));
|
||||
instance->setNode(clientData);
|
||||
}
|
||||
instances_.append(instance);
|
||||
return instance;
|
||||
};
|
||||
OfxStatus olive::plugin::OliveHost::vmessage(const char *type, const char *id, const char *format,
|
||||
va_list args){
|
||||
if (!type || !format) {
|
||||
return kOfxStatFailed;
|
||||
}
|
||||
|
||||
char buffer[1024];
|
||||
buffer[0] = '\0';
|
||||
vsnprintf(buffer, sizeof(buffer), format, args);
|
||||
QString message(buffer);
|
||||
|
||||
if (strcmp(type, kOfxMessageQuestion) == 0) {
|
||||
auto ret = QMessageBox::question(nullptr, "", message,
|
||||
QMessageBox::Ok, QMessageBox::Cancel);
|
||||
return (ret == QMessageBox::Ok) ? kOfxStatReplyYes : kOfxStatReplyNo;
|
||||
}
|
||||
|
||||
if (strcmp(type, kOfxMessageError) == 0) {
|
||||
QMessageBox::critical(nullptr, "", message);
|
||||
} else if (strcmp(type, kOfxMessageWarning) == 0) {
|
||||
QMessageBox::warning(nullptr, "", message);
|
||||
} else {
|
||||
QMessageBox::information(nullptr, "", message);
|
||||
}
|
||||
|
||||
return kOfxStatOK;
|
||||
}
|
||||
|
||||
OfxStatus olive::plugin::OliveHost::setPersistentMessage(
|
||||
const char *type, const char *id, const char *format, va_list args)
|
||||
{
|
||||
if (!type || !format) {
|
||||
return kOfxStatFailed;
|
||||
}
|
||||
|
||||
char buffer[1024];
|
||||
buffer[0] = '\0';
|
||||
vsnprintf(buffer, sizeof(buffer), format, args);
|
||||
QString message(buffer);
|
||||
|
||||
if (strcmp(type, kOfxMessageError) == 0) {
|
||||
persistent_messages_.append({HostMessageType::Error, message});
|
||||
QMessageBox::critical(nullptr, "", message);
|
||||
} else if (strcmp(type, kOfxMessageWarning) == 0) {
|
||||
persistent_messages_.append({HostMessageType::Warning, message});
|
||||
QMessageBox::warning(nullptr, "", message);
|
||||
} else if (strcmp(type, kOfxMessageMessage) == 0) {
|
||||
persistent_messages_.append({HostMessageType::Message, message});
|
||||
QMessageBox::information(nullptr, "", message);
|
||||
} else {
|
||||
return kOfxStatFailed;
|
||||
}
|
||||
|
||||
return kOfxStatOK;
|
||||
}
|
||||
|
||||
OfxStatus olive::plugin::OliveHost::clearPersistentMessage()
|
||||
{
|
||||
persistent_messages_.clear();
|
||||
return kOfxStatOK;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
#ifndef OLIVE_HOST_H
|
||||
#define OLIVE_HOST_H
|
||||
#include "node/plugins/Plugin.h"
|
||||
#include "ofxhHost.h"
|
||||
#include "ofxhImageEffectAPI.h"
|
||||
#include "ofxCore.h"
|
||||
@@ -32,6 +33,15 @@
|
||||
#include <qlist.h>
|
||||
namespace olive {
|
||||
namespace plugin {
|
||||
enum class HostMessageType{
|
||||
Error,
|
||||
Warning,
|
||||
Message
|
||||
};
|
||||
struct HostPersistentMessage{
|
||||
HostMessageType type;
|
||||
QString message;
|
||||
};
|
||||
|
||||
|
||||
void loadPlugins(QString path);
|
||||
@@ -47,7 +57,7 @@ public:
|
||||
return true;
|
||||
};
|
||||
|
||||
OFX::Host::ImageEffect::Instance* newInstance(void* clientData,
|
||||
OFX::Host::ImageEffect::Instance* newInstance(std::shared_ptr<PluginNode> ptr,
|
||||
OFX::Host::ImageEffect::ImageEffectPlugin* plugin,
|
||||
OFX::Host::ImageEffect::Descriptor& desc,
|
||||
const std::string& context) override;
|
||||
@@ -63,10 +73,27 @@ public:
|
||||
OFX::Host::ImageEffect::Descriptor *makeDescriptor(
|
||||
const std::string &bundlePath,
|
||||
OFX::Host::ImageEffect::ImageEffectPlugin *plugin) override;
|
||||
/// vmessage
|
||||
virtual OfxStatus vmessage(const char *type, const char *id,
|
||||
const char *format, va_list args);
|
||||
|
||||
/// vmessage
|
||||
virtual OfxStatus setPersistentMessage(const char *type, const char *id,
|
||||
const char *format, va_list args);
|
||||
/// vmessage
|
||||
virtual OfxStatus clearPersistentMessage();
|
||||
|
||||
#ifdef OFX_SUPPORTS_OPENGLRENDER
|
||||
/// @see OfxImageEffectOpenGLRenderSuiteV1.flushResources()
|
||||
virtual OfxStatus flushOpenGLResources() const
|
||||
{
|
||||
return kOfxStatFailed;
|
||||
};
|
||||
#endif
|
||||
private:
|
||||
QList<OFX::Host::ImageEffect::Descriptor*> descriptors_;
|
||||
QList<OFX::Host::ImageEffect::Instance*> instances_;
|
||||
QList<HostPersistentMessage> persistent_messages_;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,12 +94,18 @@ OfxStatus OlivePluginInstance::setPersistentMessage(const char *type, const char
|
||||
} else {
|
||||
return kOfxStatFailed;
|
||||
}
|
||||
if (node_) {
|
||||
emit node_->MessageCountChanged();
|
||||
}
|
||||
return kOfxStatOK;
|
||||
}
|
||||
OfxStatus OlivePluginInstance::clearPersistentMessage()
|
||||
{
|
||||
persistentErrors_.clear();
|
||||
// TODO: tell the shell to remove message.
|
||||
if (node_) {
|
||||
emit node_->MessageCountChanged();
|
||||
}
|
||||
return kOfxStatOK;
|
||||
}
|
||||
void OlivePluginInstance::getProjectSize(double &xSize, double &ySize) const
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "ofxImageEffect.h"
|
||||
#include <QString>
|
||||
#include "ofxhImageEffect.h"
|
||||
#include "node/plugins/Plugin.h"
|
||||
#include "render/videoparams.h"
|
||||
|
||||
#include <map>
|
||||
@@ -71,7 +72,7 @@ public:
|
||||
{
|
||||
this->params_=params;
|
||||
}
|
||||
void setNode(PluginNode* node)
|
||||
void setNode(std::shared_ptr<PluginNode> node)
|
||||
{
|
||||
node_ = node;
|
||||
}
|
||||
@@ -91,6 +92,14 @@ public:
|
||||
va_list args) override;
|
||||
|
||||
OfxStatus clearPersistentMessage() override;
|
||||
int persistentMessageCount() const
|
||||
{
|
||||
return persistentErrors_.size();
|
||||
}
|
||||
const QList<PersistentErrors> &persistentMessages() const
|
||||
{
|
||||
return persistentErrors_;
|
||||
}
|
||||
|
||||
void getProjectSize(double& xSize, double& ySize) const override;
|
||||
void getProjectOffset(double& xOffset, double& yOffset) const override;
|
||||
@@ -174,6 +183,8 @@ public:
|
||||
|
||||
/// get the first and last times available on the effect's timeline
|
||||
virtual void timeLineGetBounds(double &t1, double &t2);
|
||||
|
||||
|
||||
private:
|
||||
QList<PersistentErrors> persistentErrors_;
|
||||
VideoParams params_;
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Olive Community Edition - Non-Linear Video Editor
|
||||
* Copyright (C) 2026 Olive CE 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 "image.h"
|
||||
|
||||
#include "ofxImageEffect.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace olive {
|
||||
namespace plugin {
|
||||
|
||||
static const char *PixelDepthToOfx(core::PixelFormat format)
|
||||
{
|
||||
switch (format) {
|
||||
case core::PixelFormat::U8:
|
||||
return kOfxBitDepthByte;
|
||||
case core::PixelFormat::U16:
|
||||
return kOfxBitDepthShort;
|
||||
case core::PixelFormat::F16:
|
||||
return kOfxBitDepthHalf;
|
||||
case core::PixelFormat::F32:
|
||||
return kOfxBitDepthFloat;
|
||||
case core::PixelFormat::INVALID:
|
||||
case core::PixelFormat::COUNT:
|
||||
break;
|
||||
}
|
||||
|
||||
return kOfxBitDepthNone;
|
||||
}
|
||||
|
||||
static const char *ComponentsToOfx(int channel_count)
|
||||
{
|
||||
switch (channel_count) {
|
||||
case 1:
|
||||
return kOfxImageComponentAlpha;
|
||||
case 3:
|
||||
return kOfxImageComponentRGB;
|
||||
case 4:
|
||||
return kOfxImageComponentRGBA;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return kOfxImageComponentNone;
|
||||
}
|
||||
|
||||
Image::Image(OFX::Host::ImageEffect::ClipInstance &clip_instance)
|
||||
: OFX::Host::ImageEffect::Image(clip_instance)
|
||||
, width_(0)
|
||||
, height_(0)
|
||||
, format_(core::PixelFormat::INVALID)
|
||||
, premultiplied_alpha_(false)
|
||||
, channel_count_(0)
|
||||
, row_bytes_(0)
|
||||
, bounds_{0, 0, 0, 0}
|
||||
, rod_{0, 0, 0, 0}
|
||||
{
|
||||
}
|
||||
|
||||
Image::Image(OFX::Host::ImageEffect::ClipInstance &clip_instance,
|
||||
const VideoParams ¶ms,
|
||||
const OfxRectI &bounds,
|
||||
const OfxRectI &rod,
|
||||
bool clear)
|
||||
: OFX::Host::ImageEffect::Image(clip_instance)
|
||||
, width_(0)
|
||||
, height_(0)
|
||||
, format_(core::PixelFormat::INVALID)
|
||||
, premultiplied_alpha_(false)
|
||||
, channel_count_(0)
|
||||
, row_bytes_(0)
|
||||
, bounds_{0, 0, 0, 0}
|
||||
, rod_{0, 0, 0, 0}
|
||||
{
|
||||
AllocateFromParams(params, bounds, rod, clear);
|
||||
}
|
||||
|
||||
Image::~Image()
|
||||
{
|
||||
}
|
||||
|
||||
void Image::AllocateFromParams(const VideoParams ¶ms,
|
||||
const OfxRectI &bounds,
|
||||
const OfxRectI &rod,
|
||||
bool clear)
|
||||
{
|
||||
Allocate(bounds.x2 - bounds.x1,
|
||||
bounds.y2 - bounds.y1,
|
||||
params.format(),
|
||||
params.channel_count(),
|
||||
params.premultiplied_alpha(),
|
||||
bounds,
|
||||
rod,
|
||||
clear);
|
||||
}
|
||||
|
||||
void Image::EnsureAllocatedFromParams(const VideoParams ¶ms,
|
||||
const OfxRectI &bounds,
|
||||
const OfxRectI &rod,
|
||||
bool clear)
|
||||
{
|
||||
bool same = (width_ == bounds.x2 - bounds.x1) &&
|
||||
(height_ == bounds.y2 - bounds.y1) &&
|
||||
(format_ == params.format()) &&
|
||||
(channel_count_ == params.channel_count()) &&
|
||||
(premultiplied_alpha_ == params.premultiplied_alpha()) &&
|
||||
(bounds_.x1 == bounds.x1) && (bounds_.y1 == bounds.y1) &&
|
||||
(bounds_.x2 == bounds.x2) && (bounds_.y2 == bounds.y2) &&
|
||||
(rod_.x1 == rod.x1) && (rod_.y1 == rod.y1) &&
|
||||
(rod_.x2 == rod.x2) && (rod_.y2 == rod.y2);
|
||||
|
||||
if (!same) {
|
||||
AllocateFromParams(params, bounds, rod, clear);
|
||||
} else if (clear && !image_.empty()) {
|
||||
std::fill(image_.begin(), image_.end(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
void Image::Allocate(int width,
|
||||
int height,
|
||||
core::PixelFormat format,
|
||||
int channel_count,
|
||||
bool premultiplied_alpha,
|
||||
const OfxRectI &bounds,
|
||||
const OfxRectI &rod,
|
||||
bool clear)
|
||||
{
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
format_ = format;
|
||||
channel_count_ = channel_count;
|
||||
premultiplied_alpha_ = premultiplied_alpha;
|
||||
bounds_ = bounds;
|
||||
rod_ = rod;
|
||||
|
||||
int bytes_per_component = format_.byte_count();
|
||||
row_bytes_ = width_ * channel_count_ * bytes_per_component;
|
||||
int buffer_size = row_bytes_ * height_;
|
||||
if (buffer_size < 0) {
|
||||
buffer_size = 0;
|
||||
}
|
||||
|
||||
image_.resize(static_cast<size_t>(buffer_size));
|
||||
if (clear && !image_.empty()) {
|
||||
std::fill(image_.begin(), image_.end(), 0);
|
||||
}
|
||||
|
||||
setPointerProperty(kOfxImagePropData, image_.data());
|
||||
setIntProperty(kOfxImagePropRowBytes, row_bytes_);
|
||||
setIntProperty(kOfxImagePropBounds, bounds.x1, 0);
|
||||
setIntProperty(kOfxImagePropBounds, bounds.y1, 1);
|
||||
setIntProperty(kOfxImagePropBounds, bounds.x2, 2);
|
||||
setIntProperty(kOfxImagePropBounds, bounds.y2, 3);
|
||||
setIntProperty(kOfxImagePropRegionOfDefinition, rod.x1, 0);
|
||||
setIntProperty(kOfxImagePropRegionOfDefinition, rod.y1, 1);
|
||||
setIntProperty(kOfxImagePropRegionOfDefinition, rod.x2, 2);
|
||||
setIntProperty(kOfxImagePropRegionOfDefinition, rod.y2, 3);
|
||||
setStringProperty(kOfxImageEffectPropComponents,
|
||||
ComponentsToOfx(channel_count_));
|
||||
setStringProperty(kOfxImageEffectPropPixelDepth,
|
||||
PixelDepthToOfx(format_));
|
||||
setStringProperty(kOfxImageEffectPropPreMultiplication,
|
||||
premultiplied_alpha_ ? kOfxImagePreMultiplied
|
||||
: kOfxImageUnPreMultiplied);
|
||||
}
|
||||
|
||||
core::PixelFormat Image::pixel_format()
|
||||
{
|
||||
if (format_ != core::PixelFormat::INVALID) {
|
||||
return format_;
|
||||
}
|
||||
|
||||
std::string type = getStringProperty(kOfxImageEffectPropPixelDepth);
|
||||
if (type == kOfxBitDepthByte) {
|
||||
format_ = core::PixelFormat::U8;
|
||||
} else if (type == kOfxBitDepthShort) {
|
||||
format_ = core::PixelFormat::U16;
|
||||
} else if (type == kOfxBitDepthHalf) {
|
||||
format_ = core::PixelFormat::F16;
|
||||
} else if (type == kOfxBitDepthFloat) {
|
||||
format_ = core::PixelFormat::F32;
|
||||
} else {
|
||||
format_ = core::PixelFormat::INVALID;
|
||||
}
|
||||
return format_;
|
||||
}
|
||||
|
||||
bool Image::premultiplied_alpha()
|
||||
{
|
||||
std::string premultiplied =
|
||||
getStringProperty(kOfxImageEffectPropPreMultiplication);
|
||||
premultiplied_alpha_ = (premultiplied == kOfxImagePreMultiplied);
|
||||
return premultiplied_alpha_;
|
||||
}
|
||||
|
||||
int Image::width()
|
||||
{
|
||||
int bounds[4] = {0};
|
||||
getIntPropertyN(kOfxImagePropBounds, bounds, 4);
|
||||
width_ = bounds[2] - bounds[0];
|
||||
return width_;
|
||||
}
|
||||
|
||||
int Image::height()
|
||||
{
|
||||
int bounds[4] = {0};
|
||||
getIntPropertyN(kOfxImagePropBounds, bounds, 4);
|
||||
height_ = bounds[3] - bounds[1];
|
||||
return height_;
|
||||
}
|
||||
|
||||
int Image::channel_count()
|
||||
{
|
||||
std::string type = getStringProperty(kOfxImageEffectPropComponents);
|
||||
if (type == kOfxImageComponentAlpha) {
|
||||
channel_count_ = 1;
|
||||
} else if (type == kOfxImageComponentRGBA) {
|
||||
channel_count_ = 4;
|
||||
} else if (type == kOfxImageComponentRGB) {
|
||||
channel_count_ = 3;
|
||||
} else {
|
||||
channel_count_ = 0;
|
||||
}
|
||||
return channel_count_;
|
||||
}
|
||||
|
||||
} // namespace plugin
|
||||
} // namespace olive
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Olive Community Edition - Non-Linear Video Editor
|
||||
* Copyright (C) 2026 Olive CE 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef OLIVE_EDITOR_PLUGIN_IMAGE_H
|
||||
#define OLIVE_EDITOR_PLUGIN_IMAGE_H
|
||||
|
||||
#include "ofxCore.h"
|
||||
#include "ofxhClip.h"
|
||||
#include "olive/core/render/pixelformat.h"
|
||||
#include "render/loopmode.h"
|
||||
#include "render/videoparams.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
namespace olive
|
||||
{
|
||||
namespace plugin
|
||||
{
|
||||
class Image : public OFX::Host::ImageEffect::Image {
|
||||
public:
|
||||
Image(OFX::Host::ImageEffect::ClipInstance &clip_instance);
|
||||
Image(OFX::Host::ImageEffect::ClipInstance &clip_instance,
|
||||
const VideoParams ¶ms,
|
||||
const OfxRectI &bounds,
|
||||
const OfxRectI &rod,
|
||||
bool clear = true);
|
||||
~Image();
|
||||
uint8_t *data() {
|
||||
return image_.empty() ? nullptr : image_.data();
|
||||
}
|
||||
int width();
|
||||
int height();
|
||||
core::PixelFormat pixel_format();
|
||||
bool premultiplied_alpha();
|
||||
int channel_count();
|
||||
|
||||
void AllocateFromParams(const VideoParams ¶ms,
|
||||
const OfxRectI &bounds,
|
||||
const OfxRectI &rod,
|
||||
bool clear = true);
|
||||
void EnsureAllocatedFromParams(const VideoParams ¶ms,
|
||||
const OfxRectI &bounds,
|
||||
const OfxRectI &rod,
|
||||
bool clear = false);
|
||||
void Allocate(int width,
|
||||
int height,
|
||||
core::PixelFormat format,
|
||||
int channel_count,
|
||||
bool premultiplied_alpha,
|
||||
const OfxRectI &bounds,
|
||||
const OfxRectI &rod,
|
||||
bool clear = true);
|
||||
int row_bytes() const
|
||||
{
|
||||
return row_bytes_;
|
||||
}
|
||||
protected:
|
||||
std::vector<uint8_t> image_;
|
||||
int width_;
|
||||
int height_;
|
||||
core::PixelFormat format_;
|
||||
bool premultiplied_alpha_;
|
||||
int channel_count_;
|
||||
int row_bytes_;
|
||||
OfxRectI bounds_;
|
||||
OfxRectI rod_;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif //OLIVE_EDITOR_PLUGIN_IMAGE_H
|
||||
@@ -30,17 +30,31 @@ namespace plugin {
|
||||
|
||||
class PluginJob :public AcceleratedJob{
|
||||
public:
|
||||
explicit PluginJob(OFX::Host::ImageEffect::Instance* pluginInstance, NodeValueRow row): AcceleratedJob()
|
||||
explicit PluginJob(const OFX::Host::ImageEffect::Instance* pluginInstance,
|
||||
const PluginNode* node, NodeValueRow row)
|
||||
: AcceleratedJob()
|
||||
{
|
||||
this->pluginInstance = pluginInstance;
|
||||
this->pluginInstance_ = pluginInstance;
|
||||
this->node_=node;
|
||||
Insert(row);
|
||||
}
|
||||
|
||||
PluginNode *node() const {
|
||||
return const_cast<PluginNode *>(node_);
|
||||
}
|
||||
|
||||
OFX::Host::ImageEffect::Instance* pluginInstance() {
|
||||
return const_cast<OFX::Host::ImageEffect::Instance*>(pluginInstance_);
|
||||
}
|
||||
|
||||
private:
|
||||
OFX::Host::ImageEffect::Instance *pluginInstance=nullptr;
|
||||
const OFX::Host::ImageEffect::Instance *pluginInstance_=nullptr;
|
||||
|
||||
QHash<OfxTime, QHash<QString, std::any>> paramsOnTime;
|
||||
|
||||
QHash<QString, std::any> params;
|
||||
|
||||
const PluginNode *node_=nullptr;
|
||||
};
|
||||
|
||||
} // plugin
|
||||
|
||||
@@ -20,16 +20,293 @@
|
||||
//
|
||||
// Created by mikesolar on 25-10-19.
|
||||
//
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <qtypes.h>
|
||||
#define GL_PREAMBLE //QMutexLocker __l(&global_opengl_mutex);
|
||||
#include "pluginrenderer.h"
|
||||
|
||||
#include "pluginSupport/OliveClip.h"
|
||||
#include "pluginSupport/OlivePluginInstance.h"
|
||||
#include "common/ffmpegutils.h"
|
||||
#include "ofxImageEffect.h"
|
||||
extern "C"{
|
||||
#include <libavutil/pixfmt.h>
|
||||
#include <libavutil/pixdesc.h>
|
||||
#include <libswscale/swscale.h>
|
||||
}
|
||||
|
||||
void olive::plugin::PluginRenderer::Blit(QVariant shader,
|
||||
olive::AcceleratedJob &job,
|
||||
static AVPixelFormat GetOfxAVPixelFormat(const OFX::Host::ImageEffect::Image &image,
|
||||
int *bytes_per_pixel)
|
||||
{
|
||||
const std::string &depth = image.getStringProperty(kOfxImageEffectPropPixelDepth);
|
||||
const std::string &components = image.getStringProperty(kOfxImageEffectPropComponents);
|
||||
|
||||
olive::core::PixelFormat pixel_format = olive::core::PixelFormat::INVALID;
|
||||
if (depth == kOfxBitDepthByte) {
|
||||
pixel_format = olive::core::PixelFormat::U8;
|
||||
} else if (depth == kOfxBitDepthShort) {
|
||||
pixel_format = olive::core::PixelFormat::U16;
|
||||
} else if (depth == kOfxBitDepthHalf) {
|
||||
pixel_format = olive::core::PixelFormat::F16;
|
||||
} else if (depth == kOfxBitDepthFloat) {
|
||||
pixel_format = olive::core::PixelFormat::F32;
|
||||
}
|
||||
|
||||
int channel_count = 0;
|
||||
if (components == kOfxImageComponentRGBA) {
|
||||
channel_count = 4;
|
||||
} else if (components == kOfxImageComponentRGB) {
|
||||
channel_count = 3;
|
||||
} else if (components == kOfxImageComponentAlpha) {
|
||||
channel_count = 1;
|
||||
}
|
||||
|
||||
AVPixelFormat pix_fmt = olive::FFmpegUtils::GetFFmpegPixelFormat(pixel_format, channel_count);
|
||||
if (pix_fmt == AV_PIX_FMT_NONE && channel_count == 1) {
|
||||
if (pixel_format == olive::core::PixelFormat::U8) {
|
||||
pix_fmt = AV_PIX_FMT_GRAY8;
|
||||
} else if (pixel_format == olive::core::PixelFormat::U16) {
|
||||
pix_fmt = AV_PIX_FMT_GRAY16LE;
|
||||
}
|
||||
}
|
||||
|
||||
if (pix_fmt == AV_PIX_FMT_NONE) {
|
||||
return AV_PIX_FMT_NONE;
|
||||
}
|
||||
|
||||
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt);
|
||||
if (!desc) {
|
||||
return AV_PIX_FMT_NONE;
|
||||
}
|
||||
|
||||
int bits_per_pixel = av_get_bits_per_pixel(desc);
|
||||
if (bits_per_pixel <= 0 || bits_per_pixel % 8 != 0) {
|
||||
return AV_PIX_FMT_NONE;
|
||||
}
|
||||
|
||||
*bytes_per_pixel = bits_per_pixel / 8;
|
||||
return pix_fmt;
|
||||
}
|
||||
|
||||
static olive::AVFramePtr create_avframe_from_ofx_image(OFX::Host::ImageEffect::Image &image)
|
||||
{
|
||||
void *data_ptr = image.getPointerProperty(kOfxImagePropData);
|
||||
if (!data_ptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int bounds[4] = {0, 0, 0, 0};
|
||||
image.getIntPropertyN(kOfxImagePropBounds, bounds, 4);
|
||||
int width = bounds[2] - bounds[0];
|
||||
int height = bounds[3] - bounds[1];
|
||||
if (width <= 0 || height <= 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int bytes_per_pixel = 0;
|
||||
AVPixelFormat pix_fmt = GetOfxAVPixelFormat(image, &bytes_per_pixel);
|
||||
if (pix_fmt == AV_PIX_FMT_NONE || bytes_per_pixel <= 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int row_bytes = image.getIntProperty(kOfxImagePropRowBytes);
|
||||
if (row_bytes <= 0) {
|
||||
row_bytes = width * bytes_per_pixel;
|
||||
}
|
||||
|
||||
uint8_t *src = static_cast<uint8_t *>(data_ptr);
|
||||
src += bounds[1] * row_bytes + bounds[0] * bytes_per_pixel;
|
||||
|
||||
olive::AVFramePtr frame = olive::CreateAVFramePtr();
|
||||
frame->width = width;
|
||||
frame->height = height;
|
||||
frame->format = pix_fmt;
|
||||
|
||||
if (av_frame_get_buffer(frame.get(), 0) < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const int copy_bytes = width * bytes_per_pixel;
|
||||
for (int y = 0; y < height; ++y) {
|
||||
std::memcpy(frame->data[0] + y * frame->linesize[0],
|
||||
src + y * row_bytes,
|
||||
copy_bytes);
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
static AVPixelFormat GetDestinationAVPixelFormat(const olive::VideoParams ¶ms)
|
||||
{
|
||||
AVPixelFormat pix_fmt =
|
||||
olive::FFmpegUtils::GetFFmpegPixelFormat(params.format(),
|
||||
params.channel_count());
|
||||
if (pix_fmt == AV_PIX_FMT_NONE && params.channel_count() == 1) {
|
||||
if (params.format() == olive::core::PixelFormat::U8) {
|
||||
pix_fmt = AV_PIX_FMT_GRAY8;
|
||||
} else if (params.format() == olive::core::PixelFormat::U16) {
|
||||
pix_fmt = AV_PIX_FMT_GRAY16LE;
|
||||
}
|
||||
}
|
||||
return pix_fmt;
|
||||
}
|
||||
|
||||
static olive::AVFramePtr ConvertFrameIfNeeded(olive::AVFramePtr src,
|
||||
const olive::VideoParams &dst_params)
|
||||
{
|
||||
if (!src) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AVPixelFormat dst_fmt = GetDestinationAVPixelFormat(dst_params);
|
||||
if (dst_fmt == AV_PIX_FMT_NONE) {
|
||||
return src;
|
||||
}
|
||||
|
||||
if (src->format == dst_fmt &&
|
||||
src->width == dst_params.width() &&
|
||||
src->height == dst_params.height()) {
|
||||
return src;
|
||||
}
|
||||
|
||||
olive::AVFramePtr dst = olive::CreateAVFramePtr();
|
||||
dst->format = dst_fmt;
|
||||
dst->width = dst_params.width();
|
||||
dst->height = dst_params.height();
|
||||
if (av_frame_get_buffer(dst.get(), 0) < 0) {
|
||||
return src;
|
||||
}
|
||||
|
||||
SwsContext *sws_ctx = sws_getContext(
|
||||
src->width, src->height, static_cast<AVPixelFormat>(src->format),
|
||||
dst->width, dst->height, dst_fmt, SWS_POINT,
|
||||
nullptr, nullptr, nullptr);
|
||||
if (!sws_ctx) {
|
||||
return src;
|
||||
}
|
||||
|
||||
sws_scale(sws_ctx, src->data, src->linesize, 0, src->height,
|
||||
dst->data, dst->linesize);
|
||||
sws_freeContext(sws_ctx);
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
void olive::plugin::PluginRenderer::RenderPlugin(TexturePtr src, olive::plugin::PluginJob& job,
|
||||
olive::Texture *destination,
|
||||
olive::VideoParams destination_params,
|
||||
bool clear_destination)
|
||||
bool clear_destination, bool interactive)
|
||||
{
|
||||
auto instance=job.pluginInstance();
|
||||
OfxStatus stat;
|
||||
stat = instance->createInstanceAction();
|
||||
if(stat != kOfxStatOK && stat != kOfxStatReplyDefault) {
|
||||
return;
|
||||
}
|
||||
// now we need to to call getClipPreferences on the instance so that it does the clip component/depth
|
||||
// logic and caches away the components and depth on each clip.
|
||||
bool ok = instance->getClipPreferences();
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
// current render scale of 1
|
||||
OfxPointD renderScale;
|
||||
renderScale.x = renderScale.y = 1.0;
|
||||
|
||||
// The render window is in pixel coordinates
|
||||
// ie: render scale and a PAR of not 1
|
||||
OfxRectI renderWindow;
|
||||
renderWindow.x1 = renderWindow.y1 = 0;
|
||||
|
||||
|
||||
renderWindow.x2 = src->params().width();
|
||||
renderWindow.y2 = src->params().height();
|
||||
|
||||
/// RoI is in canonical coords,
|
||||
OfxRectD regionOfInterest;
|
||||
regionOfInterest.x1 = regionOfInterest.y1 = 0;
|
||||
regionOfInterest.x2 = renderWindow.x2 * instance->getProjectPixelAspectRatio();
|
||||
regionOfInterest.y2 = renderWindow.y2 * instance->getProjectPixelAspectRatio();
|
||||
|
||||
OfxRectD regionOfDefinition;
|
||||
regionOfDefinition.x1 = regionOfDefinition.y1 = 0;
|
||||
regionOfDefinition.x2 = destination_params.width();
|
||||
regionOfDefinition.y2 = destination_params.height();
|
||||
|
||||
|
||||
int numFramesToRender=1;
|
||||
stat = instance->beginRenderAction(0, numFramesToRender,
|
||||
1.0, false, renderScale, true,
|
||||
interactive);
|
||||
if (stat != kOfxStatOK && stat != kOfxStatReplyDefault) {
|
||||
return;
|
||||
}
|
||||
|
||||
OliveClipInstance *clip=dynamic_cast<plugin::OliveClipInstance *>(instance->getClip("Output"));
|
||||
if (!clip) {
|
||||
instance->endRenderAction(0, numFramesToRender, 1.0, interactive, renderScale, true,interactive
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// call get region of interest on each of the inputs
|
||||
OfxTime frame = 0;
|
||||
|
||||
const NodeValueRow &values = job.GetValues();
|
||||
const auto &clips = instance->getDescriptor().getClips();
|
||||
for (const auto &entry : clips) {
|
||||
if (entry.first == kOfxImageEffectOutputClipName) {
|
||||
continue;
|
||||
}
|
||||
OliveClipInstance *input_clip =
|
||||
dynamic_cast<OliveClipInstance *>(instance->getClip(entry.first));
|
||||
if (input_clip) {
|
||||
QString clip_key = QString::fromStdString(entry.first);
|
||||
TexturePtr input_tex = values.value(clip_key).toTexture();
|
||||
if (!input_tex &&
|
||||
entry.first == kOfxImageEffectSimpleSourceClipName) {
|
||||
input_tex = values.value(kTextureInput).toTexture();
|
||||
}
|
||||
if (input_tex) {
|
||||
input_clip->setInputTexture(input_tex, frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
// get the RoI for each input clip
|
||||
// the regions of interest for each input clip are returned in a std::map
|
||||
// on a real host, these will be the regions of each input clip that the
|
||||
// effect needs to render a given frame (clipped to the RoD).
|
||||
//
|
||||
// In our example we are doing full frame fetches regardless.
|
||||
std::map<OFX::Host::ImageEffect::ClipInstance *, OfxRectD> rois;
|
||||
stat = instance->getRegionOfInterestAction(frame, renderScale,
|
||||
regionOfInterest, rois);
|
||||
assert(stat == kOfxStatOK || stat == kOfxStatReplyDefault);
|
||||
|
||||
// render a frame
|
||||
stat = instance->renderAction(0,kOfxImageFieldBoth,renderWindow, renderScale, true, interactive, interactive);
|
||||
assert(stat == kOfxStatOK);
|
||||
|
||||
// get the output image buffer
|
||||
std::shared_ptr<OFX::Host::ImageEffect::Image> output_image = clip->getOutputImage(frame);
|
||||
if (!output_image) {
|
||||
instance->endRenderAction(frame, numFramesToRender, 1.0, interactive, renderScale, true,interactive
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
AVFramePtr frame_ptr = create_avframe_from_ofx_image(*output_image);
|
||||
if (!frame_ptr) {
|
||||
instance->endRenderAction(0, numFramesToRender, 1.0, interactive, renderScale, true,interactive
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
AVFramePtr converted = ConvertFrameIfNeeded(frame_ptr, destination_params);
|
||||
destination->handleFrame(converted);
|
||||
|
||||
instance->endRenderAction(0, numFramesToRender, 1.0, interactive, renderScale, true,interactive
|
||||
);
|
||||
|
||||
}
|
||||
@@ -43,10 +43,10 @@ public:
|
||||
PluginRenderer(QObject *parent=nullptr):OpenGLRenderer(parent){};
|
||||
virtual ~PluginRenderer() override{};
|
||||
protected:
|
||||
virtual void Blit(QVariant shader, olive::AcceleratedJob& job,
|
||||
virtual void RenderPlugin(TexturePtr src, olive::plugin::PluginJob& job,
|
||||
olive::Texture *destination,
|
||||
olive::VideoParams destination_params,
|
||||
bool clear_destination) override;
|
||||
bool clear_destination, bool interactive);
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@@ -175,83 +175,7 @@ void RenderProcessor::Run()
|
||||
= node->getPlugin();
|
||||
std::unique_ptr<OFX::Host::ImageEffect::Instance> instance(plugin->createInstance(kOfxImageEffectContextFilter, NULL));
|
||||
|
||||
OfxStatus stat;
|
||||
stat = instance->createInstanceAction();
|
||||
if(stat != kOfxStatOK && stat != kOfxStatReplyDefault) {
|
||||
ticket_->Finish();
|
||||
return;
|
||||
}
|
||||
// now we need to to call getClipPreferences on the instance so that it does the clip component/depth
|
||||
// logic and caches away the components and depth on each clip.
|
||||
bool ok = instance->getClipPreferences();
|
||||
if (!ok) {
|
||||
ticket_->Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// current render scale of 1
|
||||
OfxPointD renderScale;
|
||||
renderScale.x = renderScale.y = 1.0;
|
||||
|
||||
// The render window is in pixel coordinates
|
||||
// ie: render scale and a PAR of not 1
|
||||
OfxRectI renderWindow;
|
||||
renderWindow.x1 = renderWindow.y1 = 0;
|
||||
renderWindow.x2 = ticket_->property("size").value<QSize>().width();
|
||||
renderWindow.y2 = ticket_->property("size").value<QSize>().height();
|
||||
|
||||
/// RoI is in canonical coords,
|
||||
OfxRectD regionOfInterest;
|
||||
regionOfInterest.x1 = regionOfInterest.y1 = 0;
|
||||
regionOfInterest.x2 = renderWindow.x2 * instance->getProjectPixelAspectRatio();
|
||||
regionOfInterest.y2 = renderWindow.y2 * instance->getProjectPixelAspectRatio();
|
||||
|
||||
OfxRectD regionOfDefinition;
|
||||
regionOfDefinition.x1 = regionOfDefinition.y1 = 0;
|
||||
regionOfDefinition.x2 = params.width();
|
||||
regionOfDefinition.y2 = params.height();
|
||||
|
||||
|
||||
int numFramesToRender=ticket_->property("time").value<rational>().toDouble()
|
||||
* params.frame_rate().toDouble();
|
||||
stat = instance->beginRenderAction(0, numFramesToRender, 1.0, false, renderScale, /*sequential=#1#true, /*interactive=#1#false
|
||||
);
|
||||
if (stat != kOfxStatOK && stat != kOfxStatReplyDefault) {
|
||||
ticket_->Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
plugin::OliveClipInstance *clip=dynamic_cast<plugin::OliveClipInstance *>(instance->getClip("Output"));
|
||||
for(int t = 0; t <= numFramesToRender; ++t)
|
||||
{
|
||||
// call get region of interest on each of the inputs
|
||||
OfxTime frame = t;
|
||||
|
||||
// get the RoI for each input clip
|
||||
// the regions of interest for each input clip are returned in a std::map
|
||||
// on a real host, these will be the regions of each input clip that the
|
||||
// effect needs to render a given frame (clipped to the RoD).
|
||||
//
|
||||
// In our example we are doing full frame fetches regardless.
|
||||
std::map<OFX::Host::ImageEffect::ClipInstance *, OfxRectD> rois;
|
||||
stat = instance->getRegionOfInterestAction(frame, renderScale,
|
||||
regionOfInterest, rois);
|
||||
assert(stat == kOfxStatOK || stat == kOfxStatReplyDefault);
|
||||
|
||||
// render a frame
|
||||
stat = instance->renderAction(t,kOfxImageFieldBoth,renderWindow, renderScale, /*sequential=#1#true, /*interactive=#1#false, /*draft=#1#false);
|
||||
assert(stat == kOfxStatOK);
|
||||
|
||||
// get the output image buffer
|
||||
OFX::Host::ImageEffect::Image *outputImage = clip->getOutputImage();
|
||||
|
||||
std::ostringstream ss;
|
||||
ss << "Output." << t << ".ppm";
|
||||
exportToPPM(ss.str(), outputImage);
|
||||
}
|
||||
|
||||
instance->endRenderAction(0, numFramesToRender, 1.0, false, renderScale, /*sequential=#1#true, /*interactive=#1#false
|
||||
);
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -615,7 +539,7 @@ void RenderProcessor::ProcessShader(TexturePtr destination, const Node *node,
|
||||
locker.unlock();
|
||||
|
||||
// Run shader
|
||||
render_ctx_->BlitToTexture(shader, *job, destination.get());
|
||||
render_ctx_->BlitToTexture(shader, const_cast<ShaderJob&>(*job), destination.get());
|
||||
}
|
||||
|
||||
void RenderProcessor::ProcessSamples(SampleBuffer &destination,
|
||||
|
||||
@@ -154,6 +154,9 @@ public:
|
||||
{
|
||||
frame_=ptr;
|
||||
}
|
||||
AVFramePtr frame(){
|
||||
return frame_;
|
||||
}
|
||||
private:
|
||||
Renderer *renderer_;
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "node/group/group.h"
|
||||
#include "node/nodeundo.h"
|
||||
#include "node/project/sequence/sequence.h"
|
||||
#include "pluginSupport/OlivePluginInstance.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
@@ -49,6 +50,8 @@ NodeParamViewItem::NodeParamViewItem(
|
||||
QWidget *parent)
|
||||
: super(parent)
|
||||
, body_(nullptr)
|
||||
, message_label_(nullptr)
|
||||
, message_container_(nullptr)
|
||||
, node_(node)
|
||||
, create_checkboxes_(create_checkboxes)
|
||||
, ctx_(nullptr)
|
||||
@@ -62,6 +65,8 @@ NodeParamViewItem::NodeParamViewItem(
|
||||
connect(node_, &Node::LabelChanged, this, &NodeParamViewItem::Retranslate);
|
||||
connect(node_, &Node::InputArraySizeChanged, this,
|
||||
&NodeParamViewItem::InputArraySizeChanged);
|
||||
connect(node_, &Node::MessageCountChanged, this,
|
||||
&NodeParamViewItem::UpdateMessagePanel);
|
||||
|
||||
// FIXME: Implemented to pick up when an input is set to hidden or not - DEFINITELY not a fast
|
||||
// way of doing this, but "fine" for now.
|
||||
@@ -93,6 +98,12 @@ void NodeParamViewItem::RecreateBody()
|
||||
body_->setParent(nullptr);
|
||||
body_->deleteLater();
|
||||
}
|
||||
if (message_container_) {
|
||||
message_container_->setParent(nullptr);
|
||||
message_container_->deleteLater();
|
||||
message_container_ = nullptr;
|
||||
message_label_ = nullptr;
|
||||
}
|
||||
|
||||
body_ = new NodeParamViewItemBody(node_, create_checkboxes_, this);
|
||||
connect(body_, &NodeParamViewItemBody::RequestSelectNode, this,
|
||||
@@ -106,7 +117,57 @@ void NodeParamViewItem::RecreateBody()
|
||||
body_->Retranslate();
|
||||
body_->SetTimebase(timebase_);
|
||||
body_->SetTimeTarget(time_target_);
|
||||
SetBody(body_);
|
||||
|
||||
message_container_ = new QWidget(this);
|
||||
QVBoxLayout *message_layout = new QVBoxLayout(message_container_);
|
||||
message_layout->setContentsMargins(0, 0, 0, 0);
|
||||
message_layout->setSpacing(4);
|
||||
|
||||
message_label_ = new QLabel(message_container_);
|
||||
message_label_->setWordWrap(true);
|
||||
message_label_->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||
message_label_->setStyleSheet(
|
||||
QStringLiteral("background: rgba(0, 0, 0, 0.06); padding: 6px;"));
|
||||
message_layout->addWidget(message_label_);
|
||||
message_layout->addWidget(body_);
|
||||
|
||||
SetBody(message_container_);
|
||||
UpdateMessagePanel();
|
||||
}
|
||||
|
||||
void NodeParamViewItem::UpdateMessagePanel()
|
||||
{
|
||||
if (!message_label_) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto *instance = node_->getPluginInstance();
|
||||
auto *olive_instance =
|
||||
dynamic_cast<plugin::OlivePluginInstance *>(instance);
|
||||
if (!olive_instance || olive_instance->persistentMessageCount() == 0) {
|
||||
message_label_->setVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
QStringList lines;
|
||||
for (const auto &msg : olive_instance->persistentMessages()) {
|
||||
QString prefix;
|
||||
switch (msg.type) {
|
||||
case plugin::ErrorType::Error:
|
||||
prefix = QStringLiteral("Error");
|
||||
break;
|
||||
case plugin::ErrorType::Warning:
|
||||
prefix = QStringLiteral("Warning");
|
||||
break;
|
||||
case plugin::ErrorType::Message:
|
||||
prefix = QStringLiteral("Message");
|
||||
break;
|
||||
}
|
||||
lines.append(QStringLiteral("%1: %2").arg(prefix, msg.message));
|
||||
}
|
||||
|
||||
message_label_->setText(lines.join('\n'));
|
||||
message_label_->setVisible(true);
|
||||
}
|
||||
|
||||
int NodeParamViewItem::GetElementY(const NodeInput &c) const
|
||||
|
||||
@@ -232,6 +232,8 @@ protected slots:
|
||||
|
||||
private:
|
||||
NodeParamViewItemBody *body_;
|
||||
QLabel *message_label_;
|
||||
QWidget *message_container_;
|
||||
|
||||
Node *node_;
|
||||
|
||||
@@ -247,6 +249,7 @@ private:
|
||||
|
||||
private slots:
|
||||
void RecreateBody();
|
||||
void UpdateMessagePanel();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "node/group/group.h"
|
||||
#include "node/nodeundo.h"
|
||||
#include "node/project/serializer/serializer.h"
|
||||
#include "panel/panelmanager.h"
|
||||
#include "node/traverser.h"
|
||||
#include "ui/icons/icons.h"
|
||||
#include "widget/menu/menushared.h"
|
||||
@@ -639,6 +640,15 @@ void NodeView::mouseDoubleClickEvent(QMouseEvent *event)
|
||||
dynamic_cast<NodeViewItem *>(itemAt(event->pos()));
|
||||
if (item_at_cursor) {
|
||||
item_at_cursor->ToggleExpanded();
|
||||
if (PanelManager::instance()) {
|
||||
if (PanelWidget *panel = PanelManager::instance()
|
||||
->GetPanelWithName(
|
||||
QStringLiteral("ParamPanel"))) {
|
||||
panel->show();
|
||||
panel->raise();
|
||||
panel->setFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "config/config.h"
|
||||
#include "core.h"
|
||||
#include "node/nodeundo.h"
|
||||
#include "pluginSupport/OlivePluginInstance.h"
|
||||
#include "nodeview.h"
|
||||
#include "nodeviewscene.h"
|
||||
#include "ui/colorcoding.h"
|
||||
@@ -68,6 +69,8 @@ NodeViewItem::NodeViewItem(Node *node, const QString &input, int element,
|
||||
&NodeViewItem::NodeAppearanceChanged);
|
||||
connect(node_, &Node::ColorChanged, this,
|
||||
&NodeViewItem::NodeAppearanceChanged);
|
||||
connect(node_, &Node::MessageCountChanged, this,
|
||||
&NodeViewItem::NodeAppearanceChanged);
|
||||
|
||||
if (IsOutputItem()) {
|
||||
connect(node_, &Node::InputAdded, this,
|
||||
@@ -428,6 +431,41 @@ void NodeViewItem::paint(QPainter *painter,
|
||||
arrow_size);
|
||||
}
|
||||
|
||||
if (IsOutputItem()) {
|
||||
auto *instance = node_->getPluginInstance();
|
||||
auto *olive_instance =
|
||||
dynamic_cast<plugin::OlivePluginInstance *>(instance);
|
||||
int message_count =
|
||||
olive_instance ? olive_instance->persistentMessageCount() : 0;
|
||||
|
||||
if (message_count > 0) {
|
||||
QString badge_text = QString::number(message_count);
|
||||
QFont badge_font = painter->font();
|
||||
badge_font.setPointSizeF(badge_font.pointSizeF() * 0.7);
|
||||
painter->setFont(badge_font);
|
||||
|
||||
QFontMetrics badge_metrics(badge_font);
|
||||
int text_width = badge_metrics.horizontalAdvance(badge_text);
|
||||
int text_height = badge_metrics.height();
|
||||
int pad = text_height / 3;
|
||||
int badge_width = qMax(text_width + pad * 2, text_height + pad);
|
||||
int badge_height = text_height + pad;
|
||||
|
||||
QRectF badge_rect(
|
||||
single_unit_rect.right() - badge_width - 4,
|
||||
single_unit_rect.top() + 4,
|
||||
badge_width, badge_height);
|
||||
|
||||
painter->setPen(Qt::NoPen);
|
||||
painter->setBrush(QColor(220, 50, 47));
|
||||
painter->drawRoundedRect(badge_rect, badge_height / 2,
|
||||
badge_height / 2);
|
||||
|
||||
painter->setPen(Qt::white);
|
||||
painter->drawText(badge_rect, Qt::AlignCenter, badge_text);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw final border (output only)
|
||||
if (IsOutputItem()) {
|
||||
QPen border_pen;
|
||||
|
||||
+1
-1
Submodule ext/KDDockWidgets updated: 6b0ef44eb1...d24ba267da
@@ -14,7 +14,12 @@
|
||||
#include "ofxhParam.h"
|
||||
#include "ofxhMemory.h"
|
||||
#include "ofxhInteract.h"
|
||||
|
||||
#include <memory>
|
||||
namespace olive {
|
||||
namespace plugin {
|
||||
class PluginNode;
|
||||
}
|
||||
}
|
||||
#ifdef _MSC_VER
|
||||
//Use visual studio extension
|
||||
#define __PRETTY_FUNCTION__ __FUNCSIG__
|
||||
@@ -56,7 +61,7 @@ namespace OFX {
|
||||
/// \arg plugin - the plugin being created
|
||||
/// \arg desc - the descriptor for that plugin
|
||||
/// \arg context - the context to be created in
|
||||
virtual Instance* newInstance(void* clientData,
|
||||
virtual Instance* newInstance(std::shared_ptr<olive::plugin::PluginNode> clientData,
|
||||
ImageEffectPlugin* plugin,
|
||||
Descriptor& desc,
|
||||
const std::string& context) = 0;
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#ifndef OFXH_IMAGE_EFFECT_API_H
|
||||
#define OFXH_IMAGE_EFFECT_API_H
|
||||
|
||||
|
||||
|
||||
#include "ofxhPluginAPICache.h"
|
||||
#include "ofxhPluginCache.h"
|
||||
#include <string>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "ofxhBinary.h"
|
||||
#include "ofxhPropertySuite.h"
|
||||
#include "ofxhClip.h"
|
||||
#include "ofxhPropertySuite.h"
|
||||
#include "ofxhImageEffect.h"
|
||||
#ifdef OFX_SUPPORTS_OPENGLRENDER
|
||||
#include "ofxGPURender.h"
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
// ofx host
|
||||
#include "ofxhBinary.h"
|
||||
#include "ofxhPropertySuite.h"
|
||||
#include "ofxhClip.h"
|
||||
#include "ofxhParam.h"
|
||||
#include "ofxhMemory.h"
|
||||
|
||||
Reference in New Issue
Block a user