From 1e2ddc30b10f948e733344919db247e7f665a7bc Mon Sep 17 00:00:00 2001 From: apavlenko Date: Fri, 25 Nov 2016 12:35:55 +0300 Subject: [PATCH] Canny via OpenVX, Node wrapper extended (query/set attribute), some naming fixes --- 3rdparty/openvx/include/ivx.hpp | 222 ++++++++++++++---- modules/core/include/opencv2/core.hpp | 1 + .../include/opencv2/core/openvx/ovx_defs.hpp | 33 +++ modules/core/include/opencv2/core/ovx.hpp | 28 +++ modules/core/src/ovx.cpp | 72 ++++++ modules/core/src/precomp.hpp | 11 +- modules/imgproc/src/canny.cpp | 73 ++++++ modules/imgproc/src/deriv.cpp | 8 +- 8 files changed, 389 insertions(+), 59 deletions(-) create mode 100644 modules/core/include/opencv2/core/openvx/ovx_defs.hpp create mode 100644 modules/core/include/opencv2/core/ovx.hpp create mode 100644 modules/core/src/ovx.cpp diff --git a/3rdparty/openvx/include/ivx.hpp b/3rdparty/openvx/include/ivx.hpp index 57eeda34a2..3d5102f5cf 100644 --- a/3rdparty/openvx/include/ivx.hpp +++ b/3rdparty/openvx/include/ivx.hpp @@ -100,6 +100,9 @@ Details: TBD namespace ivx { +inline vx_uint16 compiledWithVersion() +{ return VX_VERSION; } + /// Exception class for OpenVX runtime errors class RuntimeError : public std::runtime_error { @@ -715,8 +718,9 @@ protected: #endif // IVX_USE_EXTERNAL_REFCOUNT #ifndef VX_VERSION_1_1 -//TODO: provide wrapper for border mode -typedef vx_border_mode_t vx_border_t; +typedef vx_border_mode_t border_t; +#else +typedef vx_border_t border_t; #endif /// vx_context wrapper @@ -781,7 +785,7 @@ public: } /// vxQueryContext(VX_CONTEXT_UNIQUE_KERNELS) wrapper - vx_uint32 uniqueKernels() const + vx_uint32 uniqueKernelsNum() const { vx_uint32 v; query(VX_CONTEXT_UNIQUE_KERNELS, v); @@ -789,7 +793,7 @@ public: } /// vxQueryContext(VX_CONTEXT_MODULES) wrapper - vx_uint32 modules() const + vx_uint32 modulesNum() const { vx_uint32 v; query(VX_CONTEXT_MODULES, v); @@ -797,7 +801,7 @@ public: } /// vxQueryContext(VX_CONTEXT_REFERENCES) wrapper - vx_uint32 references() const + vx_uint32 refsNum() const { vx_uint32 v; query(VX_CONTEXT_REFERENCES, v); @@ -829,15 +833,15 @@ public: } /// vxQueryContext(VX_CONTEXT_IMMEDIATE_BORDER) wrapper - vx_border_t borderMode() const + border_t immediateBorder() const { - vx_border_t v; + border_t v; query(VX_CONTEXT_IMMEDIATE_BORDER, v); return v; } /// vxQueryContext(VX_CONTEXT_IMPLEMENTATION) wrapper - std::string implementation() const + std::string implName() const { std::vector v(VX_MAX_IMPLEMENTATION_NAME); IVX_CHECK_STATUS(vxQueryContext(ref, VX_CONTEXT_IMPLEMENTATION, &v[0], v.size() * sizeof(vx_char))); @@ -845,7 +849,7 @@ public: } /// vxQueryContext(VX_CONTEXT_EXTENSIONS) wrapper - std::string extensions() const + std::string extensionsStr() const { std::vector v(extensionsSize()); IVX_CHECK_STATUS(vxQueryContext(ref, VX_CONTEXT_EXTENSIONS, &v[0], v.size() * sizeof(vx_char))); @@ -855,14 +859,14 @@ public: /// vxQueryContext(VX_CONTEXT_UNIQUE_KERNEL_TABLE) wrapper std::vector kernelTable() const { - std::vector v(uniqueKernels()); + std::vector v(uniqueKernelsNum()); IVX_CHECK_STATUS(vxQueryContext(ref, VX_CONTEXT_UNIQUE_KERNEL_TABLE, &v[0], v.size() * sizeof(vx_kernel_info_t))); return v; } #ifdef VX_VERSION_1_1 /// vxQueryContext(VX_CONTEXT_IMMEDIATE_BORDER_POLICY) wrapper - vx_enum borderPolicy() const + vx_enum immediateBorderPolicy() const { vx_enum v; query(VX_CONTEXT_IMMEDIATE_BORDER_POLICY, v); @@ -878,10 +882,28 @@ public: } #endif - /// vxSetContextAttribute(VX_CONTEXT_IMMEDIATE_BORDER) wrapper - void setBorderMode(vx_border_t &border) - { IVX_CHECK_STATUS(vxSetContextAttribute(ref, VX_CONTEXT_IMMEDIATE_BORDER, &border, sizeof(border))); } + /// vxSetContextAttribute() wrapper + template + void setAttribute(vx_enum att, const T& value) + { IVX_CHECK_STATUS( vxSetContextAttribute(ref, att, &value, sizeof(value)) ); } + /// vxSetContextAttribute(BORDER) wrapper + void setImmediateBorder(const border_t& bm) + { setAttribute(VX_CONTEXT_IMMEDIATE_BORDER, bm); } + +#ifndef VX_VERSION_1_1 + /// vxSetContextAttribute(BORDER) wrapper + void setImmediateBorder(vx_enum mode, vx_uint32 val = 0) + { border_t bm = {mode, val}; setImmediateBorder(bm); } +#else + /// vxSetContextAttribute(BORDER) wrapper + void setImmediateBorder(vx_enum mode, const vx_pixel_value_t& val) + { border_t bm = {mode, val}; setImmediateBorder(bm); } + + /// vxSetContextAttribute(BORDER) wrapper + void setImmediateBorder(vx_enum mode) + { vx_pixel_value_t val = {}; setImmediateBorder(mode, val); } +#endif }; /// vx_graph wrapper @@ -926,7 +948,6 @@ public: { return Kernel(vxGetKernelByName(c, name.c_str())); } }; -#ifdef IVX_USE_CXX98 /// vx_node wrapper class Node : public RefWrapper @@ -952,6 +973,7 @@ public: static Node create(vx_graph graph, vx_enum kernelID, const std::vector& params) { return Node::create(graph, Kernel::getByEnum(Context::getFrom(graph), kernelID), params); } +#ifdef IVX_USE_CXX98 /// Create node for the kernel ID and set one parameter template static Node create(vx_graph g, vx_enum kernelID, @@ -1113,49 +1135,140 @@ public: return create(g, Kernel::getByEnum(Context::getFrom(g), kernelID), params); } - /// vxSetParameterByIndex() wrapper - void setParameterByIndex(vx_uint32 index, vx_reference value) - { IVX_CHECK_STATUS(vxSetParameterByIndex(ref, index, value)); } -}; - #else // not IVX_USE_CXX98 -/// vx_node wrapper -class Node : public RefWrapper -{ -public: - IVX_REF_STD_CTORS_AND_ASSIGNMENT(Node); - - /// vxCreateGenericNode() wrapper - static Node create(vx_graph g, vx_kernel k) - { return Node(vxCreateGenericNode(g, k)); } - - /// Create node for the kernel and set the parameters - static Node create(vx_graph graph, vx_kernel kernel, const std::vector& params) - { - Node node = Node::create(graph, kernel); - vx_uint32 i = 0; - for (const auto& p : params) - node.setParameterByIndex(i++, p); - return node; - } - - /// Create node for the kernel ID and set the parameters - static Node create(vx_graph graph, vx_enum kernelID, const std::vector& params) - { return Node::create(graph, Kernel::getByEnum(Context::getFrom(graph), kernelID), params); } - /// Create node for the kernel ID and set the specified parameters template static Node create(vx_graph g, vx_enum kernelID, const Ts&...args) { return create(g, Kernel::getByEnum(Context::getFrom(g), kernelID), { castToReference(args)... }); } +#endif // IVX_USE_CXX98 /// vxSetParameterByIndex() wrapper void setParameterByIndex(vx_uint32 index, vx_reference value) { IVX_CHECK_STATUS(vxSetParameterByIndex(ref, index, value)); } + + /// vxQueryNode() wrapper + template + void query(vx_enum att, T& value) const + { IVX_CHECK_STATUS( vxQueryNode(ref, att, &value, sizeof(value)) ); } + +#ifndef VX_VERSION_1_1 +static const vx_enum + VX_NODE_STATUS = VX_NODE_ATTRIBUTE_STATUS, + VX_NODE_PERFORMANCE = VX_NODE_ATTRIBUTE_PERFORMANCE, + VX_NODE_BORDER = VX_NODE_ATTRIBUTE_BORDER_MODE, + VX_NODE_LOCAL_DATA_SIZE = VX_NODE_ATTRIBUTE_LOCAL_DATA_SIZE, + VX_NODE_LOCAL_DATA_PTR = VX_NODE_ATTRIBUTE_LOCAL_DATA_PTR, + VX_BORDER_UNDEFINED = VX_BORDER_MODE_UNDEFINED; +#endif + + /// vxQueryNode(STATUS) wrapper + vx_status status() const + { + vx_status v; + query(VX_NODE_STATUS, v); + return v; + } + + /// vxQueryNode(PERFORMANCE) wrapper + vx_perf_t performance() const + { + vx_perf_t v; + query(VX_NODE_PERFORMANCE, v); + return v; + } + + /// vxQueryNode(BORDER) wrapper + border_t border() const + { + border_t v; + v.mode = VX_BORDER_UNDEFINED; + query(VX_NODE_BORDER, v); + return v; + } + + /// vxQueryNode(LOCAL_DATA_SIZE) wrapper + vx_size dataSize() const + { + vx_size v; + query(VX_NODE_LOCAL_DATA_SIZE, v); + return v; + } + + /// vxQueryNode(LOCAL_DATA_PTR) wrapper + void* dataPtr() const + { + void* v; + query(VX_NODE_LOCAL_DATA_PTR, v); + return v; + } + +#ifdef VX_VERSION_1_1 + /// vxQueryNode(PARAMETERS) wrapper + vx_uint32 paramsNum() const + { + vx_uint32 v; + query(VX_NODE_PARAMETERS, v); + return v; + } + + /// vxQueryNode(REPLICATED) wrapper + vx_bool isReplicated() const + { + vx_bool v; + query(VX_NODE_IS_REPLICATED, v); + return v; + } + + /// vxQueryNode(REPLICATE_FLAGS) wrapper + void replicateFlags(std::vector& flags) const + { + if(flags.empty()) flags.resize(paramsNum(), vx_false_e); + IVX_CHECK_STATUS( vxQueryNode(ref, VX_NODE_REPLICATE_FLAGS, &flags[0], flags.size()*sizeof(flags[0])) ); + } + + /// vxQueryNode(VX_NODE_VALID_RECT_RESET) wrapper + vx_bool resetValidRect() const + { + vx_bool v; + query(VX_NODE_VALID_RECT_RESET, v); + return v; + } +#endif // VX_VERSION_1_1 + + /// vxSetNodeAttribute() wrapper + template + void setAttribute(vx_enum att, const T& value) + { IVX_CHECK_STATUS( vxSetNodeAttribute(ref, att, &value, sizeof(value)) ); } + + /// vxSetNodeAttribute(BORDER) wrapper + void setBorder(const border_t& bm) + { setAttribute(VX_NODE_BORDER, bm); } + +#ifndef VX_VERSION_1_1 + /// vxSetNodeAttribute(BORDER) wrapper + void setBorder(vx_enum mode, vx_uint32 val = 0) + { vx_border_mode_t bm = {mode, val}; setBorder(bm); } +#else + /// vxSetNodeAttribute(BORDER) wrapper + void setBorder(vx_enum mode, const vx_pixel_value_t& val) + { vx_border_t bm = {mode, val}; setBorder(bm); } + + /// vxSetNodeAttribute(BORDER) wrapper + void setBorder(vx_enum mode) + { vx_pixel_value_t val = {}; setBorder(mode, val); } +#endif + + /// vxSetNodeAttribute(LOCAL_DATA_SIZE) wrapper + void setDataSize(vx_size size) + { setAttribute(VX_NODE_LOCAL_DATA_SIZE, size); } + + /// vxSetNodeAttribute(LOCAL_DATA_PTR) wrapper + void setDataPtr(void* ptr) + { setAttribute(VX_NODE_LOCAL_DATA_PTR, ptr); } }; -#endif // IVX_USE_CXX98 /// vx_image wrapper class Image : public RefWrapper @@ -1326,7 +1439,7 @@ static const vx_enum VX_IMAGE_SIZE = VX_IMAGE_ATTRIBUTE_SIZE; #endif -/// vxQueryImage(VX_IMAGE_WIDTH) wrapper + /// vxQueryImage(VX_IMAGE_WIDTH) wrapper vx_uint32 width() const { vx_uint32 v; @@ -1580,13 +1693,18 @@ static const vx_enum copyFrom(planeIdx, createAddressing((vx_uint32)m.cols, (vx_uint32)m.rows, (vx_int32)m.elemSize(), (vx_int32)m.step), m.ptr()); } - /* +/* +private: + cv::Mat _mat; // TODO: update copy/move-c-tors, operator=() and swapHandles() +public: static Image createFromHandle(vx_context context, const cv::Mat& mat) - { throw WrapperError(std::string(__func__)+"(): NYI"); } - - cv::Mat swapHandle(const cv::Mat& newMat) - { throw WrapperError(std::string(__func__)+"(): NYI"); } - */ + { + if(mat.empty()) throw WrapperError(std::string(__func__)+"(): empty cv::Mat"); + Image res = createFromHandle(context, matTypeToFormat(mat.type()), createAddressing(mat), mat.data ); + res._mat = mat; + return res; + } +*/ #endif //IVX_USE_OPENCV struct Patch; diff --git a/modules/core/include/opencv2/core.hpp b/modules/core/include/opencv2/core.hpp index cb2a8512fe..6b89c42efa 100644 --- a/modules/core/include/opencv2/core.hpp +++ b/modules/core/include/opencv2/core.hpp @@ -3215,5 +3215,6 @@ template<> struct ParamType #include "opencv2/core/cvstd.inl.hpp" #include "opencv2/core/utility.hpp" #include "opencv2/core/optim.hpp" +#include "opencv2/core/ovx.hpp" #endif /*OPENCV_CORE_HPP*/ diff --git a/modules/core/include/opencv2/core/openvx/ovx_defs.hpp b/modules/core/include/opencv2/core/openvx/ovx_defs.hpp new file mode 100644 index 0000000000..a5cad4d99a --- /dev/null +++ b/modules/core/include/opencv2/core/openvx/ovx_defs.hpp @@ -0,0 +1,33 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +// Copyright (C) 2016, Intel Corporation, all rights reserved. +// Third party copyrights are property of their respective owners. + +// OpenVX related definitions and declarations + +#pragma once +#ifndef OPENCV_OVX_DEFS_HPP +#define OPENCV_OVX_DEFS_HPP + +#include "cvconfig.h" + +// utility macro for running OpenVX-based implementations +#ifdef HAVE_OPENVX + +#define IVX_HIDE_INFO_WARNINGS +#define IVX_USE_OPENCV +#include "ivx.hpp" + +#define CV_OVX_RUN(condition, func, ...) \ + if (cv::useOpenVX() && (condition) && func) \ + { \ + return __VA_ARGS__; \ + } + +#else + #define CV_OVX_RUN(condition, func, ...) +#endif // HAVE_OPENVX + +#endif // OPENCV_OVX_DEFS_HPP diff --git a/modules/core/include/opencv2/core/ovx.hpp b/modules/core/include/opencv2/core/ovx.hpp new file mode 100644 index 0000000000..8bb7d54911 --- /dev/null +++ b/modules/core/include/opencv2/core/ovx.hpp @@ -0,0 +1,28 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +// Copyright (C) 2016, Intel Corporation, all rights reserved. +// Third party copyrights are property of their respective owners. + +// OpenVX related definitions and declarations + +#pragma once +#ifndef OPENCV_OVX_HPP +#define OPENCV_OVX_HPP + +#include "cvdef.h" + +namespace cv +{ +/// Check if use of OpenVX is possible +CV_EXPORTS_W bool haveOpenVX(); + +/// Check if use of OpenVX is enabled +CV_EXPORTS_W bool useOpenVX(); + +/// Enable/disable use of OpenVX +CV_EXPORTS_W void setUseOpenVX(bool flag); +} // namespace cv + +#endif // OPENCV_OVX_HPP diff --git a/modules/core/src/ovx.cpp b/modules/core/src/ovx.cpp new file mode 100644 index 0000000000..a53f5533aa --- /dev/null +++ b/modules/core/src/ovx.cpp @@ -0,0 +1,72 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +// Copyright (C) 2016, Intel Corporation, all rights reserved. +// Third party copyrights are property of their respective owners. + +// OpenVX related functions + +#include "precomp.hpp" +#include "opencv2/core/ovx.hpp" +#include "opencv2/core/openvx/ovx_defs.hpp" + +namespace cv +{ + +bool haveOpenVX() +{ +#ifdef HAVE_OPENVX + static int g_haveOpenVX = -1; + if(g_haveOpenVX < 0) + { + try + { + ivx::Context context = ivx::Context::create(); + vx_uint16 vComp = ivx::compiledWithVersion(); + vx_uint16 vCurr = context.version(); + g_haveOpenVX = + VX_VERSION_MAJOR(vComp) == VX_VERSION_MAJOR(vCurr) && + VX_VERSION_MINOR(vComp) == VX_VERSION_MINOR(vCurr) + ? 1 : 0; + } + catch(const ivx::WrapperError&) + { g_haveOpenVX = 0; } + catch(const ivx::RuntimeError&) + { g_haveOpenVX = 0; } + } + return g_haveOpenVX == 1; +#else + return false; +#endif +} + +bool useOpenVX() +{ +#ifdef HAVE_OPENVX + CoreTLSData* data = getCoreTlsData().get(); + if( data->useOpenVX < 0 ) + { + // enabled (if available) by default + data->useOpenVX = haveOpenVX() ? 1 : 0; + } + return data->useOpenVX > 0; +#else + return false; +#endif +} + +void setUseOpenVX(bool flag) +{ +#ifdef HAVE_OPENVX + if( haveOpenVX() ) + { + CoreTLSData* data = getCoreTlsData().get(); + data->useOpenVX = flag ? 1 : 0; + } +#else + CV_Assert(!flag && "OpenVX support isn't enabled at compile time"); +#endif +} + +} // namespace cv diff --git a/modules/core/src/precomp.hpp b/modules/core/src/precomp.hpp index df6c5b93ec..be30664acc 100644 --- a/modules/core/src/precomp.hpp +++ b/modules/core/src/precomp.hpp @@ -262,11 +262,13 @@ struct CoreTLSData device(0), useOpenCL(-1), //#endif useIPP(-1) - { #ifdef HAVE_TEGRA_OPTIMIZATION - useTegra = -1; + ,useTegra(-1) #endif - } +#ifdef HAVE_OPENVX + ,useOpenVX(-1) +#endif + {} RNG rng; //#ifdef HAVE_OPENCL @@ -278,6 +280,9 @@ struct CoreTLSData #ifdef HAVE_TEGRA_OPTIMIZATION int useTegra; // 1 - use, 0 - do not use, -1 - auto/not initialized #endif +#ifdef HAVE_OPENVX + int useOpenVX; // 1 - use, 0 - do not use, -1 - auto/not initialized +#endif }; TLSData& getCoreTlsData(); diff --git a/modules/imgproc/src/canny.cpp b/modules/imgproc/src/canny.cpp index 4636574b90..44ac37b1c1 100644 --- a/modules/imgproc/src/canny.cpp +++ b/modules/imgproc/src/canny.cpp @@ -45,6 +45,8 @@ #include "opencv2/core/hal/intrin.hpp" #include +#include "opencv2/core/openvx/ovx_defs.hpp" + #ifdef _MSC_VER #pragma warning( disable: 4127 ) // conditional expression is constant #endif @@ -775,6 +777,64 @@ private: ptrdiff_t mapstep; }; +#ifdef HAVE_OPENVX +static bool openvx_canny(const Mat& src, Mat& dst, int loVal, int hiVal, int kSize, bool useL2) +{ + using namespace ivx; + + Context context = Context::create(); + try + { + Image _src = Image::createFromHandle( + context, + Image::matTypeToFormat(src.type()), + Image::createAddressing(src), + src.data ); + Image _dst = Image::createFromHandle( + context, + Image::matTypeToFormat(dst.type()), + Image::createAddressing(dst), + dst.data ); + Threshold threshold = Threshold::createRange(context, VX_TYPE_UINT8, saturate_cast(loVal), saturate_cast(hiVal)); + +#if 0 + // the code below is disabled because vxuCannyEdgeDetector() + // ignores context attribute VX_CONTEXT_IMMEDIATE_BORDER + + // FIXME: may fail in multithread case + border_t prevBorder = context.immediateBorder(); + context.setImmediateBorder(VX_BORDER_REPLICATE); + IVX_CHECK_STATUS( vxuCannyEdgeDetector(context, _src, threshold, kSize, (useL2 ? VX_NORM_L2 : VX_NORM_L1), _dst) ); + context.setImmediateBorder(prevBorder); +#else + // alternative code without vxuCannyEdgeDetector() + Graph graph = Graph::create(context); + ivx::Node node = ivx::Node(vxCannyEdgeDetectorNode(graph, _src, threshold, kSize, (useL2 ? VX_NORM_L2 : VX_NORM_L1), _dst) ); + node.setBorder(VX_BORDER_REPLICATE); + graph.verify(); + graph.process(); +#endif + +#ifdef VX_VERSION_1_1 + _src.swapHandle(); + _dst.swapHandle(); +#endif + } + catch(const WrapperError& e) + { + //CV_DbgAssert(!"openvx_canny - WrapperError"); + return false; + } + catch(const RuntimeError& e) + { + //CV_DbgAssert(!"openvx_canny - RuntimeError"); + return false; + } + + return true; +} +#endif // HAVE_OPENVX + void Canny( InputArray _src, OutputArray _dst, double low_thresh, double high_thresh, int aperture_size, bool L2gradient ) @@ -805,6 +865,19 @@ void Canny( InputArray _src, OutputArray _dst, Mat src = _src.getMat(), dst = _dst.getMat(); + CV_OVX_RUN( + src.type() == CV_8UC1 && + !src.isSubmatrix() && + src.cols >= aperture_size && + src.rows >= aperture_size, + openvx_canny( + src, + dst, + cvFloor(low_thresh), + cvFloor(high_thresh), + aperture_size, + L2gradient ) ); + #ifdef HAVE_TEGRA_OPTIMIZATION if (tegra::useTegra() && tegra::canny(src, dst, low_thresh, high_thresh, aperture_size, L2gradient)) return; diff --git a/modules/imgproc/src/deriv.cpp b/modules/imgproc/src/deriv.cpp index c3c30cb354..fb3b2fff10 100644 --- a/modules/imgproc/src/deriv.cpp +++ b/modules/imgproc/src/deriv.cpp @@ -263,8 +263,8 @@ namespace cv //ATTENTION: VX_CONTEXT_IMMEDIATE_BORDER attribute change could lead to strange issues in multi-threaded environments //since OpenVX standart says nothing about thread-safety for now - vx_border_t prevBorder = ctx.borderMode(); - ctx.setBorderMode(border); + vx_border_t prevBorder = ctx.immediateBorder(); + ctx.setImmediateBorder(border); if (dtype == CV_16SC1 && ksize == 3 && ((dx | dy) == 1) && (dx + dy) == 1) { if(dx) @@ -277,7 +277,7 @@ namespace cv #if VX_VERSION <= VX_VERSION_1_0 if (ctx.vendorID() == VX_ID_KHRONOS && ((vx_size)(src.cols) <= ctx.convolutionMaxDimension() || (vx_size)(src.rows) <= ctx.convolutionMaxDimension())) { - ctx.setBorderMode(prevBorder); + ctx.setImmediateBorder(prevBorder); return false; } #endif @@ -292,7 +292,7 @@ namespace cv cnv.setScale(cscale); ivx::IVX_CHECK_STATUS(vxuConvolve(ctx, ia, cnv, ib)); } - ctx.setBorderMode(prevBorder); + ctx.setImmediateBorder(prevBorder); return true; } catch (ivx::RuntimeError & e)