/* * Oak Video Editor - Non-Linear Video Editor * Copyright (C) 2025 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 . * */ // // Created by mikesolar on 25-10-1. // #include "OliveClip.h" #include "common/Current.h" #include "common/ffmpegutils.h" #include "ofxCore.h" #include "ofxhClip.h" #include "pluginSupport/image.h" #include #include #include #include #ifdef OFX_SUPPORTS_OPENGLRENDER #include #endif extern "C" { #include } namespace { const std::string kBitDepthNoneStr(kOfxBitDepthNone); const std::string kBitDepthByteStr(kOfxBitDepthByte); const std::string kBitDepthShortStr(kOfxBitDepthShort); const std::string kBitDepthHalfStr(kOfxBitDepthHalf); const std::string kBitDepthFloatStr(kOfxBitDepthFloat); const std::string kImageComponentNoneStr(kOfxImageComponentNone); const std::string kImageComponentAlphaStr(kOfxImageComponentAlpha); const std::string kImageComponentRGBStr(kOfxImageComponentRGB); const std::string kImageComponentRGBAStr(kOfxImageComponentRGBA); const std::string kImagePremultStr(kOfxImagePreMultiplied); const std::string kImageUnPremultStr(kOfxImageUnPreMultiplied); const std::string kImageFieldNoneStr(kOfxImageFieldNone); const std::string kImageFieldUpperStr(kOfxImageFieldUpper); const std::string kImageFieldLowerStr(kOfxImageFieldLower); } const std::string &olive::plugin::OliveClipInstance::getUnmappedBitDepth() const { switch (params_.format()) { case PixelFormat::INVALID: return kBitDepthNoneStr; case PixelFormat::U8: return kBitDepthByteStr; case PixelFormat::U16: return kBitDepthShortStr; case PixelFormat::F16: return kBitDepthHalfStr; case PixelFormat::F32: return kBitDepthFloatStr; default: return kBitDepthNoneStr; } } const std::string & olive::plugin::OliveClipInstance::getUnmappedComponents() const { switch (params_.channel_count()) { case 1: return kImageComponentAlphaStr; case 3: return kImageComponentRGBStr; case 4: return kImageComponentRGBAStr; default: return kImageComponentNoneStr; } } const std::string &olive::plugin::OliveClipInstance::getPremult() const { if (params_.premultiplied_alpha()) { return kImagePremultStr; } else { return kImageUnPremultStr; } } double olive::plugin::OliveClipInstance::getAspectRatio() const { return params_.pixel_aspect_ratio().toDouble(); } double olive::plugin::OliveClipInstance::getFrameRate() const { return params_.frame_rate().toDouble(); } void olive::plugin::OliveClipInstance::getFrameRange(double &startFrame, double &endFrame) const { startFrame = params_.frame_rate().toDouble() * params_.start_time(); endFrame = startFrame + params_.frame_rate().toDouble() * params_.duration(); } const std::string &olive::plugin::OliveClipInstance::getFieldOrder() const { switch (params_.interlacing()) { case VideoParams::kInterlaceNone: return kImageFieldNoneStr; case VideoParams::kInterlacedTopFirst: return kImageFieldUpperStr; case VideoParams::kInterlacedBottomFirst: return kImageFieldLowerStr; } return kImageFieldNoneStr; } bool olive::plugin::OliveClipInstance::getConnected() const { if (name_ == kOfxImageEffectOutputClipName) { return true; } #ifdef OFX_SUPPORTS_OPENGLRENDER if (!input_textures_.isEmpty()) { return true; } #endif return !images_.isEmpty(); } double olive::plugin::OliveClipInstance::getUnmappedFrameRate() const { return getFrameRate(); } void olive::plugin::OliveClipInstance::getUnmappedFrameRange( double &startFrame, double &endFrame) const { getFrameRange(startFrame, endFrame); } bool olive::plugin::OliveClipInstance::getContinuousSamples() const { return false; } OFX::Host::ImageEffect::Image * olive::plugin::OliveClipInstance::getImage(OfxTime time, const OfxRectD *optionalBounds) { OfxRectD rod_d = getRegionOfDefinition(time); OfxRectI rod = { static_cast(std::floor(rod_d.x1)), static_cast(std::floor(rod_d.y1)), static_cast(std::ceil(rod_d.x2)), static_cast(std::ceil(rod_d.y2)) }; OfxRectI bounds = rod; if (optionalBounds) { bounds.x1 = static_cast(std::floor(optionalBounds->x1)); bounds.y1 = static_cast(std::floor(optionalBounds->y1)); bounds.x2 = static_cast(std::ceil(optionalBounds->x2)); bounds.y2 = static_cast(std::ceil(optionalBounds->y2)); } // Clamp bounds to ROD to keep host/plugin coords consistent. bounds.x1 = std::max(bounds.x1, rod.x1); bounds.y1 = std::max(bounds.y1, rod.y1); bounds.x2 = std::min(bounds.x2, rod.x2); bounds.y2 = std::min(bounds.y2, rod.y2); if (name_ == "Output") { if (!images_.contains(time)) { // make a new ref counted image images_.insert(time, std::make_shared(*const_cast(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 = 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; } } std::shared_ptr olive::plugin::OliveClipInstance::getOutputImage(OfxTime time) { if (images_.contains(time)) { return images_.value(time); } OfxRectD rod_d = getRegionOfDefinition(time); OfxRectI rod = { static_cast(std::floor(rod_d.x1)), static_cast(std::floor(rod_d.y1)), static_cast(std::ceil(rod_d.x2)), static_cast(std::ceil(rod_d.y2)) }; OfxRectI bounds = rod; auto image = std::make_shared(*this, params_, bounds, rod, true); images_.insert(time, image); return image; } OfxRectD olive::plugin::OliveClipInstance::getRegionOfDefinition(OfxTime time) const { if (regionOfDefinitions_.contains(time)) { return regionOfDefinitions_[time]; } OfxRectD regionOfDefinition; regionOfDefinition.x1 = regionOfDefinition.y1 = 0; regionOfDefinition.x2 = params_.width(); regionOfDefinition.y2 = params_.height(); return regionOfDefinition; } void olive::plugin::OliveClipInstance::setRegionOfDefinition( OfxRectD regionOfDefinition, OfxTime time) { regionOfDefinitions_[time] = regionOfDefinition; } void olive::plugin::OliveClipInstance::setDefaultRegionOfDefinition( OfxRectD regionOfDefinition) { defaultRegionOfDefinitions_ = regionOfDefinition; } void olive::plugin::OliveClipInstance::setInputTexture(TexturePtr texture, OfxTime time){ if (!texture) { return; } VideoParams incoming = texture->params(); if (params_.format() != PixelFormat::INVALID && params_.channel_count() > 0) { incoming.set_format(params_.format()); incoming.set_channel_count(params_.channel_count()); incoming.set_premultiplied_alpha(params_.premultiplied_alpha()); } this->params_ = incoming; #ifdef OFX_SUPPORTS_OPENGLRENDER input_textures_.insert(time, texture); #endif AVFramePtr frame = texture->frame(); if (!frame || !frame->data[0]) { return; } 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(std::floor(rod_d.x1)), static_cast(std::floor(rod_d.y1)), static_cast(std::ceil(rod_d.x2)), static_cast(std::ceil(rod_d.y2)) }; std::shared_ptr image; if (images_.contains(time)) { image = images_.value(time); image->EnsureAllocatedFromParams(params_, bounds, regionOfDefinition, false); } else { image = std::make_shared(*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(frame->format), converted->width, converted->height, static_cast(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); } } void olive::plugin::OliveClipInstance::setOutputTexture(Texture *texture, OfxTime time) { #ifdef OFX_SUPPORTS_OPENGLRENDER if (!texture) { return; } output_textures_.insert(time, texture); #else (void)texture; (void)time; #endif } #ifdef OFX_SUPPORTS_OPENGLRENDER OFX::Host::ImageEffect::Texture * olive::plugin::OliveClipInstance::loadTexture(OfxTime time, const char *format, const OfxRectD *optionalBounds) { (void)format; Texture *gl_texture = nullptr; if (isOutput()) { gl_texture = output_textures_.value(time, nullptr); } else { TexturePtr input = input_textures_.value(time); gl_texture = input ? input.get() : nullptr; } if (!gl_texture || gl_texture->IsDummy() || !gl_texture->id().isValid()) { return nullptr; } OfxRectD rod_d = getRegionOfDefinition(time); OfxRectI rod = { static_cast(std::floor(rod_d.x1)), static_cast(std::floor(rod_d.y1)), static_cast(std::ceil(rod_d.x2)), static_cast(std::ceil(rod_d.y2)) }; OfxRectI bounds = rod; if (optionalBounds) { bounds.x1 = static_cast(std::floor(optionalBounds->x1)); bounds.y1 = static_cast(std::floor(optionalBounds->y1)); bounds.x2 = static_cast(std::ceil(optionalBounds->x2)); bounds.y2 = static_cast(std::ceil(optionalBounds->y2)); } bounds.x1 = std::max(bounds.x1, rod.x1); bounds.y1 = std::max(bounds.y1, rod.y1); bounds.x2 = std::min(bounds.x2, rod.x2); bounds.y2 = std::min(bounds.y2, rod.y2); const int bytes_per_row = params_.width() * params_.channel_count() * params_.format().byte_count(); const std::string &field = getFieldOrder(); const std::string unique_id = std::to_string( reinterpret_cast(gl_texture)) + "_" + std::to_string(static_cast(time)); const int texture_id = gl_texture->id().value(); OFX::Host::ImageEffect::Texture *texture = new OFX::Host::ImageEffect::Texture( *this, 1.0, 1.0, texture_id, GL_TEXTURE_2D, bounds, rod, bytes_per_row, field, unique_id); texture->addReference(); return texture; } #endif