RectI TrackMarker::getMarkerImageRoI(int time) const { const unsigned int mipmapLevel = 0; Point center, offset; KnobDoublePtr centerKnob = getCenterKnob(); KnobDoublePtr offsetKnob = getOffsetKnob(); center.x = centerKnob->getValueAtTime(time, 0); center.y = centerKnob->getValueAtTime(time, 1); offset.x = offsetKnob->getValueAtTime(time, 0); offset.y = offsetKnob->getValueAtTime(time, 1); RectD roiCanonical; KnobDoublePtr swBl = getSearchWindowBottomLeftKnob(); KnobDoublePtr swTr = getSearchWindowTopRightKnob(); roiCanonical.x1 = swBl->getValueAtTime(time, 0) + center.x + offset.x; roiCanonical.y1 = swBl->getValueAtTime(time, 1) + center.y + offset.y; roiCanonical.x2 = swTr->getValueAtTime(time, 0) + center.x + offset.x; roiCanonical.y2 = swTr->getValueAtTime(time, 1) + center.y + offset.y; RectI roi; NodePtr node = getContext()->getNode(); NodePtr input = node->getInput(0); if (!input) { return RectI(); } roiCanonical.toPixelEnclosing(mipmapLevel, input ? input->getEffectInstance()->getAspectRatio(-1) : 1., &roi); return roi; }
ActionRetCodeEnum RotoShapeRenderNode::isIdentity(TimeValue time, const RenderScale & scale, const RectI & roi, ViewIdx view, const ImagePlaneDesc& /*plane*/, TimeValue* inputTime, ViewIdx* inputView, int* inputNb, ImagePlaneDesc* /*inputPlane*/) { *inputView = view; NodePtr node = getNode(); *inputNb = -1; RotoDrawableItemPtr rotoItem = getAttachedRotoItem(); if (!rotoItem) { return eActionStatusFailed; } Bezier* isBezier = dynamic_cast<Bezier*>(rotoItem.get()); if (!rotoItem || !rotoItem->isActivated(time, view) || (isBezier && ((!isBezier->isCurveFinished(view) && !isBezier->isOpenBezier()) || isBezier->getControlPointsCount(view) <= 1))) { *inputTime = time; *inputNb = 0; return eActionStatusOK; } bool isPainting = isDuringPaintStrokeCreation(); RectD maskRod; getRoDFromItem(rotoItem, time, view, isPainting, &maskRod); RectI maskPixelRod; maskRod.toPixelEnclosing(scale, getAspectRatio(-1), &maskPixelRod); if ( !maskPixelRod.intersects(roi) ) { *inputTime = time; *inputNb = 0; } return eActionStatusOK; } // isIdentity
void ViewerRenderFrameSubResult::onTreeRenderFinished(int inputIndex) { PerViewerInputRenderData& inputData = perInputsData[inputIndex]; if (inputIndex == 1 && copyInputBFromA) { inputData = perInputsData[0]; return; } FrameViewRequestPtr outputRequest; if (inputData.render) { inputData.retCode = inputData.render->getStatus(); outputRequest = inputData.render->getOutputRequest(); } if (outputRequest) { inputData.viewerProcessImage = outputRequest->getRequestedScaleImagePlane(); } // There might be no output image if the RoI that was passed to render is outside of the RoD of the effect if (isFailureRetCode(inputData.retCode) || !inputData.viewerProcessImage) { inputData.viewerProcessImage.reset(); inputData.render.reset(); return; } // Find the key of the image and store it so that in the gui // we can later on re-use this key to check the cache for the timeline's cache line ImageCacheEntryPtr cacheEntry = inputData.viewerProcessImage->getCacheEntry(); if (cacheEntry) { inputData.viewerProcessImageKey = cacheEntry->getCacheKey(); } // Convert the image to a format that can be uploaded to a OpenGL texture RectI imageConvertRoI; RectD ctorCanonicalRoI = inputData.render->getCtorRoI(); if (inputData.render && !ctorCanonicalRoI.isNull()) { RenderScale scale = EffectInstance::getCombinedScale(inputData.viewerProcessImage->getMipMapLevel(), inputData.viewerProcessImage->getProxyScale()); double par = inputData.viewerProcessNode->getEffectInstance()->getAspectRatio(-1); ctorCanonicalRoI.toPixelEnclosing(scale, par, &imageConvertRoI); } else { imageConvertRoI = inputData.viewerProcessImage->getBounds(); } // If we are drawing with the RotoPaint node, only update the texture portion if (textureTransferType == OpenGLViewerI::TextureTransferArgs::eTextureTransferTypeModify) { RectI strokeArea; bool strokeAreaSet = inputData.render->getRotoPaintActiveStrokeUpdateArea(&strokeArea); if (strokeAreaSet) { imageConvertRoI = strokeArea; } } // The viewer-process node may not have rendered a 4 channel image, but this is required but the OpenGL viewer // which only draws RGBA images. // If we are in accumulation, force a copy of the image because another render thread might modify it in a future render whilst it may // still be read from the main-thread when updating the ViewerGL texture. // If texture transfer is eTextureTransferTypeOverlay, we want to upload the texture to exactly what was requested const bool forceOutputImageCopy = (inputData.viewerProcessImage == inputData.viewerProcessNode->getEffectInstance()->getAccumBuffer(inputData.viewerProcessImage->getLayer()) || (textureTransferType == OpenGLViewerI::TextureTransferArgs::eTextureTransferTypeOverlay && inputData.viewerProcessImage->getBounds() != imageConvertRoI)); inputData.viewerProcessImage = convertImageForViewerDisplay(imageConvertRoI, forceOutputImageCopy, true /*the texture must have 4 channels*/, inputData.viewerProcessImage); // Extra color-picker images as-well. if (inputData.colorPickerNode) { { FrameViewRequestPtr req = inputData.render->getExtraRequestedResultsForNode(inputData.colorPickerNode); if (req) { inputData.colorPickerImage = req->getRequestedScaleImagePlane(); if (inputData.colorPickerImage) { inputData.colorPickerImage = convertImageForViewerDisplay(inputData.colorPickerImage->getBounds(), false, false /*the picker can accept non 4-channel image*/, inputData.colorPickerImage); } } } if (inputData.colorPickerInputNode) { FrameViewRequestPtr req = inputData.render->getExtraRequestedResultsForNode(inputData.colorPickerInputNode); if (req) { inputData.colorPickerInputImage = req->getRequestedScaleImagePlane(); if (inputData.colorPickerInputImage) { inputData.colorPickerInputImage = convertImageForViewerDisplay(inputData.colorPickerInputImage->getBounds(), false, false /*the picker can accept non 4-channel image*/, inputData.colorPickerInputImage); } } } } inputData.render.reset(); } // onTreeRenderFinished
/* * @brief This is called by LibMV to retrieve an image either for reference or as search frame. */ mv::FrameAccessor::Key TrackerFrameAccessor::GetImage(int /*clip*/, int frame, mv::FrameAccessor::InputMode input_mode, int downscale, // Downscale by 2^downscale. const mv::Region* region, // Get full image if NULL. const mv::FrameAccessor::Transform* /*transform*/, // May be NULL. mv::FloatImage** destination) { // Since libmv only uses MONO images for now we have only optimized for this case, remove and handle properly // other case(s) when they get integrated into libmv. assert(input_mode == mv::FrameAccessor::MONO); FrameAccessorCacheKey key; key.frame = frame; key.mipMapLevel = downscale; key.mode = input_mode; /* Check if a frame exists in the cache with matching key and bounds enclosing the given region */ RectI roi; if (region) { convertLibMVRegionToRectI(*region, _imp->formatHeight, &roi); QMutexLocker k(&_imp->cacheMutex); std::pair<FrameAccessorCache::iterator, FrameAccessorCache::iterator> range = _imp->cache.equal_range(key); for (FrameAccessorCache::iterator it = range.first; it != range.second; ++it) { if ( (roi.x1 >= it->second.bounds.x1) && (roi.x2 <= it->second.bounds.x2) && ( roi.y1 >= it->second.bounds.y1) && ( roi.y2 <= it->second.bounds.y2) ) { #ifdef TRACE_LIB_MV qDebug() << QThread::currentThread() << "FrameAccessor::GetImage():" << "Found cached image at frame" << frame << "with RoI x1=" << region->min(0) << "y1=" << region->max(1) << "x2=" << region->max(0) << "y2=" << region->min(1); #endif // LibMV is kinda dumb on this we must necessarily copy the data either via CopyFrom or the // assignment constructor: // EDIT: fixed libmv *destination = it->second.image.get(); //destination->CopyFrom<float>(*it->second.image); ++it->second.referenceCount; return (mv::FrameAccessor::Key)it->second.image.get(); } } } EffectInstancePtr effect; if (_imp->trackerInput) { effect = _imp->trackerInput->getEffectInstance(); } if (!effect) { return (mv::FrameAccessor::Key)0; } // Not in accessor cache, call renderRoI RenderScale scale; scale.y = scale.x = Image::getScaleFromMipMapLevel( (unsigned int)downscale ); RectD precomputedRoD; if (!region) { bool isProjectFormat; StatusEnum stat = effect->getRegionOfDefinition_public(_imp->trackerInput->getHashValue(), frame, scale, ViewIdx(0), &precomputedRoD, &isProjectFormat); if (stat == eStatusFailed) { return (mv::FrameAccessor::Key)0; } double par = effect->getAspectRatio(-1); precomputedRoD.toPixelEnclosing( (unsigned int)downscale, par, &roi ); } std::list<ImageComponents> components; components.push_back( ImageComponents::getRGBComponents() ); NodePtr node = _imp->context->getNode(); const bool isRenderUserInteraction = true; const bool isSequentialRender = false; AbortableRenderInfoPtr abortInfo = AbortableRenderInfo::create(false, 0); AbortableThread* isAbortable = dynamic_cast<AbortableThread*>( QThread::currentThread() ); if (isAbortable) { isAbortable->setAbortInfo( isRenderUserInteraction, abortInfo, node->getEffectInstance() ); } ParallelRenderArgsSetter::CtorArgsPtr tlsArgs(new ParallelRenderArgsSetter::CtorArgs); tlsArgs->time = frame; tlsArgs->view = ViewIdx(0); tlsArgs->isRenderUserInteraction = isRenderUserInteraction; tlsArgs->isSequential = isSequentialRender; tlsArgs->abortInfo = abortInfo; tlsArgs->treeRoot = node; tlsArgs->textureIndex = 0; tlsArgs->timeline = node->getApp()->getTimeLine(); tlsArgs->activeRotoPaintNode = NodePtr(); tlsArgs->activeRotoDrawableItem = RotoDrawableItemPtr(); tlsArgs->isDoingRotoNeatRender = false; tlsArgs->isAnalysis = true; tlsArgs->draftMode = false; tlsArgs->stats = RenderStatsPtr(); ParallelRenderArgsSetter frameRenderArgs(tlsArgs); // Stats EffectInstance::RenderRoIArgs args( frame, scale, downscale, ViewIdx(0), false, roi, precomputedRoD, components, eImageBitDepthFloat, true, _imp->context->getNode()->getEffectInstance(), eStorageModeRAM /*returnOpenGLTex*/, frame); std::map<ImageComponents, ImagePtr> planes; EffectInstance::RenderRoIRetCode stat = effect->renderRoI(args, &planes); if ( (stat != EffectInstance::eRenderRoIRetCodeOk) || planes.empty() ) { #ifdef TRACE_LIB_MV qDebug() << QThread::currentThread() << "FrameAccessor::GetImage():" << "Failed to call renderRoI on input at frame" << frame << "with RoI x1=" << roi.x1 << "y1=" << roi.y1 << "x2=" << roi.x2 << "y2=" << roi.y2; #endif return (mv::FrameAccessor::Key)0; } assert( !planes.empty() ); const ImagePtr& sourceImage = planes.begin()->second; RectI sourceBounds = sourceImage->getBounds(); RectI intersectedRoI; if ( !roi.intersect(sourceBounds, &intersectedRoI) ) { #ifdef TRACE_LIB_MV qDebug() << QThread::currentThread() << "FrameAccessor::GetImage():" << "RoI does not intersect the source image bounds (RoI x1=" << roi.x1 << "y1=" << roi.y1 << "x2=" << roi.x2 << "y2=" << roi.y2 << ")"; #endif return (mv::FrameAccessor::Key)0; } #ifdef TRACE_LIB_MV qDebug() << QThread::currentThread() << "FrameAccessor::GetImage():" << "renderRoi (frame" << frame << ") OK (BOUNDS= x1=" << sourceBounds.x1 << "y1=" << sourceBounds.y1 << "x2=" << sourceBounds.x2 << "y2=" << sourceBounds.y2 << ") (ROI = " << roi.x1 << "y1=" << roi.y1 << "x2=" << roi.x2 << "y2=" << roi.y2 << ")"; #endif /* Copy the Natron image to the LivMV float image */ FrameAccessorCacheEntry entry; entry.image.reset( new MvFloatImage( intersectedRoI.height(), intersectedRoI.width() ) ); entry.bounds = intersectedRoI; entry.referenceCount = 1; natronImageToLibMvFloatImage(_imp->enabledChannels, sourceImage.get(), intersectedRoI, *entry.image); // we ignore the transform parameter and do it in natronImageToLibMvFloatImage instead *destination = entry.image.get(); //destination->CopyFrom<float>(*entry.image); //insert into the cache { QMutexLocker k(&_imp->cacheMutex); _imp->cache.insert( std::make_pair(key, entry) ); } #ifdef TRACE_LIB_MV qDebug() << QThread::currentThread() << "FrameAccessor::GetImage():" << "Rendered frame" << frame << "with RoI x1=" << intersectedRoI.x1 << "y1=" << intersectedRoI.y1 << "x2=" << intersectedRoI.x2 << "y2=" << intersectedRoI.y2; #endif return (mv::FrameAccessor::Key)entry.image.get(); } // TrackerFrameAccessor::GetImage