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:
|
The original author compiled following binaries:
|
||||||
- [0.1.0 alpha](https://github.com/olive-editor/olive/releases/tag/0.1.0)
|
- [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)
|
- [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 KeyframeTimeChanged(NodeKeyframe *key);
|
||||||
|
|
||||||
|
void MessageCountChanged();
|
||||||
|
|
||||||
void KeyframeTypeChanged(NodeKeyframe *key);
|
void KeyframeTypeChanged(NodeKeyframe *key);
|
||||||
|
|
||||||
void KeyframeValueChanged(NodeKeyframe *key);
|
void KeyframeValueChanged(NodeKeyframe *key);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ olive::plugin::PluginNode::PluginNode(
|
|||||||
OFX::Host::ImageEffect::Instance *plugin)
|
OFX::Host::ImageEffect::Instance *plugin)
|
||||||
{
|
{
|
||||||
plugin_instance_=plugin;
|
plugin_instance_=plugin;
|
||||||
|
bool has_texture_input = false;
|
||||||
|
|
||||||
auto params=plugin_instance_->getParams();
|
auto params=plugin_instance_->getParams();
|
||||||
for (auto param: params) {
|
for (auto param: params) {
|
||||||
@@ -70,6 +71,17 @@ olive::plugin::PluginNode::PluginNode(
|
|||||||
AddInput(param.second->getName().data(), type);
|
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
|
QString olive::plugin::PluginNode::Name() const
|
||||||
{
|
{
|
||||||
@@ -94,9 +106,19 @@ void olive::plugin::PluginNode::Value(const NodeValueRow &value,
|
|||||||
const NodeGlobals &globals,
|
const NodeGlobals &globals,
|
||||||
NodeValueTable *table) const
|
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_) {
|
if (tex && plugin_instance_) {
|
||||||
PluginJob job(plugin_instance_, value);
|
PluginJob job(plugin_instance_, this, value);
|
||||||
table->Push(NodeValue::kTexture, tex->toJob(job), this);
|
table->Push(NodeValue::kTexture, tex->toJob(job), this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#define PLUGIN_NODES_H
|
#define PLUGIN_NODES_H
|
||||||
#include "ofxhImageEffectAPI.h"
|
#include "ofxhImageEffectAPI.h"
|
||||||
#include "ofxhPluginCache.h"
|
#include "ofxhPluginCache.h"
|
||||||
|
#include "ofxImageEffect.h"
|
||||||
#include "node/node.h"
|
#include "node/node.h"
|
||||||
|
|
||||||
namespace olive
|
namespace olive
|
||||||
|
|||||||
@@ -382,7 +382,7 @@ TexturePtr NodeTraverser::ProcessVideoCacheJob(const CacheJob *val)
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
TexturePtr NodeTraverser::ProcessPluginJob(plugin::PluginJob *job)
|
TexturePtr NodeTraverser::ProcessPluginJob(TexturePtr texture, TexturePtr destination, const Node *node,)
|
||||||
{
|
{
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
@@ -496,7 +496,13 @@ void NodeTraverser::ResolveJobs(NodeValue &val)
|
|||||||
val.set_value(tex);
|
val.set_value(tex);
|
||||||
}
|
}
|
||||||
else if (plugin::PluginJob* plugin_job=dynamic_cast<plugin::PluginJob*>(base_job)) {
|
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
|
// Cache resolved value
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ protected:
|
|||||||
return SampleBuffer();
|
return SampleBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual TexturePtr ProcessPluginJob(plugin::PluginJob *job);
|
virtual TexturePtr ProcessPluginJob(TexturePtr texture, TexturePtr destination, const Node *node);
|
||||||
SampleBuffer CreateSampleBuffer(const AudioParams ¶ms,
|
SampleBuffer CreateSampleBuffer(const AudioParams ¶ms,
|
||||||
const rational &length)
|
const rational &length)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,4 +7,6 @@ target_sources(libolive-editor PRIVATE
|
|||||||
OliveClip.h
|
OliveClip.h
|
||||||
paraminstance.cpp
|
paraminstance.cpp
|
||||||
paraminstance.h
|
paraminstance.h
|
||||||
|
image.cpp
|
||||||
|
image.h
|
||||||
)
|
)
|
||||||
+147
-12
@@ -24,6 +24,18 @@
|
|||||||
#include "OliveClip.h"
|
#include "OliveClip.h"
|
||||||
|
|
||||||
#include "common/Current.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
|
const std::string &olive::plugin::OliveClipInstance::getUnmappedBitDepth() const
|
||||||
{
|
{
|
||||||
switch (params_.format()) {
|
switch (params_.format()) {
|
||||||
@@ -65,9 +77,7 @@ const std::string &olive::plugin::OliveClipInstance::getPremult() const
|
|||||||
}
|
}
|
||||||
double olive::plugin::OliveClipInstance::getAspectRatio() const
|
double olive::plugin::OliveClipInstance::getAspectRatio() const
|
||||||
{
|
{
|
||||||
return params_
|
return params_.pixel_aspect_ratio().toDouble();
|
||||||
.pixel_aspect_ratio()
|
|
||||||
.toDouble();
|
|
||||||
}
|
}
|
||||||
double olive::plugin::OliveClipInstance::getFrameRate() const
|
double olive::plugin::OliveClipInstance::getFrameRate() const
|
||||||
{
|
{
|
||||||
@@ -76,13 +86,9 @@ double olive::plugin::OliveClipInstance::getFrameRate() const
|
|||||||
void olive::plugin::OliveClipInstance::getFrameRange(double &startFrame,
|
void olive::plugin::OliveClipInstance::getFrameRange(double &startFrame,
|
||||||
double &endFrame) const
|
double &endFrame) const
|
||||||
{
|
{
|
||||||
startFrame =
|
startFrame = params_.frame_rate().toDouble() * params_.start_time();
|
||||||
params_.frame_rate().toDouble() *
|
|
||||||
params_.start_time();
|
|
||||||
endFrame =
|
endFrame =
|
||||||
startFrame +
|
startFrame + params_.frame_rate().toDouble() * params_.duration();
|
||||||
params_.frame_rate().toDouble() *
|
|
||||||
params_.duration();
|
|
||||||
}
|
}
|
||||||
const std::string &olive::plugin::OliveClipInstance::getFieldOrder() const
|
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
|
bool olive::plugin::OliveClipInstance::getConnected() const
|
||||||
{
|
{
|
||||||
return params_.format() ==
|
return params_.format() == PixelFormat::INVALID;
|
||||||
PixelFormat::INVALID;
|
|
||||||
}
|
}
|
||||||
double olive::plugin::OliveClipInstance::getUnmappedFrameRate() const
|
double olive::plugin::OliveClipInstance::getUnmappedFrameRate() const
|
||||||
{
|
{
|
||||||
@@ -118,7 +123,54 @@ OFX::Host::ImageEffect::Image *
|
|||||||
olive::plugin::OliveClipInstance::getImage(OfxTime time,
|
olive::plugin::OliveClipInstance::getImage(OfxTime time,
|
||||||
const OfxRectD *optionalBounds)
|
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
|
OfxRectD
|
||||||
olive::plugin::OliveClipInstance::getRegionOfDefinition(OfxTime time) const
|
olive::plugin::OliveClipInstance::getRegionOfDefinition(OfxTime time) const
|
||||||
@@ -143,3 +195,86 @@ void olive::plugin::OliveClipInstance::setDefaultRegionOfDefinition(
|
|||||||
{
|
{
|
||||||
defaultRegionOfDefinitions_ = regionOfDefinition;
|
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
|
#ifndef OLIVECLIP_H
|
||||||
#define OLIVECLIP_H
|
#define OLIVECLIP_H
|
||||||
|
#include "image.h"
|
||||||
|
#include "ofxCore.h"
|
||||||
#include "ofxhClip.h"
|
#include "ofxhClip.h"
|
||||||
|
#include "render/texture.h"
|
||||||
#include "render/videoparams.h"
|
#include "render/videoparams.h"
|
||||||
|
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
|
#include <memory>
|
||||||
namespace olive
|
namespace olive
|
||||||
{
|
{
|
||||||
namespace plugin
|
namespace plugin
|
||||||
@@ -34,14 +38,15 @@ namespace plugin
|
|||||||
class OliveClipInstance: public OFX::Host::ImageEffect::ClipInstance {
|
class OliveClipInstance: public OFX::Host::ImageEffect::ClipInstance {
|
||||||
public:
|
public:
|
||||||
OliveClipInstance(OFX::Host::ImageEffect::Instance* effectInstance,
|
OliveClipInstance(OFX::Host::ImageEffect::Instance* effectInstance,
|
||||||
OFX::Host::ImageEffect::ClipDescriptor& desc,VideoParams params)
|
OFX::Host::ImageEffect::ClipDescriptor& desc,VideoParams ¶ms)
|
||||||
: ClipInstance(effectInstance, desc)
|
: ClipInstance(effectInstance, desc)
|
||||||
|
, name_(desc.getName())
|
||||||
{
|
{
|
||||||
params_ = params;
|
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;
|
const std::string &getUnmappedBitDepth() const override;
|
||||||
@@ -59,11 +64,13 @@ public:
|
|||||||
OfxRectD getRegionOfDefinition(OfxTime time) const override;
|
OfxRectD getRegionOfDefinition(OfxTime time) const override;
|
||||||
|
|
||||||
void setRegionOfDefinition(OfxRectD regionOfDefinition, OfxTime time);
|
void setRegionOfDefinition(OfxRectD regionOfDefinition, OfxTime time);
|
||||||
void olive::plugin::OliveClipInstance::setDefaultRegionOfDefinition(
|
void setDefaultRegionOfDefinition(OfxRectD regionOfDefinition);
|
||||||
OfxRectD regionOfDefinition);
|
|
||||||
# ifdef OFX_SUPPORTS_OPENGLRENDER
|
# ifdef OFX_SUPPORTS_OPENGLRENDER
|
||||||
OFX::Host::ImageEffect::Texture* loadTexture(OfxTime time, const char *format, const OfxRectD *optionalBounds) { return NULL; };
|
OFX::Host::ImageEffect::Texture* loadTexture(OfxTime time, const char *format, const OfxRectD *optionalBounds) { return NULL; };
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
|
void setInputTexture(TexturePtr texture, OfxTime time);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
VideoParams params_;
|
VideoParams params_;
|
||||||
|
|
||||||
@@ -71,7 +78,8 @@ private:
|
|||||||
|
|
||||||
OfxRectD defaultRegionOfDefinitions_;
|
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 "node/project.h"
|
||||||
#include "ofxhImageEffect.h"
|
#include "ofxhImageEffect.h"
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
#include <ofxhPluginCache.h>
|
#include <ofxhPluginCache.h>
|
||||||
#include <ofxhBinary.h>
|
#include <ofxhBinary.h>
|
||||||
|
|
||||||
@@ -28,6 +30,8 @@
|
|||||||
|
|
||||||
#include "OlivePluginInstance.h"
|
#include "OlivePluginInstance.h"
|
||||||
#include "common/Current.h"
|
#include "common/Current.h"
|
||||||
|
#include "ofxMessage.h"
|
||||||
|
#include <QMessageBox>
|
||||||
using namespace OFX::Host;
|
using namespace OFX::Host;
|
||||||
using namespace olive::plugin;
|
using namespace olive::plugin;
|
||||||
|
|
||||||
@@ -93,15 +97,76 @@ OliveHost::makeDescriptor(const std::string &bundlePath,
|
|||||||
return desc;
|
return desc;
|
||||||
}
|
}
|
||||||
|
|
||||||
OFX::Host::ImageEffect::Instance* OliveHost::newInstance(void* clientData,
|
ImageEffect::Instance* OliveHost::newInstance(std::shared_ptr<PluginNode> clientData,
|
||||||
OFX::Host::ImageEffect::ImageEffectPlugin* plugin,
|
ImageEffect::ImageEffectPlugin* plugin,
|
||||||
OFX::Host::ImageEffect::Descriptor& desc,
|
ImageEffect::Descriptor& desc,
|
||||||
const std::string& context){
|
const std::string& context){
|
||||||
auto* instance = new OlivePluginInstance(
|
auto* instance = new OlivePluginInstance(
|
||||||
plugin, desc, context, Current::getInstance().interactive());
|
plugin, desc, context, Current::getInstance().interactive());
|
||||||
if (clientData) {
|
if (clientData) {
|
||||||
instance->setNode(static_cast<olive::plugin::PluginNode*>(clientData));
|
instance->setNode(clientData);
|
||||||
}
|
}
|
||||||
instances_.append(instance);
|
instances_.append(instance);
|
||||||
return 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
|
#ifndef OLIVE_HOST_H
|
||||||
#define OLIVE_HOST_H
|
#define OLIVE_HOST_H
|
||||||
|
#include "node/plugins/Plugin.h"
|
||||||
#include "ofxhHost.h"
|
#include "ofxhHost.h"
|
||||||
#include "ofxhImageEffectAPI.h"
|
#include "ofxhImageEffectAPI.h"
|
||||||
#include "ofxCore.h"
|
#include "ofxCore.h"
|
||||||
@@ -32,6 +33,15 @@
|
|||||||
#include <qlist.h>
|
#include <qlist.h>
|
||||||
namespace olive {
|
namespace olive {
|
||||||
namespace plugin {
|
namespace plugin {
|
||||||
|
enum class HostMessageType{
|
||||||
|
Error,
|
||||||
|
Warning,
|
||||||
|
Message
|
||||||
|
};
|
||||||
|
struct HostPersistentMessage{
|
||||||
|
HostMessageType type;
|
||||||
|
QString message;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
void loadPlugins(QString path);
|
void loadPlugins(QString path);
|
||||||
@@ -47,7 +57,7 @@ public:
|
|||||||
return true;
|
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::ImageEffectPlugin* plugin,
|
||||||
OFX::Host::ImageEffect::Descriptor& desc,
|
OFX::Host::ImageEffect::Descriptor& desc,
|
||||||
const std::string& context) override;
|
const std::string& context) override;
|
||||||
@@ -63,10 +73,27 @@ public:
|
|||||||
OFX::Host::ImageEffect::Descriptor *makeDescriptor(
|
OFX::Host::ImageEffect::Descriptor *makeDescriptor(
|
||||||
const std::string &bundlePath,
|
const std::string &bundlePath,
|
||||||
OFX::Host::ImageEffect::ImageEffectPlugin *plugin) override;
|
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:
|
private:
|
||||||
QList<OFX::Host::ImageEffect::Descriptor*> descriptors_;
|
QList<OFX::Host::ImageEffect::Descriptor*> descriptors_;
|
||||||
QList<OFX::Host::ImageEffect::Instance*> instances_;
|
QList<OFX::Host::ImageEffect::Instance*> instances_;
|
||||||
|
QList<HostPersistentMessage> persistent_messages_;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,12 +94,18 @@ OfxStatus OlivePluginInstance::setPersistentMessage(const char *type, const char
|
|||||||
} else {
|
} else {
|
||||||
return kOfxStatFailed;
|
return kOfxStatFailed;
|
||||||
}
|
}
|
||||||
|
if (node_) {
|
||||||
|
emit node_->MessageCountChanged();
|
||||||
|
}
|
||||||
return kOfxStatOK;
|
return kOfxStatOK;
|
||||||
}
|
}
|
||||||
OfxStatus OlivePluginInstance::clearPersistentMessage()
|
OfxStatus OlivePluginInstance::clearPersistentMessage()
|
||||||
{
|
{
|
||||||
persistentErrors_.clear();
|
persistentErrors_.clear();
|
||||||
// TODO: tell the shell to remove message.
|
// TODO: tell the shell to remove message.
|
||||||
|
if (node_) {
|
||||||
|
emit node_->MessageCountChanged();
|
||||||
|
}
|
||||||
return kOfxStatOK;
|
return kOfxStatOK;
|
||||||
}
|
}
|
||||||
void OlivePluginInstance::getProjectSize(double &xSize, double &ySize) const
|
void OlivePluginInstance::getProjectSize(double &xSize, double &ySize) const
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
#include "ofxImageEffect.h"
|
#include "ofxImageEffect.h"
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include "ofxhImageEffect.h"
|
#include "ofxhImageEffect.h"
|
||||||
|
#include "node/plugins/Plugin.h"
|
||||||
#include "render/videoparams.h"
|
#include "render/videoparams.h"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
@@ -71,7 +72,7 @@ public:
|
|||||||
{
|
{
|
||||||
this->params_=params;
|
this->params_=params;
|
||||||
}
|
}
|
||||||
void setNode(PluginNode* node)
|
void setNode(std::shared_ptr<PluginNode> node)
|
||||||
{
|
{
|
||||||
node_ = node;
|
node_ = node;
|
||||||
}
|
}
|
||||||
@@ -91,6 +92,14 @@ public:
|
|||||||
va_list args) override;
|
va_list args) override;
|
||||||
|
|
||||||
OfxStatus clearPersistentMessage() 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 getProjectSize(double& xSize, double& ySize) const override;
|
||||||
void getProjectOffset(double& xOffset, double& yOffset) 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
|
/// get the first and last times available on the effect's timeline
|
||||||
virtual void timeLineGetBounds(double &t1, double &t2);
|
virtual void timeLineGetBounds(double &t1, double &t2);
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QList<PersistentErrors> persistentErrors_;
|
QList<PersistentErrors> persistentErrors_;
|
||||||
VideoParams params_;
|
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{
|
class PluginJob :public AcceleratedJob{
|
||||||
public:
|
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:
|
private:
|
||||||
OFX::Host::ImageEffect::Instance *pluginInstance=nullptr;
|
const OFX::Host::ImageEffect::Instance *pluginInstance_=nullptr;
|
||||||
|
|
||||||
QHash<OfxTime, QHash<QString, std::any>> paramsOnTime;
|
QHash<OfxTime, QHash<QString, std::any>> paramsOnTime;
|
||||||
|
|
||||||
QHash<QString, std::any> params;
|
QHash<QString, std::any> params;
|
||||||
|
|
||||||
|
const PluginNode *node_=nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // plugin
|
} // plugin
|
||||||
|
|||||||
@@ -20,16 +20,293 @@
|
|||||||
//
|
//
|
||||||
// Created by mikesolar on 25-10-19.
|
// Created by mikesolar on 25-10-19.
|
||||||
//
|
//
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <qtypes.h>
|
||||||
#define GL_PREAMBLE //QMutexLocker __l(&global_opengl_mutex);
|
#define GL_PREAMBLE //QMutexLocker __l(&global_opengl_mutex);
|
||||||
#include "pluginrenderer.h"
|
#include "pluginrenderer.h"
|
||||||
|
#include "pluginSupport/OliveClip.h"
|
||||||
#include "pluginSupport/OlivePluginInstance.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,
|
static AVPixelFormat GetOfxAVPixelFormat(const OFX::Host::ImageEffect::Image &image,
|
||||||
olive::AcceleratedJob &job,
|
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::Texture *destination,
|
||||||
olive::VideoParams destination_params,
|
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){};
|
PluginRenderer(QObject *parent=nullptr):OpenGLRenderer(parent){};
|
||||||
virtual ~PluginRenderer() override{};
|
virtual ~PluginRenderer() override{};
|
||||||
protected:
|
protected:
|
||||||
virtual void Blit(QVariant shader, olive::AcceleratedJob& job,
|
virtual void RenderPlugin(TexturePtr src, olive::plugin::PluginJob& job,
|
||||||
olive::Texture *destination,
|
olive::Texture *destination,
|
||||||
olive::VideoParams destination_params,
|
olive::VideoParams destination_params,
|
||||||
bool clear_destination) override;
|
bool clear_destination, bool interactive);
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,83 +175,7 @@ void RenderProcessor::Run()
|
|||||||
= node->getPlugin();
|
= node->getPlugin();
|
||||||
std::unique_ptr<OFX::Host::ImageEffect::Instance> instance(plugin->createInstance(kOfxImageEffectContextFilter, NULL));
|
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();
|
locker.unlock();
|
||||||
|
|
||||||
// Run shader
|
// Run shader
|
||||||
render_ctx_->BlitToTexture(shader, *job, destination.get());
|
render_ctx_->BlitToTexture(shader, const_cast<ShaderJob&>(*job), destination.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderProcessor::ProcessSamples(SampleBuffer &destination,
|
void RenderProcessor::ProcessSamples(SampleBuffer &destination,
|
||||||
|
|||||||
@@ -154,6 +154,9 @@ public:
|
|||||||
{
|
{
|
||||||
frame_=ptr;
|
frame_=ptr;
|
||||||
}
|
}
|
||||||
|
AVFramePtr frame(){
|
||||||
|
return frame_;
|
||||||
|
}
|
||||||
private:
|
private:
|
||||||
Renderer *renderer_;
|
Renderer *renderer_;
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
#include "node/group/group.h"
|
#include "node/group/group.h"
|
||||||
#include "node/nodeundo.h"
|
#include "node/nodeundo.h"
|
||||||
#include "node/project/sequence/sequence.h"
|
#include "node/project/sequence/sequence.h"
|
||||||
|
#include "pluginSupport/OlivePluginInstance.h"
|
||||||
|
|
||||||
namespace olive
|
namespace olive
|
||||||
{
|
{
|
||||||
@@ -49,6 +50,8 @@ NodeParamViewItem::NodeParamViewItem(
|
|||||||
QWidget *parent)
|
QWidget *parent)
|
||||||
: super(parent)
|
: super(parent)
|
||||||
, body_(nullptr)
|
, body_(nullptr)
|
||||||
|
, message_label_(nullptr)
|
||||||
|
, message_container_(nullptr)
|
||||||
, node_(node)
|
, node_(node)
|
||||||
, create_checkboxes_(create_checkboxes)
|
, create_checkboxes_(create_checkboxes)
|
||||||
, ctx_(nullptr)
|
, ctx_(nullptr)
|
||||||
@@ -62,6 +65,8 @@ NodeParamViewItem::NodeParamViewItem(
|
|||||||
connect(node_, &Node::LabelChanged, this, &NodeParamViewItem::Retranslate);
|
connect(node_, &Node::LabelChanged, this, &NodeParamViewItem::Retranslate);
|
||||||
connect(node_, &Node::InputArraySizeChanged, this,
|
connect(node_, &Node::InputArraySizeChanged, this,
|
||||||
&NodeParamViewItem::InputArraySizeChanged);
|
&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
|
// 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.
|
// way of doing this, but "fine" for now.
|
||||||
@@ -93,6 +98,12 @@ void NodeParamViewItem::RecreateBody()
|
|||||||
body_->setParent(nullptr);
|
body_->setParent(nullptr);
|
||||||
body_->deleteLater();
|
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);
|
body_ = new NodeParamViewItemBody(node_, create_checkboxes_, this);
|
||||||
connect(body_, &NodeParamViewItemBody::RequestSelectNode, this,
|
connect(body_, &NodeParamViewItemBody::RequestSelectNode, this,
|
||||||
@@ -106,7 +117,57 @@ void NodeParamViewItem::RecreateBody()
|
|||||||
body_->Retranslate();
|
body_->Retranslate();
|
||||||
body_->SetTimebase(timebase_);
|
body_->SetTimebase(timebase_);
|
||||||
body_->SetTimeTarget(time_target_);
|
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
|
int NodeParamViewItem::GetElementY(const NodeInput &c) const
|
||||||
|
|||||||
@@ -232,6 +232,8 @@ protected slots:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
NodeParamViewItemBody *body_;
|
NodeParamViewItemBody *body_;
|
||||||
|
QLabel *message_label_;
|
||||||
|
QWidget *message_container_;
|
||||||
|
|
||||||
Node *node_;
|
Node *node_;
|
||||||
|
|
||||||
@@ -247,6 +249,7 @@ private:
|
|||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void RecreateBody();
|
void RecreateBody();
|
||||||
|
void UpdateMessagePanel();
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
#include "node/group/group.h"
|
#include "node/group/group.h"
|
||||||
#include "node/nodeundo.h"
|
#include "node/nodeundo.h"
|
||||||
#include "node/project/serializer/serializer.h"
|
#include "node/project/serializer/serializer.h"
|
||||||
|
#include "panel/panelmanager.h"
|
||||||
#include "node/traverser.h"
|
#include "node/traverser.h"
|
||||||
#include "ui/icons/icons.h"
|
#include "ui/icons/icons.h"
|
||||||
#include "widget/menu/menushared.h"
|
#include "widget/menu/menushared.h"
|
||||||
@@ -639,6 +640,15 @@ void NodeView::mouseDoubleClickEvent(QMouseEvent *event)
|
|||||||
dynamic_cast<NodeViewItem *>(itemAt(event->pos()));
|
dynamic_cast<NodeViewItem *>(itemAt(event->pos()));
|
||||||
if (item_at_cursor) {
|
if (item_at_cursor) {
|
||||||
item_at_cursor->ToggleExpanded();
|
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 "config/config.h"
|
||||||
#include "core.h"
|
#include "core.h"
|
||||||
#include "node/nodeundo.h"
|
#include "node/nodeundo.h"
|
||||||
|
#include "pluginSupport/OlivePluginInstance.h"
|
||||||
#include "nodeview.h"
|
#include "nodeview.h"
|
||||||
#include "nodeviewscene.h"
|
#include "nodeviewscene.h"
|
||||||
#include "ui/colorcoding.h"
|
#include "ui/colorcoding.h"
|
||||||
@@ -68,6 +69,8 @@ NodeViewItem::NodeViewItem(Node *node, const QString &input, int element,
|
|||||||
&NodeViewItem::NodeAppearanceChanged);
|
&NodeViewItem::NodeAppearanceChanged);
|
||||||
connect(node_, &Node::ColorChanged, this,
|
connect(node_, &Node::ColorChanged, this,
|
||||||
&NodeViewItem::NodeAppearanceChanged);
|
&NodeViewItem::NodeAppearanceChanged);
|
||||||
|
connect(node_, &Node::MessageCountChanged, this,
|
||||||
|
&NodeViewItem::NodeAppearanceChanged);
|
||||||
|
|
||||||
if (IsOutputItem()) {
|
if (IsOutputItem()) {
|
||||||
connect(node_, &Node::InputAdded, this,
|
connect(node_, &Node::InputAdded, this,
|
||||||
@@ -428,6 +431,41 @@ void NodeViewItem::paint(QPainter *painter,
|
|||||||
arrow_size);
|
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)
|
// Draw final border (output only)
|
||||||
if (IsOutputItem()) {
|
if (IsOutputItem()) {
|
||||||
QPen border_pen;
|
QPen border_pen;
|
||||||
|
|||||||
+1
-1
Submodule ext/KDDockWidgets updated: 6b0ef44eb1...d24ba267da
@@ -14,7 +14,12 @@
|
|||||||
#include "ofxhParam.h"
|
#include "ofxhParam.h"
|
||||||
#include "ofxhMemory.h"
|
#include "ofxhMemory.h"
|
||||||
#include "ofxhInteract.h"
|
#include "ofxhInteract.h"
|
||||||
|
#include <memory>
|
||||||
|
namespace olive {
|
||||||
|
namespace plugin {
|
||||||
|
class PluginNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
//Use visual studio extension
|
//Use visual studio extension
|
||||||
#define __PRETTY_FUNCTION__ __FUNCSIG__
|
#define __PRETTY_FUNCTION__ __FUNCSIG__
|
||||||
@@ -56,7 +61,7 @@ namespace OFX {
|
|||||||
/// \arg plugin - the plugin being created
|
/// \arg plugin - the plugin being created
|
||||||
/// \arg desc - the descriptor for that plugin
|
/// \arg desc - the descriptor for that plugin
|
||||||
/// \arg context - the context to be created in
|
/// \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,
|
ImageEffectPlugin* plugin,
|
||||||
Descriptor& desc,
|
Descriptor& desc,
|
||||||
const std::string& context) = 0;
|
const std::string& context) = 0;
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
#ifndef OFXH_IMAGE_EFFECT_API_H
|
#ifndef OFXH_IMAGE_EFFECT_API_H
|
||||||
#define OFXH_IMAGE_EFFECT_API_H
|
#define OFXH_IMAGE_EFFECT_API_H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#include "ofxhPluginAPICache.h"
|
#include "ofxhPluginAPICache.h"
|
||||||
#include "ofxhPluginCache.h"
|
#include "ofxhPluginCache.h"
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "ofxhBinary.h"
|
#include "ofxhBinary.h"
|
||||||
#include "ofxhPropertySuite.h"
|
#include "ofxhPropertySuite.h"
|
||||||
#include "ofxhClip.h"
|
#include "ofxhClip.h"
|
||||||
|
#include "ofxhPropertySuite.h"
|
||||||
#include "ofxhImageEffect.h"
|
#include "ofxhImageEffect.h"
|
||||||
#ifdef OFX_SUPPORTS_OPENGLRENDER
|
#ifdef OFX_SUPPORTS_OPENGLRENDER
|
||||||
#include "ofxGPURender.h"
|
#include "ofxGPURender.h"
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
// ofx host
|
// ofx host
|
||||||
#include "ofxhBinary.h"
|
#include "ofxhBinary.h"
|
||||||
#include "ofxhPropertySuite.h"
|
|
||||||
#include "ofxhClip.h"
|
#include "ofxhClip.h"
|
||||||
#include "ofxhParam.h"
|
#include "ofxhParam.h"
|
||||||
#include "ofxhMemory.h"
|
#include "ofxhMemory.h"
|
||||||
|
|||||||
Reference in New Issue
Block a user