Merge pull request #6243 from mshabunin:hal_morph
This commit is contained in:
commit
e792ee89de
@ -221,6 +221,18 @@ static inline IppiSize ippiSize(const cv::Size & _size)
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline IppiPoint ippiPoint(const cv::Point & _point)
|
||||||
|
{
|
||||||
|
IppiPoint point = { _point.x, _point.y };
|
||||||
|
return point;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline IppiPoint ippiPoint(int x, int y)
|
||||||
|
{
|
||||||
|
IppiPoint point = { x, y };
|
||||||
|
return point;
|
||||||
|
}
|
||||||
|
|
||||||
static inline IppiBorderType ippiGetBorderType(int borderTypeNI)
|
static inline IppiBorderType ippiGetBorderType(int borderTypeNI)
|
||||||
{
|
{
|
||||||
return borderTypeNI == cv::BORDER_CONSTANT ? ippBorderConst :
|
return borderTypeNI == cv::BORDER_CONSTANT ? ippBorderConst :
|
||||||
|
@ -44,6 +44,22 @@ struct CV_EXPORTS SepFilter2D
|
|||||||
virtual ~SepFilter2D() {}
|
virtual ~SepFilter2D() {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct CV_EXPORTS MorphContext
|
||||||
|
{
|
||||||
|
static Ptr<MorphContext> create(int op, int src_type, int dst_type, int max_width, int max_height,
|
||||||
|
int kernel_type, uchar * kernel_data, size_t kernel_step,
|
||||||
|
int kernel_width, int kernel_height,
|
||||||
|
int anchor_x, int anchor_y,
|
||||||
|
int borderType, const double borderValue[4],
|
||||||
|
int iterations, bool isSubmatrix, bool allowInplace);
|
||||||
|
virtual void apply(uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int width, int height,
|
||||||
|
int roi_width, int roi_height, int roi_x, int roi_y,
|
||||||
|
int roi_width2, int roi_height2, int roi_x2, int roi_y2) = 0;
|
||||||
|
virtual ~MorphContext() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
//! @}
|
//! @}
|
||||||
|
|
||||||
}}
|
}}
|
||||||
|
@ -37,6 +37,8 @@ the use of this software, even if advised of the possibility of such damage.
|
|||||||
#ifndef __OPENCV_IMGPROC_FILTERENGINE_HPP__
|
#ifndef __OPENCV_IMGPROC_FILTERENGINE_HPP__
|
||||||
#define __OPENCV_IMGPROC_FILTERENGINE_HPP__
|
#define __OPENCV_IMGPROC_FILTERENGINE_HPP__
|
||||||
|
|
||||||
|
#include "opencv2/imgproc.hpp"
|
||||||
|
|
||||||
namespace cv
|
namespace cv
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -21,6 +21,14 @@ inline int hal_ni_sepFilterFree(cvhalFilter2D *) { return CV_HAL_ERROR_NOT_IMPLE
|
|||||||
#define cv_hal_sepFilter hal_ni_sepFilter
|
#define cv_hal_sepFilter hal_ni_sepFilter
|
||||||
#define cv_hal_sepFilterFree hal_ni_sepFilterFree
|
#define cv_hal_sepFilterFree hal_ni_sepFilterFree
|
||||||
|
|
||||||
|
inline int hal_ni_morphInit(cvhalFilter2D **, int, int, int, int, int, int, uchar *, size_t, int, int, int, int, int, const double[4], int, bool, bool) { return CV_HAL_ERROR_NOT_IMPLEMENTED; }
|
||||||
|
inline int hal_ni_morph(cvhalFilter2D *, uchar *, size_t, uchar *, size_t, int, int, int, int, int, int, int, int, int, int) { return CV_HAL_ERROR_NOT_IMPLEMENTED; }
|
||||||
|
inline int hal_ni_morphFree(cvhalFilter2D *) { return CV_HAL_ERROR_NOT_IMPLEMENTED; }
|
||||||
|
|
||||||
|
#define cv_hal_morphInit hal_ni_morphInit
|
||||||
|
#define cv_hal_morph hal_ni_morph
|
||||||
|
#define cv_hal_morphFree hal_ni_morphFree
|
||||||
|
|
||||||
#include "custom_hal.hpp"
|
#include "custom_hal.hpp"
|
||||||
|
|
||||||
#endif // OPENCV_IMGPROC_HAL_REPLACEMENT_HPP
|
#endif // OPENCV_IMGPROC_HAL_REPLACEMENT_HPP
|
||||||
|
@ -43,11 +43,15 @@
|
|||||||
#include "precomp.hpp"
|
#include "precomp.hpp"
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include "opencl_kernels_imgproc.hpp"
|
#include "opencl_kernels_imgproc.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
#include "hal_replacement.hpp"
|
||||||
|
|
||||||
/****************************************************************************************\
|
/****************************************************************************************\
|
||||||
Basic Morphological Operations: Erosion & Dilation
|
Basic Morphological Operations: Erosion & Dilation
|
||||||
\****************************************************************************************/
|
\****************************************************************************************/
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
namespace cv
|
namespace cv
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -584,19 +588,11 @@ typedef MorphFVec<VMax32f> DilateVec32f;
|
|||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
#ifdef HAVE_TEGRA_OPTIMIZATION
|
|
||||||
using tegra::ErodeRowVec8u;
|
|
||||||
using tegra::DilateRowVec8u;
|
|
||||||
|
|
||||||
using tegra::ErodeColumnVec8u;
|
|
||||||
using tegra::DilateColumnVec8u;
|
|
||||||
#else
|
|
||||||
typedef MorphRowNoVec ErodeRowVec8u;
|
typedef MorphRowNoVec ErodeRowVec8u;
|
||||||
typedef MorphRowNoVec DilateRowVec8u;
|
typedef MorphRowNoVec DilateRowVec8u;
|
||||||
|
|
||||||
typedef MorphColumnNoVec ErodeColumnVec8u;
|
typedef MorphColumnNoVec ErodeColumnVec8u;
|
||||||
typedef MorphColumnNoVec DilateColumnVec8u;
|
typedef MorphColumnNoVec DilateColumnVec8u;
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef MorphRowNoVec ErodeRowVec16u;
|
typedef MorphRowNoVec ErodeRowVec16u;
|
||||||
typedef MorphRowNoVec DilateRowVec16u;
|
typedef MorphRowNoVec DilateRowVec16u;
|
||||||
@ -1081,247 +1077,157 @@ cv::Mat cv::getStructuringElement(int shape, Size ksize, Point anchor)
|
|||||||
namespace cv
|
namespace cv
|
||||||
{
|
{
|
||||||
|
|
||||||
class MorphologyRunner : public ParallelLoopBody
|
// ===== 1. replacement implementation
|
||||||
|
|
||||||
|
struct ReplacementMorphImpl : public hal::MorphContext
|
||||||
{
|
{
|
||||||
public:
|
cvhalFilter2D * ctx;
|
||||||
MorphologyRunner(Mat _src, Mat _dst, int _nStripes, int _iterations,
|
bool isInitialized;
|
||||||
int _op, Mat _kernel, Point _anchor,
|
bool init(int op, int src_type, int dst_type, int max_width, int max_height,
|
||||||
int _rowBorderType, int _columnBorderType, const Scalar& _borderValue) :
|
int kernel_type, uchar * kernel_data, size_t kernel_step, int kernel_width, int kernel_height,
|
||||||
borderValue(_borderValue)
|
int anchor_x, int anchor_y,
|
||||||
|
int borderType, const double borderValue[4],
|
||||||
|
int iterations, bool isSubmatrix, bool allowInplace)
|
||||||
{
|
{
|
||||||
src = _src;
|
int res = cv_hal_morphInit(&ctx, op, src_type, dst_type, max_width, max_height,
|
||||||
dst = _dst;
|
kernel_type, kernel_data, kernel_step, kernel_width, kernel_height,
|
||||||
|
anchor_x, anchor_y,
|
||||||
nStripes = _nStripes;
|
borderType, borderValue,
|
||||||
iterations = _iterations;
|
iterations, isSubmatrix, allowInplace);
|
||||||
|
isInitialized = (res == CV_HAL_ERROR_OK);
|
||||||
op = _op;
|
return isInitialized;
|
||||||
kernel = _kernel;
|
|
||||||
anchor = _anchor;
|
|
||||||
rowBorderType = _rowBorderType;
|
|
||||||
columnBorderType = _columnBorderType;
|
|
||||||
}
|
}
|
||||||
|
void apply(uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int width, int height,
|
||||||
void operator () ( const Range& range ) const
|
int roi_width, int roi_height, int roi_x, int roi_y,
|
||||||
|
int roi_width2, int roi_height2, int roi_x2, int roi_y2)
|
||||||
{
|
{
|
||||||
int row0 = std::min(cvRound(range.start * src.rows / nStripes), src.rows);
|
if (isInitialized)
|
||||||
int row1 = std::min(cvRound(range.end * src.rows / nStripes), src.rows);
|
|
||||||
|
|
||||||
/*if(0)
|
|
||||||
printf("Size = (%d, %d), range[%d,%d), row0 = %d, row1 = %d\n",
|
|
||||||
src.rows, src.cols, range.start, range.end, row0, row1);*/
|
|
||||||
|
|
||||||
Mat srcStripe = src.rowRange(row0, row1);
|
|
||||||
Mat dstStripe = dst.rowRange(row0, row1);
|
|
||||||
|
|
||||||
Ptr<FilterEngine> f = createMorphologyFilter(op, src.type(), kernel, anchor,
|
|
||||||
rowBorderType, columnBorderType, borderValue );
|
|
||||||
|
|
||||||
{
|
{
|
||||||
Point ofs;
|
int res = cv_hal_morph(ctx, src_data, src_step, dst_data, dst_step, width, height,
|
||||||
Size wsz(srcStripe.cols, srcStripe.rows);
|
roi_width, roi_height,
|
||||||
srcStripe.locateROI( wsz, ofs );
|
roi_x, roi_y,
|
||||||
|
roi_width2, roi_height2,
|
||||||
f->apply( srcStripe, dstStripe, wsz, ofs );
|
roi_x2, roi_y2);
|
||||||
}
|
if (res != CV_HAL_ERROR_OK)
|
||||||
|
CV_Error(Error::StsNotImplemented, "Failed to run HAL morph implementation");
|
||||||
{
|
}
|
||||||
Point ofs;
|
}
|
||||||
Size wsz(dstStripe.cols, dstStripe.rows);
|
~ReplacementMorphImpl()
|
||||||
dstStripe.locateROI( wsz, ofs );
|
{
|
||||||
|
if (isInitialized)
|
||||||
for( int i = 1; i < iterations; i++ )
|
{
|
||||||
f->apply( dstStripe, dstStripe, wsz, ofs );
|
int res = cv_hal_morphFree(ctx);
|
||||||
|
if (res != CV_HAL_ERROR_OK)
|
||||||
|
CV_Error(Error::StsNotImplemented, "Failed to run HAL morph implementation");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
Mat src;
|
|
||||||
Mat dst;
|
|
||||||
int nStripes;
|
|
||||||
int iterations;
|
|
||||||
|
|
||||||
int op;
|
|
||||||
Mat kernel;
|
|
||||||
Point anchor;
|
|
||||||
int rowBorderType;
|
|
||||||
int columnBorderType;
|
|
||||||
Scalar borderValue;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ===== 2. IPP implementation
|
||||||
|
|
||||||
#ifdef HAVE_IPP
|
#ifdef HAVE_IPP
|
||||||
static bool ipp_MorphReplicate(int op, const Mat &src, Mat &dst, const Mat &kernel,
|
|
||||||
const Size& ksize, const Point &anchor, bool rectKernel)
|
|
||||||
{
|
|
||||||
#if IPP_VERSION_X100 >= 810
|
#if IPP_VERSION_X100 >= 810
|
||||||
int type = src.type();
|
|
||||||
const Mat* _src = &src;
|
|
||||||
Mat temp;
|
|
||||||
if (src.data == dst.data)
|
|
||||||
{
|
|
||||||
src.copyTo(temp);
|
|
||||||
_src = &temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
IppiSize roiSize = {src.cols, src.rows};
|
template <int cvtype> struct IppMorphTrait {};
|
||||||
IppiSize kernelSize = {ksize.width, ksize.height};
|
|
||||||
|
|
||||||
if (!rectKernel)
|
|
||||||
{
|
|
||||||
#if IPP_VERSION_X100 >= 900
|
#if IPP_VERSION_X100 >= 900
|
||||||
if (((kernel.cols - 1) / 2 != anchor.x) || ((kernel.rows - 1) / 2 != anchor.y))
|
|
||||||
return false;
|
#define INIT_TRAIT(cvtype, ipptype, flavor, channels, zerodef)\
|
||||||
#define IPP_MORPH_CASE(cvtype, flavor, data_type) \
|
template <>\
|
||||||
case cvtype: \
|
struct IppMorphTrait<cvtype>\
|
||||||
{\
|
{\
|
||||||
int specSize = 0, bufferSize = 0;\
|
typedef Ipp##ipptype ipp_data_type;\
|
||||||
if (0 > ippiMorphologyBorderGetSize_##flavor(roiSize, kernelSize, &specSize, &bufferSize))\
|
enum { cn = channels };\
|
||||||
return false;\
|
IppDataType getDataType() {return ipp##ipptype;}\
|
||||||
IppiMorphState *pSpec = (IppiMorphState*)ippMalloc(specSize);\
|
\
|
||||||
Ipp8u *pBuffer = (Ipp8u*)ippMalloc(bufferSize);\
|
IppStatus getMorphSize(IppiSize roiSize, IppiSize maskSize, int* pSpecSize, int* pBufferSize) {return ippiMorphologyBorderGetSize_##flavor(roiSize, maskSize, pSpecSize, pBufferSize);}\
|
||||||
if (0 > ippiMorphologyBorderInit_##flavor(roiSize, kernel.ptr(), kernelSize, pSpec, pBuffer))\
|
IppStatus morphInit(IppiSize roiSize, const Ipp8u* pMask, IppiSize maskSize, IppiMorphState* pMorphSpec, Ipp8u* pBuffer) {return ippiMorphologyBorderInit_##flavor(roiSize, pMask, maskSize, pMorphSpec, pBuffer);}\
|
||||||
{\
|
IppStatus filterGetMinSize(IppiSize dstRoiSize, IppiSize maskSize, IppDataType dataType, int numChannels, int* pBufferSize) {return ippiFilterMinBorderGetBufferSize(dstRoiSize, maskSize, dataType, numChannels, pBufferSize);}\
|
||||||
ippFree(pBuffer);\
|
IppStatus filterGetMaxSize(IppiSize dstRoiSize, IppiSize maskSize, IppDataType dataType, int numChannels, int* pBufferSize) {return ippiFilterMaxBorderGetBufferSize(dstRoiSize, maskSize, dataType, numChannels, pBufferSize);}\
|
||||||
ippFree(pSpec);\
|
IppStatus filterMinBorder(const ipp_data_type* pSrc, int srcStep, ipp_data_type* pDst, int dstStep, IppiSize dstRoiSize, IppiSize maskSize, IppiPoint, Ipp8u* pBuffer) { ipp_data_type zerodef; return ippiFilterMinBorder_##flavor(pSrc, srcStep, pDst, dstStep, dstRoiSize, maskSize, ippBorderRepl, zero, pBuffer); }\
|
||||||
return false;\
|
IppStatus filterMaxBorder(const ipp_data_type* pSrc, int srcStep, ipp_data_type* pDst, int dstStep, IppiSize dstRoiSize, IppiSize maskSize, IppiPoint, Ipp8u* pBuffer) { ipp_data_type zerodef; return ippiFilterMaxBorder_##flavor(pSrc, srcStep, pDst, dstStep, dstRoiSize, maskSize, ippBorderRepl, zero, pBuffer); }\
|
||||||
}\
|
IppStatus morphDilate(const ipp_data_type* pSrc, int srcStep, ipp_data_type* pDst, int dstStep, IppiSize roiSize, const IppiMorphState* pMorphSpec, Ipp8u* pBuffer) { ipp_data_type zerodef; return ippiDilateBorder_##flavor(pSrc, srcStep, pDst, dstStep, roiSize, ippBorderRepl, zero, pMorphSpec, pBuffer); }\
|
||||||
bool ok = false;\
|
IppStatus morphErode(const ipp_data_type* pSrc, int srcStep, ipp_data_type* pDst, int dstStep, IppiSize roiSize, const IppiMorphState* pMorphSpec, Ipp8u* pBuffer) { ipp_data_type zerodef; return ippiErodeBorder_##flavor(pSrc, srcStep, pDst, dstStep, roiSize, ippBorderRepl, zero, pMorphSpec, pBuffer); }\
|
||||||
if (op == MORPH_ERODE)\
|
};
|
||||||
ok = (0 <= ippiErodeBorder_##flavor(_src->ptr<Ipp##data_type>(), (int)_src->step[0], dst.ptr<Ipp##data_type>(), (int)dst.step[0],\
|
|
||||||
roiSize, ippBorderRepl, 0, pSpec, pBuffer));\
|
|
||||||
else\
|
|
||||||
ok = (0 <= ippiDilateBorder_##flavor(_src->ptr<Ipp##data_type>(), (int)_src->step[0], dst.ptr<Ipp##data_type>(), (int)dst.step[0],\
|
|
||||||
roiSize, ippBorderRepl, 0, pSpec, pBuffer));\
|
|
||||||
ippFree(pBuffer);\
|
|
||||||
ippFree(pSpec);\
|
|
||||||
return ok;\
|
|
||||||
}\
|
|
||||||
break;
|
|
||||||
#else
|
#else
|
||||||
if (((kernel.cols - 1) / 2 != anchor.x) || ((kernel.rows - 1) / 2 != anchor.y))
|
|
||||||
return false;
|
|
||||||
#define IPP_MORPH_CASE(cvtype, flavor, data_type) \
|
|
||||||
case cvtype: \
|
|
||||||
{\
|
|
||||||
int specSize = 0, bufferSize = 0;\
|
|
||||||
if (0 > ippiMorphologyBorderGetSize_##flavor(roiSize.width, kernelSize, &specSize, &bufferSize))\
|
|
||||||
return false;\
|
|
||||||
IppiMorphState *pSpec = (IppiMorphState*)ippMalloc(specSize);\
|
|
||||||
Ipp8u *pBuffer = (Ipp8u*)ippMalloc(bufferSize);\
|
|
||||||
if (0 > ippiMorphologyBorderInit_##flavor(roiSize.width, kernel.ptr(), kernelSize, pSpec, pBuffer))\
|
|
||||||
{\
|
|
||||||
ippFree(pBuffer);\
|
|
||||||
ippFree(pSpec);\
|
|
||||||
return false;\
|
|
||||||
}\
|
|
||||||
bool ok = false;\
|
|
||||||
if (op == MORPH_ERODE)\
|
|
||||||
ok = (0 <= ippiErodeBorder_##flavor(_src->ptr<Ipp##data_type>(), (int)_src->step[0], dst.ptr<Ipp##data_type>(), (int)dst.step[0],\
|
|
||||||
roiSize, ippBorderRepl, 0, pSpec, pBuffer));\
|
|
||||||
else\
|
|
||||||
ok = (0 <= ippiDilateBorder_##flavor(_src->ptr<Ipp##data_type>(), (int)_src->step[0], dst.ptr<Ipp##data_type>(), (int)dst.step[0],\
|
|
||||||
roiSize, ippBorderRepl, 0, pSpec, pBuffer));\
|
|
||||||
ippFree(pBuffer);\
|
|
||||||
ippFree(pSpec);\
|
|
||||||
return ok;\
|
|
||||||
}\
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
CV_SUPPRESS_DEPRECATED_START
|
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
IPP_MORPH_CASE(CV_8UC1, 8u_C1R, 8u);
|
|
||||||
IPP_MORPH_CASE(CV_8UC3, 8u_C3R, 8u);
|
|
||||||
IPP_MORPH_CASE(CV_8UC4, 8u_C4R, 8u);
|
|
||||||
IPP_MORPH_CASE(CV_32FC1, 32f_C1R, 32f);
|
|
||||||
IPP_MORPH_CASE(CV_32FC3, 32f_C3R, 32f);
|
|
||||||
IPP_MORPH_CASE(CV_32FC4, 32f_C4R, 32f);
|
|
||||||
default:
|
|
||||||
;
|
|
||||||
}
|
|
||||||
CV_SUPPRESS_DEPRECATED_END
|
|
||||||
#undef IPP_MORPH_CASE
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
#if IPP_VERSION_X100 != 900 // Problems with accuracy in 9.0.0
|
|
||||||
#if IPP_VERSION_X100 >= 900
|
|
||||||
if (((kernelSize.width - 1) / 2 != anchor.x) || ((kernelSize.height - 1) / 2 != anchor.y)) // Arbitrary anchor is no longer supporeted since IPP 9.0.0
|
|
||||||
return false;
|
|
||||||
|
|
||||||
#define IPP_MORPH_CASE(cvtype, flavor, data_type, cn) \
|
#define INIT_TRAIT(cvtype, ipptype, flavor, channels, zerodef)\
|
||||||
case cvtype: \
|
template <>\
|
||||||
{\
|
struct IppMorphTrait<cvtype>\
|
||||||
if (op == MORPH_ERODE)\
|
{\
|
||||||
{\
|
typedef Ipp##ipptype ipp_data_type;\
|
||||||
int bufSize = 0;\
|
enum { cn = channels };\
|
||||||
if (0 > ippiFilterMinBorderGetBufferSize(roiSize, kernelSize, ipp##data_type, cn, &bufSize))\
|
IppDataType getDataType() {return ipp##ipptype;}\
|
||||||
return false;\
|
\
|
||||||
AutoBuffer<uchar> buf(bufSize + 64);\
|
IppStatus getMorphSize(IppiSize roiSize, IppiSize maskSize, int* pSpecSize, int* pBufferSize) {return ippiMorphologyBorderGetSize_##flavor(roiSize.width, maskSize, pSpecSize, pBufferSize);}\
|
||||||
uchar* buffer = alignPtr((uchar*)buf, 32);\
|
IppStatus morphInit(IppiSize roiSize, const Ipp8u* pMask, IppiSize maskSize, IppiMorphState* pMorphSpec, Ipp8u* pBuffer) {return ippiMorphologyBorderInit_##flavor(roiSize.width, pMask, maskSize, pMorphSpec, pBuffer);}\
|
||||||
return (0 <= ippiFilterMinBorder_##flavor(_src->ptr<Ipp##data_type>(), (int)_src->step[0], dst.ptr<Ipp##data_type>(), (int)dst.step[0], roiSize, kernelSize, ippBorderRepl, 0, buffer));\
|
IppStatus filterGetMinSize(IppiSize dstRoiSize, IppiSize maskSize, IppDataType, int, int* pBufferSize) {return ippiFilterMinGetBufferSize_##flavor(dstRoiSize.width, maskSize, pBufferSize);}\
|
||||||
}\
|
IppStatus filterGetMaxSize(IppiSize dstRoiSize, IppiSize maskSize, IppDataType, int, int* pBufferSize) {return ippiFilterMinGetBufferSize_##flavor(dstRoiSize.width, maskSize, pBufferSize);}\
|
||||||
else\
|
IppStatus filterMinBorder(const ipp_data_type* pSrc, int srcStep, ipp_data_type* pDst, int dstStep, IppiSize dstRoiSize, IppiSize maskSize, IppiPoint anchor, Ipp8u* pBuffer) { return ippiFilterMinBorderReplicate_##flavor(pSrc, srcStep, pDst, dstStep, dstRoiSize, maskSize, anchor, pBuffer); }\
|
||||||
{\
|
IppStatus filterMaxBorder(const ipp_data_type* pSrc, int srcStep, ipp_data_type* pDst, int dstStep, IppiSize dstRoiSize, IppiSize maskSize, IppiPoint anchor, Ipp8u* pBuffer) { return ippiFilterMaxBorderReplicate_##flavor(pSrc, srcStep, pDst, dstStep, dstRoiSize, maskSize, anchor, pBuffer); }\
|
||||||
int bufSize = 0;\
|
IppStatus morphDilate(const ipp_data_type* pSrc, int srcStep, ipp_data_type* pDst, int dstStep, IppiSize roiSize, const IppiMorphState* pMorphSpec, Ipp8u* pBuffer) {return ippiDilateBorder_##flavor(pSrc, srcStep, pDst, dstStep, roiSize, ippBorderRepl, 0, pMorphSpec, pBuffer);}\
|
||||||
if (0 > ippiFilterMaxBorderGetBufferSize(roiSize, kernelSize, ipp##data_type, cn, &bufSize))\
|
IppStatus morphErode(const ipp_data_type* pSrc, int srcStep, ipp_data_type* pDst, int dstStep, IppiSize roiSize, const IppiMorphState* pMorphSpec, Ipp8u* pBuffer) {return ippiErodeBorder_##flavor(pSrc, srcStep, pDst, dstStep, roiSize, ippBorderRepl, 0, pMorphSpec, pBuffer);}\
|
||||||
return false;\
|
};
|
||||||
AutoBuffer<uchar> buf(bufSize + 64);\
|
|
||||||
uchar* buffer = alignPtr((uchar*)buf, 32);\
|
|
||||||
return (0 <= ippiFilterMaxBorder_##flavor(_src->ptr<Ipp##data_type>(), (int)_src->step[0], dst.ptr<Ipp##data_type>(), (int)dst.step[0], roiSize, kernelSize, ippBorderRepl, 0, buffer));\
|
|
||||||
}\
|
|
||||||
}\
|
|
||||||
break;
|
|
||||||
#else
|
|
||||||
IppiPoint point = {anchor.x, anchor.y};
|
|
||||||
|
|
||||||
#define IPP_MORPH_CASE(cvtype, flavor, data_type, cn) \
|
|
||||||
case cvtype: \
|
|
||||||
{\
|
|
||||||
int bufSize = 0;\
|
|
||||||
if (0 > ippiFilterMinGetBufferSize_##flavor(src.cols, kernelSize, &bufSize))\
|
|
||||||
return false;\
|
|
||||||
AutoBuffer<uchar> buf(bufSize + 64);\
|
|
||||||
uchar* buffer = alignPtr((uchar*)buf, 32);\
|
|
||||||
if (op == MORPH_ERODE)\
|
|
||||||
return (0 <= ippiFilterMinBorderReplicate_##flavor(_src->ptr<Ipp##data_type>(), (int)_src->step[0], dst.ptr<Ipp##data_type>(), (int)dst.step[0], roiSize, kernelSize, point, buffer));\
|
|
||||||
return (0 <= ippiFilterMaxBorderReplicate_##flavor(_src->ptr<Ipp##data_type>(), (int)_src->step[0], dst.ptr<Ipp##data_type>(), (int)dst.step[0], roiSize, kernelSize, point, buffer));\
|
|
||||||
}\
|
|
||||||
break;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
CV_SUPPRESS_DEPRECATED_START
|
INIT_TRAIT(CV_8UC1, 8u, 8u_C1R, 1, zero = 0)
|
||||||
switch (type)
|
INIT_TRAIT(CV_8UC3, 8u, 8u_C3R, 3, zero[3] = {0})
|
||||||
{
|
INIT_TRAIT(CV_8UC4, 8u, 8u_C4R, 4, zero[4] = {0})
|
||||||
IPP_MORPH_CASE(CV_8UC1, 8u_C1R, 8u, 1);
|
INIT_TRAIT(CV_32FC1, 32f, 32f_C1R, 1, zero = 0)
|
||||||
IPP_MORPH_CASE(CV_8UC3, 8u_C3R, 8u, 3);
|
INIT_TRAIT(CV_32FC3, 32f, 32f_C3R, 3, zero[3] = {0})
|
||||||
IPP_MORPH_CASE(CV_8UC4, 8u_C4R, 8u, 4);
|
INIT_TRAIT(CV_32FC4, 32f, 32f_C4R, 4, zero[4] = {0})
|
||||||
IPP_MORPH_CASE(CV_32FC1, 32f_C1R, 32f, 1);
|
|
||||||
IPP_MORPH_CASE(CV_32FC3, 32f_C3R, 32f, 3);
|
|
||||||
IPP_MORPH_CASE(CV_32FC4, 32f_C4R, 32f, 4);
|
|
||||||
default:
|
|
||||||
;
|
|
||||||
}
|
|
||||||
CV_SUPPRESS_DEPRECATED_END
|
|
||||||
#undef IPP_MORPH_CASE
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
CV_UNUSED(op); CV_UNUSED(src); CV_UNUSED(dst); CV_UNUSED(kernel); CV_UNUSED(ksize); CV_UNUSED(anchor); CV_UNUSED(rectKernel);
|
|
||||||
#endif
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool ipp_MorphOp(int op, InputArray _src, OutputArray _dst,
|
#undef INIT_TRAIT
|
||||||
const Mat& _kernel, Point anchor, int iterations,
|
|
||||||
int borderType, const Scalar &borderValue)
|
//--------------------------------------
|
||||||
|
|
||||||
|
struct IppMorphBaseImpl : public hal::MorphContext
|
||||||
{
|
{
|
||||||
Mat src = _src.getMat(), kernel = _kernel;
|
virtual bool init(int _op, int _src_type, int dst_type, int max_width, int max_height,
|
||||||
int type = src.type(), depth = CV_MAT_DEPTH(type), cn = CV_MAT_CN(type);
|
int kernel_type, uchar * kernel_data, size_t kernel_step, int kernel_width, int kernel_height,
|
||||||
|
int anchor_x, int anchor_y,
|
||||||
|
int borderType, const double borderValue[4],
|
||||||
|
int iterations, bool isSubmatrix, bool allowInplace) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
if( !( depth == CV_8U || depth == CV_32F ) || !(cn == 1 || cn == 3 || cn == 4) ||
|
template <int cvtype>
|
||||||
!( borderType == cv::BORDER_REPLICATE || (borderType == cv::BORDER_CONSTANT && borderValue == morphologyDefaultBorderValue() &&
|
struct IppMorphImpl : public IppMorphBaseImpl
|
||||||
kernel.size() == Size(3,3)) ) || !( op == MORPH_DILATE || op == MORPH_ERODE) || _src.isSubmatrix() )
|
{
|
||||||
|
IppMorphTrait<cvtype> trait;
|
||||||
|
typedef typename IppMorphTrait<cvtype>::ipp_data_type ipp_data_type;
|
||||||
|
IppAutoBuffer<IppiMorphState> specBuf;
|
||||||
|
IppAutoBuffer<Ipp8u> workBuf;
|
||||||
|
IppiSize kernelSize;
|
||||||
|
bool rectKernel;
|
||||||
|
IppiPoint anchor;
|
||||||
|
int op;
|
||||||
|
int src_type;
|
||||||
|
int border;
|
||||||
|
|
||||||
|
bool init(int _op, int _src_type, int dst_type, int max_width, int max_height,
|
||||||
|
int kernel_type, uchar * kernel_data, size_t kernel_step, int kernel_width, int kernel_height,
|
||||||
|
int anchor_x, int anchor_y,
|
||||||
|
int borderType, const double borderValue[4],
|
||||||
|
int iterations, bool isSubmatrix, bool allowInplace)
|
||||||
|
{
|
||||||
|
border = borderType; // TODO: remove
|
||||||
|
anchor = ippiPoint(anchor_x, anchor_y);
|
||||||
|
CV_UNUSED(dst_type);
|
||||||
|
src_type = _src_type;
|
||||||
|
|
||||||
|
Mat kernel(Size(kernel_width, kernel_height), kernel_type, kernel_data, kernel_step);
|
||||||
|
int depth = CV_MAT_DEPTH(src_type), cn = CV_MAT_CN(src_type);
|
||||||
|
|
||||||
|
if( !( depth == CV_8U || depth == CV_32F )
|
||||||
|
|| !(cn == 1 || cn == 3 || cn == 4)
|
||||||
|
|| !( borderType == cv::BORDER_REPLICATE
|
||||||
|
|| (borderType == cv::BORDER_CONSTANT && Vec<double, 4>(borderValue) == morphologyDefaultBorderValue() && kernel.size() == Size(3,3)))
|
||||||
|
|| !( op == MORPH_DILATE || op == MORPH_ERODE)
|
||||||
|
|| isSubmatrix
|
||||||
|
|| allowInplace)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// In case BORDER_CONSTANT, IPPMorphReplicate works correct with kernels of size 3*3 only
|
// In case BORDER_CONSTANT, IPPMorphReplicate works correct with kernels of size 3*3 only
|
||||||
@ -1350,22 +1256,14 @@ static bool ipp_MorphOp(int op, InputArray _src, OutputArray _dst,
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Size ksize = !kernel.empty() ? kernel.size() : Size(3,3);
|
Size ksize = !kernel.empty() ? kernel.size() : Size(3,3);
|
||||||
|
|
||||||
_dst.create( src.size(), src.type() );
|
rectKernel = false;
|
||||||
Mat dst = _dst.getMat();
|
|
||||||
|
|
||||||
if( iterations == 0 || kernel.rows*kernel.cols == 1 )
|
|
||||||
{
|
|
||||||
src.copyTo(dst);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool rectKernel = false;
|
|
||||||
if( kernel.empty() )
|
if( kernel.empty() )
|
||||||
{
|
{
|
||||||
ksize = Size(1+iterations*2,1+iterations*2);
|
ksize = Size(1+iterations*2,1+iterations*2);
|
||||||
anchor = Point(iterations, iterations);
|
anchor = ippiPoint(iterations, iterations);
|
||||||
rectKernel = true;
|
rectKernel = true;
|
||||||
iterations = 1;
|
iterations = 1;
|
||||||
}
|
}
|
||||||
@ -1373,7 +1271,7 @@ static bool ipp_MorphOp(int op, InputArray _src, OutputArray _dst,
|
|||||||
{
|
{
|
||||||
ksize = Size(ksize.width + (iterations-1)*(ksize.width-1),
|
ksize = Size(ksize.width + (iterations-1)*(ksize.width-1),
|
||||||
ksize.height + (iterations-1)*(ksize.height-1)),
|
ksize.height + (iterations-1)*(ksize.height-1)),
|
||||||
anchor = Point(anchor.x*iterations, anchor.y*iterations);
|
anchor = ippiPoint(anchor.x*iterations, anchor.y*iterations);
|
||||||
kernel = Mat();
|
kernel = Mat();
|
||||||
rectKernel = true;
|
rectKernel = true;
|
||||||
iterations = 1;
|
iterations = 1;
|
||||||
@ -1383,9 +1281,195 @@ static bool ipp_MorphOp(int op, InputArray _src, OutputArray _dst,
|
|||||||
if( iterations > 1 )
|
if( iterations > 1 )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return ipp_MorphReplicate( op, src, dst, kernel, ksize, anchor, rectKernel );
|
IppiSize roiSize = {max_width, max_height};
|
||||||
|
kernelSize = ippiSize(ksize);
|
||||||
|
op = _op;
|
||||||
|
|
||||||
|
IppStatus res;
|
||||||
|
if (!rectKernel)
|
||||||
|
{
|
||||||
|
if (((kernel.cols - 1) / 2 != anchor.x) || ((kernel.rows - 1) / 2 != anchor.y))
|
||||||
|
return false;
|
||||||
|
int specSize = 0, bufferSize = 0;
|
||||||
|
res = trait.getMorphSize(roiSize, kernelSize, &specSize, &bufferSize);
|
||||||
|
if (res >= 0)
|
||||||
|
{
|
||||||
|
specBuf.Alloc(specSize);
|
||||||
|
workBuf.Alloc(bufferSize);
|
||||||
|
res = trait.morphInit(roiSize, kernel.ptr(), kernelSize, specBuf, workBuf);
|
||||||
|
if (res >= 0)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (((kernelSize.width - 1) / 2 != anchor.x) || ((kernelSize.height - 1) / 2 != anchor.y))
|
||||||
|
return false;
|
||||||
|
if (op == MORPH_ERODE)
|
||||||
|
{
|
||||||
|
int bufSize = 0;
|
||||||
|
res = trait.filterGetMinSize(roiSize, kernelSize, trait.getDataType(), trait.cn, &bufSize);
|
||||||
|
if (res >= 0)
|
||||||
|
{
|
||||||
|
workBuf.Alloc(bufSize);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int bufSize = 0;
|
||||||
|
res = trait.filterGetMaxSize(roiSize, kernelSize, trait.getDataType(), trait.cn, &bufSize);
|
||||||
|
if (res >= 0)
|
||||||
|
{
|
||||||
|
workBuf.Alloc(bufSize);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void apply(uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int width, int height,
|
||||||
|
int roi_width, int roi_height, int roi_x, int roi_y,
|
||||||
|
int roi_width2, int roi_height2, int roi_x2, int roi_y2)
|
||||||
|
{
|
||||||
|
CV_UNUSED(roi_width); CV_UNUSED(roi_height); CV_UNUSED(roi_x); CV_UNUSED(roi_y);
|
||||||
|
CV_UNUSED(roi_width2); CV_UNUSED(roi_height2); CV_UNUSED(roi_x2); CV_UNUSED(roi_y2);
|
||||||
|
if (src_data == dst_data)
|
||||||
|
CV_Error(Error::StsBadArg, "IPP Morph inplace is not alowed");
|
||||||
|
|
||||||
|
IppiSize roiSize = {width, height};
|
||||||
|
|
||||||
|
IppStatus res;
|
||||||
|
if (!rectKernel)
|
||||||
|
{
|
||||||
|
if (op == MORPH_ERODE)
|
||||||
|
res = (trait.morphErode((ipp_data_type*)src_data, (int)src_step, (ipp_data_type*)dst_data, (int)dst_step, roiSize, specBuf, workBuf));
|
||||||
|
else
|
||||||
|
res = (trait.morphDilate((ipp_data_type*)src_data, (int)src_step, (ipp_data_type*)dst_data, (int)dst_step, roiSize, specBuf, workBuf));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (op == MORPH_ERODE)
|
||||||
|
res = (trait.filterMinBorder((ipp_data_type*)src_data, (int)src_step, (ipp_data_type*)dst_data, (int)dst_step, roiSize, kernelSize, anchor, workBuf));
|
||||||
|
else
|
||||||
|
res = (trait.filterMaxBorder((ipp_data_type*)src_data, (int)src_step, (ipp_data_type*)dst_data, (int)dst_step, roiSize, kernelSize, anchor, workBuf));
|
||||||
|
}
|
||||||
|
if (res < 0)
|
||||||
|
CV_Error(Error::StsBadArg, "Failed to run IPP morph");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static IppMorphBaseImpl * createIppImpl(int type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case CV_8UC1: return new IppMorphImpl<CV_8UC1>();
|
||||||
|
case CV_8UC3: return new IppMorphImpl<CV_8UC3>();
|
||||||
|
case CV_8UC4: return new IppMorphImpl<CV_8UC4>();
|
||||||
|
case CV_32FC1: return new IppMorphImpl<CV_32FC1>();
|
||||||
|
case CV_32FC3: return new IppMorphImpl<CV_32FC3>();
|
||||||
|
case CV_32FC4: return new IppMorphImpl<CV_32FC4>();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif // IPP_VERSION_X100 >= 810
|
||||||
|
#endif // HAVE_IPP
|
||||||
|
|
||||||
|
// ===== 3. Fallback implementation
|
||||||
|
|
||||||
|
struct OcvMorphImpl : public hal::MorphContext
|
||||||
|
{
|
||||||
|
Ptr<FilterEngine> f;
|
||||||
|
int iterations;
|
||||||
|
int src_type;
|
||||||
|
int dst_type;
|
||||||
|
bool init(int op, int _src_type, int _dst_type, int, int,
|
||||||
|
int kernel_type, uchar * kernel_data, size_t kernel_step, int kernel_width, int kernel_height,
|
||||||
|
int anchor_x, int anchor_y,
|
||||||
|
int borderType, const double _borderValue[4],
|
||||||
|
int _iterations, bool, bool)
|
||||||
|
{
|
||||||
|
iterations = _iterations;
|
||||||
|
src_type = _src_type;
|
||||||
|
dst_type = _dst_type;
|
||||||
|
Mat kernel(Size(kernel_width, kernel_height), kernel_type, kernel_data, kernel_step);
|
||||||
|
Point anchor(anchor_x, anchor_y);
|
||||||
|
Vec<double, 4> borderValue(_borderValue);
|
||||||
|
f = createMorphologyFilter(op, src_type, kernel, anchor, borderType, borderType, borderValue );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void apply(uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int width, int height,
|
||||||
|
int roi_width, int roi_height, int roi_x, int roi_y,
|
||||||
|
int roi_width2, int roi_height2, int roi_x2, int roi_y2)
|
||||||
|
{
|
||||||
|
Mat src(Size(width, height), src_type, src_data, src_step);
|
||||||
|
Mat dst(Size(width, height), dst_type, dst_data, dst_step);
|
||||||
|
{
|
||||||
|
Point ofs(roi_x, roi_y);
|
||||||
|
Size wsz(roi_width, roi_height);
|
||||||
|
f->apply( src, dst, wsz, ofs );
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Point ofs(roi_x2, roi_y2);
|
||||||
|
Size wsz(roi_width2, roi_height2);
|
||||||
|
for( int i = 1; i < iterations; i++ )
|
||||||
|
f->apply( dst, dst, wsz, ofs );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===== HAL interface implementation
|
||||||
|
|
||||||
|
namespace hal {
|
||||||
|
|
||||||
|
Ptr<MorphContext> MorphContext ::create(int op, int src_type, int dst_type, int max_width, int max_height,
|
||||||
|
int kernel_type, uchar * kernel_data, size_t kernel_step, int kernel_width, int kernel_height,
|
||||||
|
int anchor_x, int anchor_y,
|
||||||
|
int borderType, const double borderValue[4],
|
||||||
|
int iterations, bool isSubmatrix, bool allowInplace)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
ReplacementMorphImpl * impl = new ReplacementMorphImpl();
|
||||||
|
if (impl->init(op, src_type, dst_type, max_width, max_height,
|
||||||
|
kernel_type, kernel_data, kernel_step, kernel_width, kernel_height,
|
||||||
|
anchor_x, anchor_y,
|
||||||
|
borderType, borderValue, iterations, isSubmatrix, allowInplace))
|
||||||
|
{
|
||||||
|
return Ptr<MorphContext>(impl);
|
||||||
|
}
|
||||||
|
delete impl;
|
||||||
|
}
|
||||||
|
#if defined(HAVE_IPP) && IPP_VERSION_X100 >= 810
|
||||||
|
CV_IPP_CHECK()
|
||||||
|
{
|
||||||
|
IppMorphBaseImpl * impl = createIppImpl(src_type);
|
||||||
|
if (impl)
|
||||||
|
{
|
||||||
|
if (impl->init(op, src_type, dst_type, max_width, max_height,
|
||||||
|
kernel_type, kernel_data, kernel_step, kernel_width, kernel_height,
|
||||||
|
anchor_x, anchor_y,
|
||||||
|
borderType, borderValue, iterations, isSubmatrix, allowInplace))
|
||||||
|
{
|
||||||
|
return Ptr<MorphContext>(impl);
|
||||||
|
}
|
||||||
|
delete impl;
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
{
|
||||||
|
OcvMorphImpl * impl = new OcvMorphImpl();
|
||||||
|
impl->init(op, src_type, dst_type, max_width, max_height,
|
||||||
|
kernel_type, kernel_data, kernel_step, kernel_width, kernel_height,
|
||||||
|
anchor_x, anchor_y,
|
||||||
|
borderType, borderValue, iterations, isSubmatrix, allowInplace);
|
||||||
|
return Ptr<MorphContext>(impl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // cv::hal
|
||||||
|
|
||||||
#ifdef HAVE_OPENCL
|
#ifdef HAVE_OPENCL
|
||||||
|
|
||||||
@ -1763,22 +1847,24 @@ static void morphOp( int op, InputArray _src, OutputArray _dst,
|
|||||||
iterations = 1;
|
iterations = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
CV_IPP_RUN(IPP_VERSION_X100 >= 810, ipp_MorphOp(op, _src, _dst, kernel, anchor, iterations, borderType, borderValue))
|
|
||||||
|
|
||||||
Mat src = _src.getMat();
|
Mat src = _src.getMat();
|
||||||
_dst.create( src.size(), src.type() );
|
_dst.create( src.size(), src.type() );
|
||||||
Mat dst = _dst.getMat();
|
Mat dst = _dst.getMat();
|
||||||
|
|
||||||
int nStripes = 1;
|
Point s_ofs;
|
||||||
#if defined HAVE_TEGRA_OPTIMIZATION
|
Size s_wsz(src.cols, src.rows);
|
||||||
if (src.data != dst.data && iterations == 1 && //NOTE: threads are not used for inplace processing
|
src.locateROI(s_wsz, s_ofs);
|
||||||
(borderType & BORDER_ISOLATED) == 0 && //TODO: check border types
|
Point d_ofs;
|
||||||
src.rows >= 64 ) //NOTE: just heuristics
|
Size d_wsz(dst.cols, dst.rows);
|
||||||
nStripes = 4;
|
dst.locateROI(d_wsz, d_ofs);
|
||||||
#endif
|
|
||||||
|
|
||||||
parallel_for_(Range(0, nStripes),
|
Ptr<hal::MorphContext> ctx = hal::MorphContext::create(op, src.type(), dst.type(), src.cols, src.rows,
|
||||||
MorphologyRunner(src, dst, nStripes, iterations, op, kernel, anchor, borderType, borderType, borderValue));
|
kernel.type(), kernel.data, kernel.step, kernel.cols, kernel.rows,
|
||||||
|
anchor.x, anchor.y, borderType, borderValue.val, iterations,
|
||||||
|
src.isSubmatrix(), src.data == dst.data);
|
||||||
|
ctx->apply(src.data, src.step, dst.data, dst.step, src.cols, src.rows,
|
||||||
|
s_wsz.width, s_wsz.height, s_ofs.x, s_ofs.y,
|
||||||
|
d_wsz.width, d_wsz.height, d_ofs.x, d_ofs.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user